diff options
Diffstat (limited to 'lib/snmp/src/compile')
-rw-r--r-- | lib/snmp/src/compile/Makefile | 125 | ||||
-rw-r--r-- | lib/snmp/src/compile/depend.mk | 46 | ||||
-rw-r--r-- | lib/snmp/src/compile/modules.mk | 36 | ||||
-rw-r--r-- | lib/snmp/src/compile/snmpc.erl | 1358 | ||||
-rw-r--r-- | lib/snmp/src/compile/snmpc.hrl | 153 | ||||
-rw-r--r-- | lib/snmp/src/compile/snmpc_lib.erl | 1819 | ||||
-rw-r--r-- | lib/snmp/src/compile/snmpc_lib.hrl | 37 | ||||
-rw-r--r-- | lib/snmp/src/compile/snmpc_mib_gram.yrl | 973 | ||||
-rw-r--r-- | lib/snmp/src/compile/snmpc_mib_to_hrl.erl | 391 | ||||
-rw-r--r-- | lib/snmp/src/compile/snmpc_misc.erl | 173 | ||||
-rw-r--r-- | lib/snmp/src/compile/snmpc_misc.hrl | 74 | ||||
-rw-r--r-- | lib/snmp/src/compile/snmpc_tok.erl | 357 |
12 files changed, 5542 insertions, 0 deletions
diff --git a/lib/snmp/src/compile/Makefile b/lib/snmp/src/compile/Makefile new file mode 100644 index 0000000000..4be60e1835 --- /dev/null +++ b/lib/snmp/src/compile/Makefile @@ -0,0 +1,125 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 1997-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% + +include $(ERL_TOP)/make/target.mk + +EBIN = ../../ebin + +include $(ERL_TOP)/make/$(TARGET)/otp.mk + + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk + +VSN = $(SNMP_VSN) + + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/snmp-$(VSN) + + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +include modules.mk + +ERL_FILES = $(MODULES:%=%.erl) + +TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +GENERATED_PARSER = $(PARSER_MODULE:%=%.erl) + +PARSER_TARGET = $(PARSER_MODULE).$(EMULATOR) + + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ifeq ($(WARN_UNUSED_VARS),true) +ERL_COMPILE_FLAGS += +warn_unused_vars +endif + +ERL_COMPILE_FLAGS += -I../../include \ + -Dversion=\"$(VSN)$(PRE_VSN)\" \ + +'{parse_transform,sys_pre_attributes}' \ + +'{attribute,insert,app_vsn,$(APP_VSN)}' \ + -I$(ERL_TOP)/lib/stdlib + +YRL_FLAGS = -o . + + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug: + @${MAKE} TYPE=debug opt + +opt: $(TARGET_FILES) + +clean: + rm -f $(TARGET_FILES) $(GENERATED_PARSER) + rm -f core *~ + +docs: + +info: + @echo "PARSER_SRC: $(PARSER_SRC)" + @echo "PARSER_MODULE: $(PARSER_MODULE)" + @echo "" + @echo "GENERATED_PARSER: $(GENERATED_PARSER)" + @echo "PARSER_TARGET: $(PARSER_TARGET)" + @echo "" + @echo "MODULES: $(MODULES)" + @echo "" + @echo "TARGET_FILES: $(TARGET_FILES)" + @echo "" + @echo "EBIN: $(EBIN)" + @echo "" + @echo "" + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + +parser: $(PARSER_TARGET) + +$(GENERATED_PARSER): $(PARSER_SRC) + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/compiler + $(INSTALL_DATA) $(PARSER_SRC) $(ERL_FILES) $(INTERNAL_HRL_FILES) $(RELSYSDIR)/src/compiler + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin + +release_docs_spec: + +include depend.mk + diff --git a/lib/snmp/src/compile/depend.mk b/lib/snmp/src/compile/depend.mk new file mode 100644 index 0000000000..75af1bf293 --- /dev/null +++ b/lib/snmp/src/compile/depend.mk @@ -0,0 +1,46 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2004-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% + +snmpc_mib_gram.erl: snmpc_mib_gram.yrl + +$(EBIN)/snmpc.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + snmpc.erl \ + snmpc.hrl + +$(EBIN)/snmpc_lib.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + snmpc_lib.erl \ + snmpc.hrl + +$(EBIN)/snmpc_tok.$(EMULATOR): \ + snmpc_tok.erl + +$(EBIN)/snmpc_misc.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + snmpc_misc.erl + +$(EBIN)/snmpc_mib_to_hrl.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + snmpc_mib_to_hrl.erl + +$(EBIN)/snmpc_mib_gram.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + snmpc_mib_gram.erl + diff --git a/lib/snmp/src/compile/modules.mk b/lib/snmp/src/compile/modules.mk new file mode 100644 index 0000000000..6365b0e694 --- /dev/null +++ b/lib/snmp/src/compile/modules.mk @@ -0,0 +1,36 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2004-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% + +PARSER_SRC = snmpc_mib_gram.yrl + +PARSER_MODULE = $(PARSER_SRC:%.yrl=%) + +MODULES = \ + $(PARSER_MODULE) \ + snmpc \ + snmpc_lib \ + snmpc_mib_to_hrl \ + snmpc_misc \ + snmpc_tok + + +INTERNAL_HRL_FILES = \ + snmpc.hrl \ + snmpc_lib.hrl \ + snmpc_misc.hrl diff --git a/lib/snmp/src/compile/snmpc.erl b/lib/snmp/src/compile/snmpc.erl new file mode 100644 index 0000000000..8a1f15d4a4 --- /dev/null +++ b/lib/snmp/src/compile/snmpc.erl @@ -0,0 +1,1358 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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(snmpc). + +%% API +-export([compile/1, compile/2, compile/3, + mib_to_hrl/1, mib_to_hrl/3, + is_consistent/1]). + +%% Debug +-export([look_at/1]). + +%% Internal Exports +-export([init/3]). + +-include_lib("stdlib/include/erl_compile.hrl"). +-include("snmp_types.hrl"). +-include("snmpc.hrl"). +-include("snmpc_lib.hrl"). + + +look_at(Mib) -> + io:format("~p ~n", [snmpc_lib:look_at(Mib)]). + + +%%----------------------------------------------------------------- +%% Misc compiler stuff +%%----------------------------------------------------------------- + +is_consistent(Filenames) -> + snmpc_lib:is_consistent(Filenames). + +mib_to_hrl(MibName) -> + snmpc_mib_to_hrl:convert(MibName). + +mib_to_hrl(MibName, HrlFile, Opts) -> + snmpc_mib_to_hrl:compile(MibName, HrlFile, Opts). + + +%%%----------------------------------------------------------------- +%%% Interface for erl_compile. +%%%----------------------------------------------------------------- + +compile(Input, _Output, Options) -> + case compile(Input, make_options(Options)) of + {ok, _} -> + ok; + {error, Reason} -> + io:format("~p", [Reason]), + error + end. + +%% Converts generic options to format expected by compile/2 + +make_options(#options{includes = Incs, + outdir = Outdir, + warning = Warning, + specific = Spec}) -> + + OutdirOpt = {outdir, Outdir}, + + WarningOpt = + case Warning of + 0 -> {warnings, false}; + _ -> {warnings, true} + end, + + IncludeOpt = + {i, case Incs of + [] -> + [""]; + _ -> + lists:map(fun(Dir) -> Dir++"/" end, Incs) + end}, + + [WarningOpt, OutdirOpt, IncludeOpt | Spec]. + +%% Returns: {ok, File}|{error, Reason} +compile([AtomFilename]) when is_atom(AtomFilename) -> + compile(atom_to_list(AtomFilename), []), % from cmd line + halt(); +compile(FileName) -> + compile(FileName, []). + + +%%---------------------------------------------------------------------- +%% Options: +%% {deprecated, bool()} true +%% {group_check, bool()} true +%% {db, volatile|persistent|mnesia} volatile +%% {i, [import_dir_string()]} ["./"] +%% {il, [import_lib_dir_string()]} [] +%% {warnings, bool()} true +%% {outdir, string()} "./" +%% description +%% reference +%% imports +%% module_identity +%% {module, string()} +%% no_defs +%% (hidden) {verbosity, trace|debug|log|info|silence} silence +%% (hidden) version +%% (hidden) options +%%---------------------------------------------------------------------- + +compile(FileName, Options) when is_list(FileName) -> + true = snmpc_misc:is_string(FileName), + DefOpts = [{deprecated, true}, + {group_check, true}, + {i, ["./"]}, + {db, volatile}, + {warnings, true}, + {outdir, "./"}, + {il, []}], + Opts = update_options(DefOpts, Options), + case check_options(Opts) of + ok -> + maybe_display_version(Opts), + maybe_display_options(Opts), + Pid = spawn_link(?MODULE,init,[self(),FileName,Opts]), + receive + {compile_result,R} -> R; + {'EXIT',Pid, Reason} when Reason =/= normal -> + exit(Reason) + end; + {error, Reason} -> + {error, Reason} + end. + +maybe_display_version(Opts) -> + case lists:member(version, Opts) of + true -> + Vsn = (catch get_version()), + io:format("version: ~s~n", [Vsn]); + false -> + ok + end. + +get_version() -> + MI = ?MODULE:module_info(), + Attr = get_info(attributes, MI), + Vsn = get_info(app_vsn, Attr), + Comp = get_info(compile, MI), + Time = get_info(time, Comp), + {Year, Month, Day, Hour, Min, Sec} = Time, + io_lib:format("~s [~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w]", + [Vsn, Year, Month, Day, Hour, Min, Sec]). + +maybe_display_options(Opts) -> + case lists:member(options, Opts) of + true -> + {F, A} = get_options(Opts, [], []), + io:format("options: " ++ F ++ "~n", A); + false -> + ok + end. + +get_options([], Formats, Args) -> + {lists:concat(lists:reverse(Formats)), lists:reverse(Args)}; +get_options([{deprecated, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n deprecated: ~w"|Formats], [Val|Args]); +get_options([{group_check, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n group_check: ~w"|Formats], [Val|Args]); +get_options([{db, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n db: ~w"|Formats], [Val|Args]); +get_options([{i, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n i: ~p"|Formats], [Val|Args]); +get_options([{il, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n il: ~p"|Formats], [Val|Args]); +get_options([{outdir, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n outdir: ~s"|Formats], [Val|Args]); +get_options([{description, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n description: ~w"|Formats], [Val|Args]); +get_options([description|Opts], Formats, Args) -> + get_options(Opts, ["~n description"|Formats], Args); +get_options([{reference, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n reference: ~w"|Formats], [Val|Args]); +get_options([reference|Opts], Formats, Args) -> + get_options(Opts, ["~n reference"|Formats], Args); +get_options([{warnings, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n warnings: ~w"|Formats], [Val|Args]); +get_options([{verbosity, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n verbosity: ~w"|Formats], [Val|Args]); +get_options([imports|Opts], Formats, Args) -> + get_options(Opts, ["~n imports"|Formats], Args); +get_options([module_identity|Opts], Formats, Args) -> + get_options(Opts, ["~n module_identity"|Formats], Args); +get_options([_|Opts], Formats, Args) -> + get_options(Opts, Formats, Args). + + +get_info(Key, Info) -> + case lists:keysearch(Key, 1, Info) of + {value, {Key, Val}} -> + Val; + false -> + throw("undefined") + end. + +% p(F, A) -> +% io:format("DBG: " ++ F ++ "~n", A). + +update_options([], Options) -> + Options; +update_options([{Key,DefVal}|DefOpts], Options) -> + case snmpc_misc:assq(Key, Options) of + false -> + update_options(DefOpts, [{Key,DefVal}|Options]); + {value, Val} when Key =:= i -> + Options1 = + lists:keyreplace(Key, 1, Options, {Key, Val++DefVal}), + update_options(DefOpts, Options1); + {value, Val} when Key =:= il -> + Options1 = + lists:keyreplace(Key, 1, Options, {Key, Val++DefVal}), + update_options(DefOpts, Options1); + {value, DefVal} -> %% Same value, no need to update + update_options(DefOpts, Options); + {value, Val} -> %% New value, so update + Options1 = + lists:keyreplace(Key, 1, Options, {Key, Val}), + update_options(DefOpts, Options1) + end. + +check_options([]) -> ok; +check_options([no_symbolic_info|T]) -> check_options(T); +check_options([{outdir, Str} | T]) when is_list(Str) -> + check_options(T); +check_options([{debug, Atom} | T]) when is_atom(Atom) -> + check_options(T); +check_options([{deprecated, Atom} | T]) when is_atom(Atom) -> + check_options(T); +check_options([{group_check, Atom} | T]) when is_atom(Atom) -> + check_options(T); +check_options([{warnings, Bool} | T]) -> + check_bool(warnings, Bool), + check_options(T); +check_options([{db, volatile} | T]) -> + check_options(T); +check_options([{db, persistent} | T]) -> + check_options(T); +check_options([{db, mnesia} | T]) -> + check_options(T); +check_options([{i, [Str|_]} | T]) when is_list(Str) -> + check_options(T); +check_options([{il, []} | T]) -> + check_options(T); +check_options([{il, [Str|_]} | T]) when is_list(Str) -> + check_options(T); +check_options([{description, Bool}| T]) -> + check_bool(description, Bool), + check_options(T); +check_options([description| T]) -> %% same as {description, true} + check_options(T); +check_options([{reference, Bool}| T]) -> + check_bool(reference, Bool), + check_options(T); +check_options([reference| T]) -> %% same as {reference, true} + check_options(T); +check_options([{verbosity, V} | T]) when is_atom(V) -> + snmpc_lib:vvalidate(V), + check_options(T); +check_options([version| T]) -> + check_options(T); +check_options([options| T]) -> + check_options(T); +check_options([imports| T]) -> + check_options(T); +check_options([module_identity| T]) -> + check_options(T); +check_options([{module, M} | T]) when is_atom(M) -> + check_options(T); +check_options([no_defs| T]) -> + check_options(T); +check_options([Opt|_]) -> + {error, {invalid_option, Opt}}. + + +check_bool(_Key, Bool) when (Bool =:= true) orelse (Bool =:= false) -> + ok; +check_bool(Key, Val) -> + {error, {invalid_option, {Key, Val}}}. + +get_group_check(Options) -> + snmpc_lib:key1search(group_check, Options, true). + +get_deprecated(Options) -> + snmpc_lib:key1search(deprecated, Options, true). + +get_description(Options) -> + get_bool_option(description, Options). + +get_reference(Options) -> + get_bool_option(reference, Options). + +get_bool_option(Option, Options) -> + case lists:member(Option, Options) of + false -> + snmpc_lib:key1search(Option, Options, false); + true -> + true + end. + +make_description(Message) -> + case get(description) of + true -> + Message; + _ -> + undefined + end. + +make_reference(undefined) -> + []; +make_reference(Reference) -> + case get(reference) of + true -> + [{reference, Reference}]; + _ -> + [] + end. + + + +%%---------------------------------------------------------------------- +%% verbosity stuff +%%---------------------------------------------------------------------- + +%% Verbosity level is selected from three (historical reasons) +%% options: warnings, debug and verbosity +%% - If warnings is true, then verbosity is _atleast_ warning +%% (even if the verbosity flag is set to silence) +%% - If debug is true, the verbosity is _atleast_ log +%% - Otherwise, verbosity is used as is. +get_verbosity(Options) -> + WarningsSeverity = + case snmpc_lib:key1search(warnings, Options) of + true -> + warning; + _ -> + silence + end, + case snmpc_lib:key1search(verbosity, Options) of + undefined -> + %% Backward compatible: If not defined then try debug and convert + case snmpc_lib:key1search(debug, Options, false) of + true -> + log; + false -> + WarningsSeverity + end; + silence -> + WarningsSeverity; + Verbosity -> + Verbosity + end. + + +%%---------------------------------------------------------------------- +%% The compile process. +%%---------------------------------------------------------------------- + +init(From, MibFileName, Options) -> + {A,B,C} = now(), + random:seed(A,B,C), + put(options, Options), + put(verbosity, get_verbosity(Options)), + put(description, get_description(Options)), + put(reference, get_reference(Options)), + File = filename:rootname(MibFileName, ".mib"), + put(filename, filename:basename(File ++ ".mib")), + R = case catch c_impl(File) of + {ok, OutFile} -> {ok, OutFile}; + {'EXIT',error} -> {error, compilation_failed}; + Error -> exit(Error) + end, + From ! {compile_result, R}. + + +c_impl(File) -> + {ok, PData} = parse(File), + ?vtrace("Syntax analysis:" + "~n PData: ~p", [PData]), + MibName = compile_parsed_data(PData), + ?vtrace("Compiler output:" + "~n CDATA: ~p", [get(cdata)]), + save(File, MibName, get(options)). + +compile_parsed_data(#pdata{mib_name = MibName, + imports = Imports, + defs = Definitions}) -> + snmpc_lib:import(Imports), + update_imports(Imports), + Deprecated = get_deprecated(get(options)), + definitions_loop(Definitions, Deprecated), + MibName. + +update_imports(Imports) -> + case lists:member(imports, get(options)) of + true -> + IMPs = do_update_imports(Imports, []), + CDATA = get(cdata), + put(cdata, CDATA#cdata{imports = IMPs}); + false -> + ok + end. + +do_update_imports([], Acc) -> + lists:reverse(Acc); +do_update_imports([{{Mib, ImportsFromMib0},_Line}|Imports], Acc) -> + ImportsFromMib = [Name || {_, Name} <- ImportsFromMib0], + Import = {Mib, ImportsFromMib}, + do_update_imports(Imports, [Import|Acc]). + + +update_status(Name, Status) -> + #cdata{status_ets = Ets} = get(cdata), + ets:insert(Ets, {Name, Status}). + + +%% A deprecated object +definitions_loop([{#mc_object_type{name = ObjName, status = deprecated}, + Line}|T], + false) -> + %% May be implemented but the compiler chooses not to. + ?vinfo2("object_type ~w is deprecated => ignored", [ObjName], Line), + update_status(ObjName, deprecated), + definitions_loop(T, false); + +%% A obsolete object +definitions_loop([{#mc_object_type{name = ObjName, status = obsolete}, + Line}|T], + Deprecated) -> + ?vlog2("object_type ~w is obsolete => ignored", [ObjName], Line), + %% No need to implement a obsolete object + update_status(ObjName, obsolete), + ensure_macro_imported('OBJECT-TYPE', Line), + definitions_loop(T, Deprecated); + +%% Defining a table +definitions_loop([{#mc_object_type{name = NameOfTable, + syntax = {{sequence_of, SeqName}, _}, + max_access = Taccess, + kind = Kind, + status = Tstatus, + description = Desc1, + units = Tunits, + reference = Ref, + name_assign = Tindex}, + Tline}, + {#mc_object_type{name = NameOfEntry, + syntax = {{type, SeqName}, TEline}, + max_access = 'not-accessible', + kind = {table_entry, IndexingInfo}, + status = Estatus, + description = Desc2, + units = Eunits, + name_assign = {NameOfTable,[1]}}, + Eline}, + {#mc_sequence{name = SeqName, + fields = FieldList}, + Sline}|ColsEtc], + Deprecated) -> + ?vlog("defloop -> " + "[object_type(sequence_of),object_type(type,[1]),sequence]:" + "~n NameOfTable: ~p" + "~n SeqName: ~p" + "~n Taccess: ~p" + "~n Kind: ~p" + "~n Tstatus: ~p" + "~n Tindex: ~p" + "~n Tunits: ~p" + "~n Tline: ~p" + "~n NameOfEntry: ~p" + "~n TEline: ~p" + "~n IndexingInfo: ~p" + "~n Estatus: ~p" + "~n Eunits: ~p" + "~n Ref: ~p" + "~n Eline: ~p" + "~n FieldList: ~p" + "~n Sline: ~p", + [NameOfTable,SeqName,Taccess,Kind,Tstatus, + Tindex,Tunits,Tline, + NameOfEntry,TEline,IndexingInfo,Estatus,Eunits,Ref,Eline, + FieldList,Sline]), + update_status(NameOfTable, Tstatus), + update_status(NameOfEntry, Estatus), + update_status(SeqName, undefined), + ensure_macro_imported('OBJECT-TYPE', Tline), + test_table(NameOfTable,Taccess,Kind,Tindex,Tline), + {Tfather,Tsubindex} = Tindex, + snmpc_lib:register_oid(Tline,NameOfTable,Tfather,Tsubindex), + Description1 = make_description(Desc1), + TableME = #me{aliasname = NameOfTable, + entrytype = table, + access = 'not-accessible', + description = Description1, + units = Tunits}, + snmpc_lib:register_oid(TEline,NameOfEntry,NameOfTable,[1]), + Description2 = make_description(Desc2), + TableEntryME = #me{aliasname = NameOfEntry, + entrytype = table_entry, + assocList = [{table_entry_with_sequence, SeqName}], + access = 'not-accessible', + description = Description2, + units = Eunits}, + {ColMEs, RestObjs} = + define_cols(ColsEtc, 1, FieldList, NameOfEntry, NameOfTable, []), + TableInfo = snmpc_lib:make_table_info(Eline, NameOfTable, + IndexingInfo, ColMEs), + snmpc_lib:add_cdata(#cdata.mes, + [TableEntryME, + TableME#me{assocList=[{table_info, + TableInfo} | make_reference(Ref)]} | + ColMEs]), + definitions_loop(RestObjs, Deprecated); + +definitions_loop([{#mc_object_type{name = NameOfTable, + syntax = {{sequence_of, SeqName},_}, + max_access = Taccess, + kind = Kind, + status = Tstatus, + description = Desc1, + units = Tunits, + reference = Ref, + name_assign = Tindex}, Tline}, + {#mc_object_type{name = NameOfEntry, + syntax = {{type, SeqName},_}, + max_access = 'not-accessible', + kind = {table_entry,IndexingInfo}, + status = Estatus, + description = Desc2, + units = Eunits, + name_assign = BadOID}, Eline}, + {#mc_sequence{name = SeqName, + fields = FieldList}, Sline}|ColsEtc], + Deprecated) -> + ?vlog("defloop -> " + "[object_type(sequence_of),object_type(type),sequence(fieldList)]:" + "~n NameOfTable: ~p" + "~n SeqName: ~p" + "~n Taccess: ~p" + "~n Kind: ~p" + "~n Tstatus: ~p" + "~n Tindex: ~p" + "~n Tunits: ~p" + "~n Tline: ~p" + "~n NameOfEntry: ~p" + "~n IndexingInfo: ~p" + "~n Estatus: ~p" + "~n BadOID: ~p" + "~n Eunits: ~p" + "~n Ref: ~p" + "~n Eline: ~p" + "~n FieldList: ~p" + "~n Sline: ~p", + [NameOfTable,SeqName,Taccess,Kind,Tstatus, + Tindex,Tunits,Tline, + NameOfEntry,IndexingInfo,Estatus,BadOID,Eunits,Ref,Eline, + FieldList,Sline]), + update_status(NameOfTable, Tstatus), + update_status(NameOfEntry, Estatus), + update_status(SeqName, undefined), + ensure_macro_imported('OBJECT-TYPE', Tline), + snmpc_lib:print_error("Bad TableEntry OID definition (~w)", + [BadOID],Eline), + test_table(NameOfTable,Taccess,Kind,Tindex,Tline), + {Tfather,Tsubindex} = Tindex, + snmpc_lib:register_oid(Tline,NameOfTable,Tfather,Tsubindex), + Description1 = make_description(Desc1), + TableME = #me{aliasname = NameOfTable, + entrytype = table, + access = 'not-accessible', + description = Description1, + units = Tunits}, + Description2 = make_description(Desc2), + TableEntryME = #me{aliasname = NameOfEntry, + entrytype = table_entry, + access = 'not-accessible', + assocList = [{table_entry_with_sequence,SeqName}], + description = Description2, + units = Eunits}, + {ColMEs, RestObjs} = + define_cols(ColsEtc, 1, FieldList, NameOfEntry, NameOfTable, []), + TableInfo = snmpc_lib:make_table_info(Eline, NameOfTable, + IndexingInfo, ColMEs), + snmpc_lib:add_cdata(#cdata.mes, + [TableEntryME, + TableME#me{assocList=[{table_info, + TableInfo} | make_reference(Ref)]} | + ColMEs]), + definitions_loop(RestObjs, Deprecated); + +definitions_loop([{#mc_new_type{name = NewTypeName, + macro = Macro, + syntax = OldType, + display_hint = DisplayHint},Line}|T], + Deprecated) -> + ?vlog2("defloop -> new_type:" + "~n Macro: ~p" + "~n NewTypeName: ~p" + "~n OldType: ~p" + "~n DisplayHint: ~p", + [Macro, NewTypeName, OldType, DisplayHint], Line), + ensure_macro_imported(Macro,Line), + Types = (get(cdata))#cdata.asn1_types, + case lists:keysearch(NewTypeName, #asn1_type.aliasname, Types) of + {value,_} -> + snmpc_lib:print_error("Type ~w already defined.", + [NewTypeName],Line); + false -> + %% NameOfOldType = element(2,OldType), + ASN1 = snmpc_lib:make_ASN1type(OldType), + snmpc_lib:add_cdata(#cdata.asn1_types, + [ASN1#asn1_type{aliasname = NewTypeName, + imported = false, + display_hint = DisplayHint}]) + end, + definitions_loop(T, Deprecated); + +%% Plain variable +definitions_loop([{#mc_object_type{name = NewVarName, + syntax = Type, + max_access = Access, + kind = {variable, DefVal}, + status = Status, + description = Desc1, + units = Units, + name_assign = {Parent,SubIndex}},Line} |T], + Deprecated) -> + ?vlog2("defloop -> object_type (variable):" + "~n NewVarName: ~p" + "~n Type: ~p" + "~n Access: ~p" + "~n DefVal: ~p" + "~n Status: ~p" + "~n Units: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [NewVarName, Type, Access, DefVal, + Status, Units, Parent, SubIndex], Line), + update_status(NewVarName, Status), + snmpc_lib:test_father(Parent, NewVarName, SubIndex, Line), + ASN1type = snmpc_lib:make_ASN1type(Type), + snmpc_lib:register_oid(Line, NewVarName, Parent, SubIndex), + Description1 = make_description(Desc1), + NewME = #me{aliasname = NewVarName, + asn1_type = ASN1type, + entrytype = variable, + access = Access, + description = Description1, + units = Units, + assocList = DefVal}, + NewME2 = snmpc_lib:resolve_defval(NewME), + %% hmm, should this be done in resolve_defval? + VI = snmpc_lib:make_variable_info(NewME2), + snmpc_lib:add_cdata(#cdata.mes, + [NewME2#me{assocList = [{variable_info, VI}]}]), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_module_identity{name = NewVarName, + last_updated = LU, + organization = Org, + contact_info = CI, + description = Desc, + revisions = Revs0, + name_assign = {Parent, SubIndex}}, + Line}|T], + Deprecated) -> + ?vlog2("defloop -> module-identity: " + "~n NewVarName: ~p" + "~n LU: ~p" + "~n Org: ~p" + "~n CI: ~p" + "~n Desc: ~p" + "~n Revs0: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [NewVarName, LU, Org, CI, Desc, Revs0, Parent, SubIndex], Line), + ensure_macro_imported('MODULE-IDENTITY', Line), + snmpc_lib:register_oid(Line, NewVarName, Parent, SubIndex), + Revs = [{R,D}||#mc_revision{revision = R,description = D} <- Revs0], + MI = #module_identity{last_updated = LU, + organization = Org, + contact_info = CI, + description = Desc, + revisions = Revs}, + CDATA = get(cdata), + put(cdata, CDATA#cdata{module_identity = MI}), + snmpc_lib:add_cdata( + #cdata.mes, + [snmpc_lib:makeInternalNode2(false, NewVarName)]), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_internal{name = NewVarName, + macro = Macro, + parent = Parent, + sub_index = SubIndex},Line}|T], + Deprecated) -> + ?vlog2("defloop -> internal:" + "~n NewVarName: ~p" + "~n Macro: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [NewVarName, Macro, Parent, SubIndex], Line), + ensure_macro_imported(Macro, Line), + snmpc_lib:register_oid(Line, NewVarName, Parent, SubIndex), + snmpc_lib:add_cdata( + #cdata.mes, + [snmpc_lib:makeInternalNode2(false, NewVarName)]), + definitions_loop(T, Deprecated); + +%% A trap message +definitions_loop([{#mc_trap{name = TrapName, + enterprise = EnterPrise, + vars = Variables, + description = Desc1, + num = SpecificCode}, Line}|T], + Deprecated) -> + ?vlog2("defloop -> trap:" + "~n TrapName: ~p" + "~n EnterPrise: ~p" + "~n Variables: ~p" + "~n SpecificCode: ~p", + [TrapName, EnterPrise, Variables, SpecificCode], Line), + update_status(TrapName, undefined), + CDATA = get(cdata), + snmpc_lib:check_trap_name(EnterPrise, Line, CDATA#cdata.mes), + Descriptions = make_description(Desc1), + Trap = #trap{trapname = TrapName, + enterpriseoid = EnterPrise, + specificcode = SpecificCode, + %% oidobjects: Store Variables temporary here. + %% This will be replaced later in the + %% get_final_mib function by a call to + %% the update_trap_objects function. + oidobjects = Variables, + description = Descriptions}, + lists:foreach(fun(Trap2) -> snmpc_lib:check_trap(Trap2, Trap, Line) end, + CDATA#cdata.traps), + snmpc_lib:add_cdata(#cdata.traps, [Trap]), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_object_type{name = NameOfEntry, + syntax = Type, + max_access = Eaccess, + kind = {table_entry, Index}, + status = Estatus, + name_assign = SubIndex},Eline}|T], + Deprecated) -> + ?vlog("defloop -> object_type (table_entry):" + "~n NameOfEntry: ~p" + "~n Type: ~p" + "~n Eaccess: ~p" + "~n Index: ~p" + "~n Estatus: ~p" + "~n SubIndex: ~p" + "~n SubIndex: ~p" + "~n Eline: ~p", + [NameOfEntry, Type, Eaccess, Index, Estatus, SubIndex, Eline]), + update_status(NameOfEntry, Estatus), + snmpc_lib:print_error("Misplaced TableEntry definition (~w)", + [NameOfEntry], Eline), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_notification{name = TrapName, + status = deprecated}, Line}|T], + false) -> + ?vinfo2("defloop -> notification ~w is deprecated => ignored", + [TrapName], Line), + update_status(TrapName, deprecated), + ensure_macro_imported('NOTIFICATION-TYPE', Line), + definitions_loop(T, false); + +definitions_loop([{#mc_notification{name = TrapName, + status = obsolete}, Line}|T], + Deprecated) -> + ?vlog2("defloop -> notification ~w is obsolete => ignored", + [TrapName], Line), + update_status(TrapName, obsolete), + ensure_macro_imported('NOTIFICATION-TYPE', Line), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_notification{name = TrapName, + vars = Variables, + status = Status, + description = Desc, + name_assign = {Parent, SubIndex}},Line}|T], + Deprecated) -> + ?vlog2("defloop -> notification:" + "~n TrapName: ~p" + "~n Variables: ~p" + "~n Status: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [TrapName, Variables, Status, Parent, SubIndex], Line), + update_status(TrapName, Status), + ensure_macro_imported('NOTIFICATION-TYPE', Line), + CDATA = get(cdata), + snmpc_lib:register_oid(Line, TrapName, Parent, SubIndex), + Descriptions = make_description(Desc), + Notif = #notification{trapname = TrapName, + description = Descriptions, + %% oidobjects: Store Variables temporary here. + %% This will be replaced later in the + %% get_final_mib function by a call to + %% the update_trap_objects function. + oidobjects = Variables}, + snmpc_lib:check_notification(Notif, Line, CDATA#cdata.traps), + snmpc_lib:add_cdata(#cdata.traps, [Notif]), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_module_compliance{name = Name},Line}|T], Deprecated) -> + ?vlog2("defloop -> module_compliance:" + "~n Name: ~p", [Name], Line), + ensure_macro_imported('MODULE-COMPLIANCE', Line), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_object_group{name = Name, + objects = GroupObjects, + status = Status, + description = Desc, + reference = Ref, + name_assign = {Parent,SubIndex}}, Line}|T], + Deprecated) -> + ?vlog2("defloop -> object_group ~p:" + "~n GroupObjects: ~p" + "~n Status: ~p" + "~n Desc: ~p" + "~n Ref: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [Name, GroupObjects, Status, Desc, Ref, Parent, SubIndex], Line), + ensure_macro_imported('OBJECT-GROUP', Line), + GroupBool = get_group_check(get(options)), + case GroupBool of + true -> + snmpc_lib:add_cdata(#cdata.objectgroups, + [{Name,GroupObjects,Line}]), + %% Check that the group members has been defined + %% and that they have the correct status + snmpc_lib:check_object_group(Name, GroupObjects, + Line, Status); + _ -> + ok + end, + + update_status(Name, Status), + snmpc_lib:test_father(Parent, Name, SubIndex, Line), + snmpc_lib:register_oid(Line, Name, Parent, SubIndex), + Description = make_description(Desc), + NewME = #me{aliasname = Name, + entrytype = group, + access = 'not-accessible', + description = Description, + assocList = [{kind, object}, + {objects, GroupObjects}]}, + snmpc_lib:add_cdata(#cdata.mes, [NewME]), + + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_notification_group{name = Name, + objects = GroupObjects, + status = Status, + description = Desc, + reference = Ref, + name_assign = {Parent,SubIndex}}, + Line} + |T], Deprecated) -> + ?vlog2("defloop -> notification_group ~p:" + "~n GroupObjects: ~p" + "~n Status: ~p" + "~n Desc: ~p" + "~n Ref: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [Name, GroupObjects, Status, Desc, Ref, Parent, SubIndex], Line), + ensure_macro_imported('NOTIFICATION-GROUP', Line), + GroupBool = get_group_check(get(options)), + case GroupBool of + true -> + snmpc_lib:add_cdata(#cdata.notificationgroups, + [{Name,GroupObjects,Line}]), + + %% Check that the group members has been defined + %% and that they have the correct status + snmpc_lib:check_notification_group(Name, GroupObjects, + Line, Status); + _ -> + ok + end, + + update_status(Name, Status), + snmpc_lib:test_father(Parent, Name, SubIndex, Line), + snmpc_lib:register_oid(Line, Name, Parent, SubIndex), + Description = make_description(Desc), + NewME = #me{aliasname = Name, + entrytype = group, + access = 'not-accessible', + description = Description, + assocList = [{kind, notification}, + {objects, GroupObjects}]}, + snmpc_lib:add_cdata(#cdata.mes, [NewME]), + + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_object_type{name = NameOfTable, + syntax = {{sequence_of, SeqName},_}, + status = Tstatus},Tline}, + Entry, Seq|T], + Deprecated) -> + ?vlog("defloop -> object_type (sequence_of): " + "~n NameOfTable: ~p" + "~n SeqName: ~p" + "~n Tline: ~p" + "~n Entry: ~p" + "~n Seq: ~p", + [NameOfTable, SeqName, Tline, Entry, Seq]), + update_status(NameOfTable, Tstatus), + case Entry of + {#mc_object_type{syntax = {{type, SeqName},_line}, + max_access = 'not-accessible', + kind = {table_entry, _IndexingInfo}, + name_assign = {_NameOfTable,[1]}}, _Eline} -> + case Seq of + {#mc_sequence{name = SeqName}, Sline} -> + snmpc_lib:error("Internal error. Correct incorrect " + "table (~p,~w).",[SeqName,Sline], + Tline); + _ -> + ?vinfo("defloop -> Invalid sequence: ~p", [Seq]), + snmpc_lib:print_error( + "Invalid SEQUENCE OF '~p'.", + [safe_elem(1,safe_elem(2,Seq))],Tline) + end; + Else -> + ?vinfo("defloop -> Invalid table entry: " + "~n ~p", [Else]), + snmpc_lib:print_error( + "Invalid TableEntry '~p' (check STATUS, Sequence name, Oid)", + [safe_elem(1,safe_elem(2,Entry))],Tline) + end, + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_object_type{name = NameOfTable, + syntax = {{sequence_of, SeqName},_}, + status = Tstatus},Tline}|T], + Deprecated) -> + ?vlog("defloop -> object_type (sequence_of):" + "~n object_type: ~p" + "~n sequence_of: ~p" + "~n Tline: ~p", [NameOfTable, SeqName, Tline]), + update_status(NameOfTable, Tstatus), + snmpc_lib:print_error("Invalid statements following table ~p.", + [NameOfTable],Tline), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_sequence{name = SeqName, + fields = _FieldList},Line}|T], + Deprecated) -> + ?vwarning2("Unexpected SEQUENCE ~w => ignoring", [SeqName], Line), + definitions_loop(T, Deprecated); + +definitions_loop([{Obj,Line}|T], Deprecated) -> + ?vinfo2("defloop -> unknown error" + "~n Obj: ~p", [Obj], Line), + snmpc_lib:print_error("Unknown Error in MIB. " + "Can't describe the error better than this: ~999p ignored." + " Please send a trouble report to [email protected].", + [Obj], Line), + definitions_loop(T, Deprecated); + +definitions_loop([], _Deprecated) -> + ?vlog("defloop -> done", []), + ok. + +safe_elem(N,T) -> + case catch(element(N,T)) of + {'EXIT',_} -> + "no more information available"; + X -> X + end. + +%% A correct column +define_cols([{#mc_object_type{name = NameOfCol, + syntax = Type1, + max_access = Access, + kind = {variable,Defval}, + status = Status, + description = Desc, + units = Units, + name_assign = {NameOfEntry,[SubIndex]}}, + Oline}|Rest], + SubIndex, + [{NameOfCol,Type2}|Fields], NameOfEntry, TableName, ColMEs) -> + ?vlog("defcols -> object_type (variable):" + "~n NameOfCol: ~p" + "~n Type1: ~p" + "~n Access: ~p" + "~n Defval: ~p" + "~n Status ~p" + "~n Units ~p" + "~n NameOfEntry ~p" + "~n Oline: ~p", + [NameOfCol, Type1, Access, Defval, Status, Units, + NameOfEntry, Oline]), + update_status(NameOfCol, Status), + Deprecated = get_deprecated(get(options)), + ASN1type = snmpc_lib:make_ASN1type(Type1), + case (snmpc_lib:make_ASN1type(Type2))#asn1_type.bertype of + T2 when T2 == ASN1type#asn1_type.bertype -> ok; + _Else -> + snmpc_lib:error( + "Types for ~p differs from the SEQUENCE definition. ", + [NameOfCol],Oline) + end, + NewAccess = % a simple way to get the obsolete behaviour + if + Status =:= obsolete -> + %% Be quiet and don't implement + 'not-accessible'; + (Status =:= deprecated) andalso (Deprecated =:= false) -> + %% The compiler chooses not to implement the column. + ?vinfo2("object_type ~w is deprecated => ignored", + [NameOfCol], Oline), + 'not-accessible'; + true -> Access + end, + snmpc_lib:register_oid(Oline,NameOfCol,NameOfEntry,[SubIndex]), + Description = make_description(Desc), + ColumnME = snmpc_lib:resolve_defval( + #me{oid = SubIndex, + aliasname = NameOfCol, + asn1_type = ASN1type, + entrytype = table_column, + access = NewAccess, + description = Description, + units = Units, %% Propably not usefull + assocList = [{table_name,TableName} | Defval]}), + define_cols(Rest,SubIndex+1,Fields,NameOfEntry,TableName, + [ColumnME|ColMEs]); + +%% A "hole" (non-consecutive columns) in the table. +%% Implemented as a not-accessible column so Col always is index in +%% row tuple. +define_cols([{#mc_object_type{name = NameOfCol, + syntax = Type1, + max_access = Access, + kind = Kind, + status = Status, + name_assign = {NameOfEntry,[SubIndex]}}, + Oline}|Rest], + ExpectedSubIndex, Fields, NameOfEntry, TableName, ColMEs) + when SubIndex > ExpectedSubIndex -> + ?vlog("defcols -> object_type (non consecutive cols):" + "~n NameOfCol: ~p" + "~n Type1: ~p" + "~n Access: ~p" + "~n Status ~p" + "~n NameOfEntry ~p" + "~n Oline: ~p", + [NameOfCol, Type1, Access, Status, NameOfEntry, Oline]), + update_status(NameOfCol, Status), + Int = {{type, 'INTEGER'},Oline}, + GeneratedColumn = + %% be sure to use an invalid column name here! + {#mc_object_type{name = '$no_name$', + syntax = Int, + max_access = 'not-accessible', + kind = {variable, [{defval,0}]}, + status = current, + description = undefined, + name_assign = {NameOfEntry, [ExpectedSubIndex]}}, + Oline}, + define_cols([GeneratedColumn, + {#mc_object_type{name = NameOfCol, + syntax = Type1, + max_access = Access, + kind = Kind, + status = Status, + description = undefined, + name_assign = {NameOfEntry,[SubIndex]}}, + Oline}|Rest], ExpectedSubIndex, + [{'$no_name$', Int}|Fields], NameOfEntry, TableName,ColMEs) ; + +%% Ok. done. All fields are eaten. +define_cols(Rest, _SubIndex, [], _NameOfEntry, _TableName, ColMEs) -> + {ColMEs, Rest}; + + +%% Error Handling + +%% The name of the field and object is the same +define_cols([{#mc_object_type{name = NameOfCol, + kind = Kind, + name_assign = SubIndex}, Oline}|Rest], + SubIndex2, [{NameOfCol, _Type2}|Fields], + NameOfEntry, TableName, ColMEs) -> + ?vlog("defcols -> object_type (name of field and object is the same):" + "~n NameOfCol: ~p" + "~n Kind: ~p" + "~n SubIndex: ~p" + "~n Oline: ~p" + "~n SubIndex2: ~p" + "~n NameOfEntry ~p" + "~n TableName ~p", + [NameOfCol,Kind,SubIndex,Oline,SubIndex2,NameOfEntry,TableName]), + SIok = case SubIndex of + {Parent,[_SI]} when Parent =/= NameOfEntry -> + snmpc_lib:print_error( + "Invalid parent ~p for table column ~p (should be ~p).", + [Parent,NameOfCol,NameOfEntry],Oline), + error; + {NameOfEntry,[SubIndex2]} -> + ok; + {NameOfEntry,[SI]} -> + snmpc_lib:print_error( + "Invalid column number ~p for column ~p.", + [SI, NameOfCol], Oline), + error; + _Q -> + snmpc_lib:print_error( + "Invalid parent for column ~p.",[NameOfCol],Oline), + error + end, + Kok = case Kind of + {variable,_} -> + ok; + _Q2 -> + snmpc_lib:print_error( + "Expected a table column.",[],Oline), + error + end, + case {SIok, Kok} of + {ok, ok} -> + snmpc_lib:print_error("Invalid table column definition for" + " ~p.",[NameOfCol],Oline); + _Q4 -> + done % already reported + end, + define_cols(Rest,SubIndex2+1,Fields,NameOfEntry,TableName,ColMEs); + +%% It's an object-type but everything else is wrong +define_cols([{#mc_object_type{name = NameOfCol},Oline}|Rest],SubIndex2,Fields, + NameOfEntry,TableName,ColMEs) -> + snmpc_lib:print_error( + "Number of columns differs from SEQUENCE definition (object:~p).", + [NameOfCol],Oline), + define_cols(Rest,SubIndex2+1,Fields,NameOfEntry,TableName,ColMEs); + +define_cols([{Obj,Line}|Tl], _SubIndex,_,_,_,ColMEs) -> + snmpc_lib:print_error("Corrupt table definition.",[],Line), + {ColMEs,[{Obj,Line}|Tl]}; +define_cols(Rest, _SubIndex,_,_,_,ColMEs) -> + snmpc_lib:print_error("Corrupt table definition.",[]), + {ColMEs,Rest}. + +ensure_macro_imported(dummy, _Line) -> ok; +ensure_macro_imported(Macro, Line) -> + Macros = (get(cdata))#cdata.imported_macros, + case lists:member(Macro, Macros) of + true -> ok; + false -> + snmpc_lib:print_error("Macro ~p not imported.", [Macro], + Line) + end. + +test_table(NameOfTable, Taccess, Kind, _Tindex, Tline) -> + if + Taccess =/= 'not-accessible' -> + snmpc_lib:print_error( + "Table ~w must have STATUS not-accessible", + [NameOfTable],Tline), + error; + Kind =/= {variable,[]} -> + snmpc_lib:print_error( + "Bad table definition (~w).", + [NameOfTable],Tline), + error; + true -> + ok + end. + +save(Filename, MibName, Options) -> + R = filename:rootname(Filename), + File1 = filename:basename(R), + File3 = snmpc_misc:to_upper(File1), + case snmpc_misc:to_upper(atom_to_list(MibName)) of + File3 -> + {value, OutDirr} = snmpc_misc:assq(outdir, Options), + OutDir = snmpc_misc:ensure_trailing_dir_delimiter(OutDirr), + File2 = (OutDir ++ File1) ++ ".bin", + {ok, MIB} = snmpc_lib:get_final_mib(File1, Options), + case get(errors) of + undefined -> + case file:write_file(File2, term_to_binary(MIB)) of + ok -> + {ok, File2}; + _Err -> + snmpc_lib:error( + "Couldn't write file \"~s\".",[File2]) + end; + E -> + ?vlog("save failed: " + "~n ~p", [E]), + {'EXIT',error} + end; + MibNameL -> + snmpc_lib:error("Mibname (~s) differs from filename (~s).", + [MibNameL, File1]) + end. + +%% parse takes a text file as a input and the output is a list of tokens. +%% Input: FileName (file of mibs) +%% Output: {ok, Mib} where MIB is a tuple of Tokens. +%% {error, {LineNbr, Mod, Msg} an error on line number LineNb. + + +parse(FileName) -> + case snmpc_tok:start_link(reserved_words(), + [{file, FileName ++ ".mib"}, + {forget_stringdata, true}]) of + {error,ReasonStr} -> + snmpc_lib:error(lists:flatten(ReasonStr),[]); + {ok, TokPid} -> + Toks = snmpc_tok:get_all_tokens(TokPid), + set_version(Toks), + %% io:format("parse -> lexical analysis: ~n~p~n", [Toks]), + %% t("parse -> lexical analysis: ~n~p", [Toks]), + CDataArg = + case lists:keysearch(module, 1, get(options)) of + {value, {module, M}} -> {module, M}; + _ -> {file, FileName ++ ".funcs"} + end, + put(cdata,snmpc_lib:make_cdata(CDataArg)), + snmpc_tok:stop(TokPid), + Res = if + is_list(Toks) -> + snmpc_mib_gram:parse(Toks); + true -> + Toks + end, + %% t("parse -> parsed: ~n~p", [Res]), + case Res of + {ok, PData} -> + {ok, PData}; + {error, {LineNbr, Mod, Msg}} -> + case catch format_yecc_error(LineNbr, Msg) of + {Line, Format, Data} -> + snmpc_lib:error(Format,Data,Line); + _Q -> % sorry, have to use ugly yecc printouts + Str = apply(Mod, format_error, [Msg]), + snmpc_lib:error("~s",[Str],LineNbr) + end + end + end. + +set_version(Toks) when is_list(Toks) -> +%% MODULE-IDENTITY _must_ be invoked in SNMPv2 according to RFC1908 + case lists:keymember('MODULE-IDENTITY',1,Toks) of + true -> + put(snmp_version,2); + false -> + put(snmp_version,1) + end; +set_version(_) -> + put(snmp_version,1). + + +%% YeccGeneratedFile:format_error/1 is bad. +format_yecc_error(Line, [ErrMsg, [${,Category, $,, _LineStr,$,, Value, $}]]) -> + {Line, "~s \"~s\" (~s).", [ErrMsg, Value, Category]}. + +%% The same as the (quoted) Terminals in the snmpc_mib_gram.yrl +reserved_words() -> + [ + 'ACCESS', + 'BEGIN', + 'BIT', + 'CONTACT-INFO', + 'Counter', + 'DEFINITIONS', + 'DEFVAL', + 'DESCRIPTION', + 'DISPLAY-HINT', + 'END', + 'ENTERPRISE', + 'FROM', + 'Gauge', + 'IDENTIFIER', + 'IDENTIFIER', + 'IMPORTS', + 'INDEX', + 'INTEGER', + 'IpAddress', + 'LAST-UPDATED', + 'NetworkAddress', + 'OBJECT', + 'OBJECT', + 'OBJECT-TYPE', + 'OCTET', + 'OF', + 'Opaque', + 'REFERENCE', + 'SEQUENCE', + 'SIZE', + 'STATUS', + 'STRING', + 'SYNTAX', + 'TRAP-TYPE', + 'TimeTicks', + 'VARIABLES', + + %% v2 + 'LAST-UPDATED', + 'ORGANIZATION', + 'CONTACT-INFO', + 'MODULE-IDENTITY', + 'NOTIFICATION-TYPE', + 'MODULE-COMPLIANCE', + 'OBJECT-GROUP', + 'NOTIFICATION-GROUP', + 'REVISION', + 'OBJECT-IDENTITY', + 'MAX-ACCESS', + 'UNITS', + 'AUGMENTS', + 'IMPLIED', + 'OBJECTS', + 'TEXTUAL-CONVENTION', + 'OBJECT-GROUP', + 'NOTIFICATION-GROUP', + 'NOTIFICATIONS', + 'MODULE-COMPLIANCE', + 'MODULE', + 'MANDATORY-GROUPS', + 'GROUP', + 'WRITE-SYNTAX', + 'MIN-ACCESS', + 'BITS' + ] +. diff --git a/lib/snmp/src/compile/snmpc.hrl b/lib/snmp/src/compile/snmpc.hrl new file mode 100644 index 0000000000..eb896cde6b --- /dev/null +++ b/lib/snmp/src/compile/snmpc.hrl @@ -0,0 +1,153 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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% +%% + +%% Parser output +-record(pdata, {mib_version, + mib_name, + imports, + defs}). + +%% compilation information record +-record(cdata, {module_identity, + asn1_types = [], + mes = [], + traps = [], + mibfuncs, + sequences = [], + imported_macros = [], + objectgroups = [], + notificationgroups = [], + imports, + oid_ets, + status_ets}). + + +-record(mc_module_identity, + {name, + last_updated, + organization, + contact_info, + description, + revisions = [], %% A list of mc_revision + name_assign + } + ). + +-record(mc_revision, + {revision, + description + } + ). + +-record(mc_object_type, + {name, + syntax, + units, + max_access, + status, + description, + reference, + kind, + name_assign + } + ). + + +-record(mc_new_type, + {name, + macro, + status, + description, + reference, + display_hint, + syntax + } + ). + + +-record(mc_trap, + {name, + enterprise, + vars, + description, + reference, + num + } + ). + + +-record(mc_notification, + {name, + vars, + status, + description, + reference, + name_assign + } + ). + + +-record(mc_module_compliance, + {name, + status, + description, + reference, + module, + name_assign + } + ). + + +-record(mc_object_group, + {name, + objects, + status, + description, + reference, + name_assign + } + ). + + +-record(mc_notification_group, + {name, + objects, + status, + description, + reference, + name_assign + } + ). + + +-record(mc_sequence, + {name, + fields + } + ). + + +-record(mc_internal, + {name, + macro, + parent, + sub_index + } + ). + diff --git a/lib/snmp/src/compile/snmpc_lib.erl b/lib/snmp/src/compile/snmpc_lib.erl new file mode 100644 index 0000000000..b7e84e7d6b --- /dev/null +++ b/lib/snmp/src/compile/snmpc_lib.erl @@ -0,0 +1,1819 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-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(snmpc_lib). + +%% API +-export([test_father/4, make_ASN1type/1, import/1, makeInternalNode2/2, + is_consistent/1, resolve_defval/1, make_variable_info/1, + check_trap_name/3, make_table_info/4, get_final_mib/2, set_dir/2, + look_at/1, add_cdata/2, + check_object_group/4, check_notification_group/4, + check_notification/3, + register_oid/4, + error/2, error/3, + %% warning/2, warning/3, + print_error/2, print_error/3, + make_cdata/1, + key1search/2, key1search/3]). + +%% internal exports +-export([check_of/1, check_trap/2, check_trap/3, get_elem/2]). + +%% debug exports +-export([vvalidate/1, vprint/6]). + + +-include("snmp_types.hrl"). +-include("snmpc.hrl"). +-include("snmpc_lib.hrl"). + + +%%---------------------------------------------------------------------------- +%% Some basic types +%%---------------------------------------------------------------------------- + +-type severity() :: 'silence' | 'warning' | 'info' | 'log' | 'debug' | 'trace'. + + +test_father(FatherName, NewVarName, SubIndex, Line) -> + CDATA = get(cdata), + case lists:keysearch(FatherName, #me.aliasname, CDATA#cdata.mes) of + {value, #me{entrytype = table, aliasname = TableName}} -> + print_error("Variable '~w' (sub-index '~w') cannot " + "be defined under table '~w'.", + [NewVarName, SubIndex, TableName],Line); + {value, #me{entrytype = table_entry, aliasname = TableName}} -> + print_error("Variable '~w' (sub-index '~w') cannot " + "be defined under table entry '~w'.", + [NewVarName, SubIndex, TableName], Line); + + _X -> %% internal or variable + case lists:last(SubIndex) of + 0 -> + print_error("'~w'. A zero-valued final subidentifier is reserved for future use. (RFC1902, 7.10)",[NewVarName],Line); + _ -> ok + end + end. + +make_ASN1type({{type,Type},Line}) -> + case lookup_vartype(Type) of + {value, ASN1type} -> + ASN1type; + false -> + print_error("Undefined type '~w'",[Type],Line), + guess_integer_type() + end; +make_ASN1type({{type_with_size,Type,{range,Lo,Hi}},Line}) -> + case lookup_vartype(Type) of + {value,ASN1type} -> + case allow_size_rfc1902(BaseType = ASN1type#asn1_type.bertype) of + true -> + ok; + false -> + print_error( + "Size refinement is not allowed for subclass from ~w.", + [BaseType],Line) + end, + ASN1type#asn1_type{lo = Lo, hi = Hi}; + false -> + print_error("Undefined type '~w'",[Type],Line), + guess_string_type() + end; +make_ASN1type({{integer_with_enum,Type,Enums},Line}) -> + case lookup_vartype(Type) of + {value,ASN1type} -> ASN1type#asn1_type{assocList = [{enums, Enums}]}; + false -> + print_error("Undefined type '~w'",[Type],Line), + guess_integer_type() + end; +make_ASN1type({{bits,Kibbles},Line}) -> + case get(snmp_version) of + 2 -> + {value,Bits} = lookup_vartype('BITS'), + Kibbles2 = test_kibbles(Kibbles, Line), + Bits#asn1_type{assocList = [{kibbles, Kibbles2}]}; + _ -> + guess_integer_type() + end; +make_ASN1type({{sequence_of, _Type},Line}) -> + print_error("Use of SEQUENCE OF in non-table context.",[],Line), + guess_integer_type(). + +test_kibbles([], Line) -> + print_error("No kibbles found.",[],Line), + []; +test_kibbles(Kibbles,Line) -> + test_kibbles2(R = lists:keysort(2,Kibbles),0,Line), + R. + +test_kibbles2([],_,_) -> + ok; +test_kibbles2([{_KibbleName,BitNo}|Ks],BitNo,Line) -> + test_kibbles2(Ks,BitNo+1,Line); +test_kibbles2([{_KibbleName,BitNo}|_Ks],ExpectBitNo,Line) -> + print_error("Expected kibble no ~p but got ~p.",[ExpectBitNo,BitNo],Line). + + +allow_size_rfc1902('INTEGER') -> true; +allow_size_rfc1902('Integer32') -> true; +allow_size_rfc1902('Unsigned32') -> true; +allow_size_rfc1902('OCTET STRING') -> true; +allow_size_rfc1902('Gauge32') -> true; +allow_size_rfc1902(_) -> false. + +guess_integer_type() -> + {value,ASN1int} = lookup_vartype('INTEGER'), + ASN1int. + +guess_string_type() -> + {value,ASN1str} = lookup_vartype('OCTET STRING'), + ASN1str. + +lookup_vartype(Type) -> + CDATA = get(cdata), + lists:keysearch(Type, #asn1_type.aliasname, CDATA#cdata.asn1_types). + + + + +%%-------------------------------------------------- +%% Reads the oid-function files. +%% Out: A list of {oid, entry}. +%% oid is here either a Oid with integers, or +%% with symbolic names. +%% entry is {M,F,A}. +%%-------------------------------------------------- +read_funcs(FileName) -> + case snmpc_misc:read_noexit(FileName, fun check_of/1) of + {ok, Res} -> Res; + {error, LineNo, Reason} -> + print_error("~p: ~w: Syntax error: ~p", + [FileName, LineNo, Reason]), + []; + {error, open_file} -> [] + end. + +check_of({module, M}) when is_atom(M) -> + {ok, {module, M}}; +check_of({Oid, {M, F, A}}) when is_atom(M) andalso is_atom(F) andalso is_list(A) -> + {ok, {Oid, {M, F, A}}}; +check_of({_Oid, {M, F, A}}) -> + {invalid_argument, {M, F, A}}; +check_of(X) -> + {invalid_func, X}. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for IMPORT implementation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +import(ImportList) -> + %% Register some well known top-nodes (directly under the root) + WellKnownNodes = [ makeInternalNode(ccitt, [0]), + makeInternalNode(iso, [1]), + makeInternalNode('joint-iso-ccitt', [2]) ], + lists:foreach( + fun(#me{aliasname = AliasName, oid = Oid}) -> + register_oid(undef, AliasName, root, Oid) + end, + WellKnownNodes), + lists:foreach(fun import_mib/1, ImportList). + + +%%---------------------------------------------------------------------- +%% Returns: <nothing> only side effect stuff. +%%---------------------------------------------------------------------- +import_mib({{'SNMPv2-SMI', ImportsFromMib},Line}) -> + Nodes = [makeInternalNode(internet, [1,3,6,1]), + makeInternalNode(directory, [1,3,6,1,1]), + makeInternalNode(mgmt, [1,3,6,1,2]), + makeInternalNode('mib-2', [1,3,6,1,2,1]), + makeInternalNode(transmission, [1,3,6,1,2,1,10]), + makeInternalNode(experimental, [1,3,6,1,3]), + makeInternalNode(private, [1,3,6,1,4]), + makeInternalNode(enterprises, [1,3,6,1,4,1]), + makeInternalNode(zeroDotZero, [0,0]), + makeInternalNode(security, [1,3,6,1,5]), + makeInternalNode(snmpV2, [1,3,6,1,6]), + makeInternalNode(snmpDomains, [1,3,6,1,6,1]), + makeInternalNode(snmpProxys, [1,3,6,1,6,2]), + makeInternalNode(snmpModules, [1,3,6,1,6,3])], + Types = [#asn1_type{bertype = 'Integer32', + aliasname = 'Integer32', + lo = -2147483648, hi = 2147483647}, + #asn1_type{bertype = 'IpAddress', + aliasname = 'IpAddress', + lo = 4, hi = 4}, + #asn1_type{bertype = 'Counter32', + aliasname = 'Counter32', + lo = 0, hi = 4294967295}, + #asn1_type{bertype = 'Gauge32', + aliasname = 'Gauge32', + lo = 0, hi = 4294967295}, + #asn1_type{bertype = 'Unsigned32', + aliasname = 'Unsigned32', + lo = 0, hi = 4294967295}, + #asn1_type{bertype = 'TimeTicks', + aliasname = 'TimeTicks', + lo = 0, hi=4294967295}, + #asn1_type{bertype = 'Opaque', + aliasname = 'Opaque'}, + #asn1_type{bertype = 'Counter64', + aliasname = 'Counter64', + lo = 0, hi = 18446744073709551615}], + Macros = ['MODULE-IDENTITY','OBJECT-IDENTITY','OBJECT-TYPE', + 'NOTIFICATION-TYPE'], + import_built_in_loop(ImportsFromMib,Nodes,Types,Macros,'SNMPv2-SMI',Line); +import_mib({{'RFC-1215', ImportsFromMib},Line}) -> + Macros = ['TRAP-TYPE'], + import_built_in_loop(ImportsFromMib, [],[],Macros,'RFC-1215', Line); +import_mib({{'RFC-1212', ImportsFromMib},Line}) -> + Macros = ['OBJECT-TYPE'], + import_built_in_loop(ImportsFromMib, [],[],Macros,'RFC-1212', Line); +import_mib({{'SNMPv2-TC', ImportsFromMib},Line}) -> + Nodes = [], + Types = [#asn1_type{aliasname = 'DisplayString', + bertype = 'OCTET STRING', + lo = 0, hi = 255}, + #asn1_type{aliasname = 'PhysAddress', + bertype = 'OCTET STRING'}, + #asn1_type{aliasname = 'MacAddress', + bertype = 'OCTET STRING', + lo = 6, hi = 6}, + #asn1_type{aliasname = 'TruthValue', + bertype = 'INTEGER', + assocList = [{enums,[{false,2},{true,1}]}]}, + #asn1_type{aliasname = 'TestAndIncr', + bertype = 'INTEGER', + lo = 0, hi = 2147483647}, + #asn1_type{aliasname = 'AutonomousType', + bertype = 'OBJECT IDENTIFIER'}, + #asn1_type{aliasname = 'InstancePointer', + bertype = 'OBJECT IDENTIFIER'}, + #asn1_type{aliasname = 'VariablePointer', + bertype = 'OBJECT IDENTIFIER'}, + #asn1_type{aliasname = 'RowPointer', + bertype = 'OBJECT IDENTIFIER'}, + #asn1_type{aliasname = 'RowStatus', + bertype = 'INTEGER', + assocList = [{enums,[{destroy, 6}, + {createAndWait, 5}, + {createAndGo, 4}, + {notReady, 3}, + {notInService, 2}, + {active, 1}]}]}, + #asn1_type{aliasname = 'TimeStamp', + bertype = 'TimeTicks'}, + #asn1_type{aliasname = 'TimeInterval', + bertype = 'INTEGER', + lo = 0, hi = 2147483647}, + #asn1_type{aliasname = 'DateAndTime', + bertype = 'OCTET STRING', + lo = 8, hi = 11}, %% Actually 8 | 11 + #asn1_type{aliasname = 'StorageType', + bertype = 'INTEGER', + assocList = [{enums,[{readOnly, 5}, + {permanent, 4}, + {nonVolatile, 3}, + {volatile, 2}, + {other, 1}]}]}, + #asn1_type{aliasname = 'TDomain', + bertype = 'OBJECT IDENTIFIER'}, + #asn1_type{aliasname = 'TAddress', + bertype = 'OCTET STRING', + lo = 1, hi = 255} + ], + Macros = ['TEXTUAL-CONVENTION'], + import_built_in_loop(ImportsFromMib,Nodes,Types,Macros,'SNMPv2-TC',Line); +import_mib({{'SNMPv2-CONF', ImportsFromMib},Line}) -> + Macros = ['OBJECT-GROUP','NOTIFICATION-GROUP','MODULE-COMPLIANCE'], + import_built_in_loop(ImportsFromMib,[],[],Macros,'SNMPv2-CONF',Line); +import_mib({{'RFC1155-SMI', ImportsFromMib},Line}) -> + Nodes = [makeInternalNode(internet, [1,3,6,1]), + makeInternalNode(directory, [1,3,6,1,1]), + makeInternalNode(mgmt, [1,3,6,1,2]), + makeInternalNode(experimental, [1,3,6,1,3]), + makeInternalNode(private, [1,3,6,1,4]), + makeInternalNode(enterprises, [1,3,6,1,4,1])], + Types = [#asn1_type{bertype = 'NetworkAddress', + aliasname = 'NetworkAddress', lo = 4, hi = 4}, + #asn1_type{bertype='Counter',aliasname='Counter', + lo=0,hi=4294967295}, + #asn1_type{bertype='Gauge',aliasname='Gauge', + lo = 0, hi = 4294967295}, + #asn1_type{bertype='IpAddress',aliasname='IpAddress',lo=4,hi=4}, + #asn1_type{bertype = 'TimeTicks', aliasname = 'TimeTicks', + lo = 0, hi=4294967295}, + #asn1_type{bertype = 'Opaque', aliasname = 'Opaque'}], + Macros = ['OBJECT-TYPE'], + import_built_in_loop(ImportsFromMib,Nodes,Types,Macros,'RFC1155-SMI',Line); +import_mib({{MibName, ImportsFromMib},Line}) -> + import_from_file({{MibName, ImportsFromMib},Line}). + +import_built_in_loop(Objs, Nodes, Types, Macros, MibName, Line) -> + lists:foreach(fun (Obj) -> + import_built_in(Obj,Nodes,Types,Macros,MibName,Line) + end, Objs). + +import_from_file({{_MibName, []}, _Line}) -> + done; +import_from_file({{MibName, ImportsFromMib},Line}) -> + Filename = atom_to_list(MibName) ++ ".bin", + {value, Path} = snmpc_misc:assq(i, get(options)), + {value, LibPath} = snmpc_misc:assq(il,get(options)), + LibPath2 = include_lib(LibPath), + Path2 = Path++LibPath2++[filename:join(code:priv_dir(snmp),"mibs"), + "./"], + ImportedMib = case read_mib(Line,Filename, Path2) of + error -> + error("Could not import ~p from mib ~s. " + "File not found. " + "Check that the MIB to be IMPORTED " + "is compiled and present in the import path.", + [ImportsFromMib, Filename], Line); + Mib -> + Mib + end, + lists:foreach(fun (ImpObj) -> import(ImpObj,ImportedMib) end, + ImportsFromMib). + +import_built_in({_tag,Obj}, Nodes, Types, Macros, MibName, Line) -> + case lookup(Obj, Nodes) of + {value, ME} -> + register_oid(undef, ME#me.aliasname, root, ME#me.oid), + add_cdata(#cdata.mes, [ME#me{imported = true, oid = undefined}]); + false -> + case lists:keysearch(Obj, #asn1_type.aliasname, Types) of + {value, ASN1Type} -> + add_cdata(#cdata.asn1_types, + [ASN1Type#asn1_type{imported=true}]); + false -> + case lists:member(Obj, Macros) of + true -> + add_cdata(#cdata.imported_macros,[Obj]); + false -> + print_error("Cannot find '~w' in mib '~s'.", + [Obj, MibName], Line) + end + end + end. + +include_lib([]) -> []; +include_lib([Dir|Dirs]) -> + [Appl|Path] = filename:split(Dir), + case code:lib_dir(list_to_atom(Appl)) of + {error, _Reason} -> + include_lib(Dirs); + DirPath -> + [filename:join(DirPath,filename:join(Path))|include_lib(Dirs)] + end. + + +%%---------------------------------------------------------------------- +%% Returns: #mib +%%---------------------------------------------------------------------- +read_mib(_Line, _Filename, []) -> + error; +read_mib(Line, Filename, [Dir|Path]) -> + Dir2 = snmpc_misc:ensure_trailing_dir_delimiter(Dir), + case snmpc_misc:read_mib(AbsFile=lists:append(Dir2, Filename)) of + {ok, MIB} -> MIB; + {error, enoent} -> + read_mib(Line, Filename, Path); + {error, Reason} -> + ?vwarning("~s found but not imported: " + "~n Reason: ~p", [AbsFile,Reason]), + read_mib(Line, Filename, Path) + end. + + +%%---------------------------------------------------------------------- +%% imports ME or Type from other Mib into current compilation data. +%%---------------------------------------------------------------------- +import({node, NodeName}, #mib{mes = IMES, name = MibName}) -> + case lookup(NodeName, IMES) of + {value, ME} when ME#me.imported == false -> + register_oid(undef, ME#me.aliasname, root, ME#me.oid), + add_cdata(#cdata.mes, [ME#me{imported = true}]); + _ -> + print_error("Cannot find '~w' among the objects in the mib '~s'.", + [NodeName, MibName]) + end; +import({type, TypeName}, #mib{asn1_types = Types, name = MibName}) -> + case lists:keysearch(TypeName, #asn1_type.aliasname, Types) of + {value, ASN1Type} when is_record(ASN1Type, asn1_type) andalso + (ASN1Type#asn1_type.imported =:= false) -> + add_cdata(#cdata.asn1_types, [ASN1Type#asn1_type{imported=true, + aliasname=TypeName}]); + _X -> + print_error("Cannot find '~w' among the types in the mib '~s'.", + [TypeName, MibName]) + end; +import({builtin, Obj}, #mib{}) -> + print_error("~p should be imported from a standard mib.",[Obj]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for initialisation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Types defined in rfc1155 (SMI) are hard coded. +init_types() -> + VerDep = case get(snmp_version) of + 1 -> []; + 2 -> + [#asn1_type{imported=true,bertype='BITS',aliasname='BITS'}] + end, + [#asn1_type{imported = true, bertype = 'INTEGER', aliasname = 'INTEGER'}, + #asn1_type{imported=true,bertype='OCTET STRING',aliasname='OCTET STRING'}, + #asn1_type{imported=true,bertype='BIT STRING',aliasname='BIT STRING'}, + #asn1_type{imported = true, bertype = 'OBJECT IDENTIFIER', + aliasname = 'OBJECT IDENTIFIER'} | VerDep]. + +makeInternalNode(Name, Oid) -> + makeInternalNode3(false, Name, Oid). + +makeInternalNode2(Imported, Name) -> + #me{imported = Imported, aliasname = Name, entrytype = internal}. + +makeInternalNode3(Imported, Name, Oid) -> + #me{imported = Imported, oid = Oid, aliasname = Name, entrytype = internal}. + +make_cdata(CDataArg) -> + MibFuncs = + case CDataArg of + {module, _Mod} -> [CDataArg]; + {file, MibFuncsFile} -> read_funcs(MibFuncsFile) + end, + #cdata{mibfuncs = MibFuncs, + asn1_types = init_types(), + oid_ets = ets:new(oid_ets, [set, private]), + status_ets = ets:new(status_ets, [set, private])}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for Intermib consistency checking +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +is_consistent(Filenames) -> + case catch check_all_consistency(Filenames) of + ok -> + ok; + {undef, Format, Data} -> + ok = io:format(Format, Data), + io:format("~n"), + {error, inconsistent} + end. + +check_all_consistency(Filenames) -> + MIBs = [load_mib(Filename) || Filename <- Filenames], + check_oid_conflicts(MIBs), + check_trap_conflicts(MIBs), + ok. + +check_oid_conflicts(MIBs) -> + MEs = lists:append( [get_elem(MIB, #mib.mes) || MIB <- MIBs] ), + SortedMEs = lists:keysort(#me.oid, MEs), + search_for_dublettes2(#me{aliasname=dummy_init}, SortedMEs). + +check_trap_conflicts(MIBs) -> + Traps = lists:append( [get_elem(MIB, #mib.traps) || MIB <- MIBs] ), + [check_trap(Trap, Traps) || Trap <- Traps]. + +check_trap(Trap, Traps) -> + %% check_trap/3 -> error | ok + Checked = [check_trap(T, Trap, undef) || T <- lists:delete(Trap, Traps)], + case lists:member(error, Checked) of + true -> + throw({undef,"",[]}); + false -> + ok + end. + + +%%---------------------------------------------------------------------- +%% Returns: {Oid, ASN1Type} +%%---------------------------------------------------------------------- +trap_variable_info(Variable, Type, MEs) -> + case lookup(Variable, MEs) of + false -> + error("Error in ~s definition. Cannot find object '~w'.", + [Type, Variable]); + {value, ME} when ME#me.entrytype == variable -> + {{variable, ME#me.aliasname}, ME#me.asn1_type}; + {value, ME} -> + {{column, ME#me.aliasname}, ME#me.asn1_type} + end. + +get_elem(MIB, Idx) -> + element(Idx, MIB). + +load_mib(Filename) -> + F1 = snmpc_misc:strip_extension_from_filename(Filename, ".mib"), + F2 = lists:append(F1, ".bin"), + case snmpc_misc:read_mib(F2) of + {error, Reason} -> + throw({undef, "Error reading file: ~w. Reason:~w", [F1, Reason]}); + {ok, Mib} -> + Mib + end. + +search_for_dublettes2(_PrevME, []) -> ok; +search_for_dublettes2(PrevME, [ME|MEs]) + when ME#me.imported==true -> + search_for_dublettes2(PrevME, MEs); +search_for_dublettes2(PrevME, [ME|MEs]) + when PrevME#me.oid == ME#me.oid -> + if PrevME#me.entrytype == internal, ME#me.entrytype == internal, + PrevME#me.aliasname == ME#me.aliasname -> + search_for_dublettes2(ME, MEs); + true -> + throw({undef,"Multiple used object with OBJECT IDENTIFIER '~w'" + " Used by '~w' and '~w' ", [PrevME#me.oid, + PrevME#me.aliasname, + ME#me.aliasname]}) + end; +search_for_dublettes2(_PrevME, [ME|MEs]) -> + search_for_dublettes2(ME, MEs). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for handling of default value resolving +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +resolve_defval(ME) -> + case has_complex_defval(ME) of + true -> + CDATA = get(cdata), + resolve_complex_defval(ME, CDATA#cdata.mes); + false -> ME + end. + +has_complex_defval(#me{aliasname = N, + assocList = AssocList, + asn1_type = #asn1_type{bertype = BT}}) + when is_list(AssocList) -> + case snmpc_misc:assq(defval, AssocList) of + {value, Int} when is_integer(Int) -> + false; + {value, Val} when is_atom(Val) andalso (BT =:= 'OBJECT IDENTIFIER') -> + false; % resolved in update_me_oids + {value, Val} when is_atom(Val) andalso (BT =:= 'INTEGER') -> + true; + {value, Bits} when is_list(Bits) andalso (BT =:= 'BITS') -> + true; + {value, Str} when is_list(Str) andalso (BT =:= 'OCTET STRING') -> + false; % but ok + {value, Str} when is_list(Str) andalso (BT =:= 'Opaque') -> + false; % but ok + {value, Str} when is_list(Str) andalso + (length(Str) =:= 4) andalso + (BT =:= 'IpAddress') -> + false; % but ok + {value, Shit} -> + print_error("Bad default value for ~p: ~p [~p]",[N,Shit,BT]), + false; + false -> %% no defval (or strings nyi) + false + end; +has_complex_defval(_) -> false. + +resolve_complex_defval(ME, _AllMEs) + when (ME#me.asn1_type)#asn1_type.bertype == 'INTEGER' -> + #me{aliasname = MEName, assocList = AssocList} = ME, + {value, DefVal} = snmpc_misc:assq(defval, AssocList), + #asn1_type{bertype = TypeName, + assocList = AssocListForASN1Type} = ME#me.asn1_type, + case snmpc_misc:assq(enums, AssocListForASN1Type) of + false -> + print_error("Type '~w' has no defined enums. " + "Used in DEFVAL for '~w'.", [TypeName, MEName]), + ME; + {value, Enums} -> + case snmpc_misc:assq(DefVal, Enums) of + false -> + print_error("Enum '~w' not found. " + "Used in DEFVAL for '~w'.", [DefVal, MEName]), + ME; + {value, IntVal} when is_integer(IntVal) -> + ME#me{assocList = lists:keyreplace(defval, 1, AssocList, + {defval, IntVal})} + end + end; + +resolve_complex_defval(ME, _AllMEs) + when (ME#me.asn1_type)#asn1_type.bertype =:= 'BITS' -> + #me{aliasname = MEName, assocList = AssocList} = ME, + {value, DefVal} = snmpc_misc:assq(defval, AssocList), + #asn1_type{assocList = AssocListForASN1Type} = ME#me.asn1_type, + {value, Kibbles} = snmpc_misc:assq(kibbles, AssocListForASN1Type), + case snmpc_misc:bits_to_int(DefVal,Kibbles) of + error-> + print_error("Invalid default value ~w for ~w.",[DefVal, MEName]), + ME; + IntVal when is_integer(IntVal) -> + ME#me{assocList = lists:keyreplace(defval, 1, AssocList, + {defval, IntVal})} + end. + + +make_variable_info(#me{asn1_type = Asn1Type, assocList = Alist}) -> + Defval = + case snmpc_misc:assq(defval, Alist) of + {value, Val} -> + Val; + _ -> + get_def(Asn1Type) + end, + #variable_info{defval = Defval}. + +get_def(#asn1_type{bertype = BT, lo = LO, assocList = AL}) -> + ?vtrace("get_def -> entry with" + "~n BT: ~p" + "~n LO: ~p" + "~n AL: ~p", [BT, LO, AL]), + get_def(BT, LO, AL). + +get_def('INTEGER', Lo, _) when is_integer(Lo) -> Lo; +get_def('INTEGER', _, AL) -> + case snmpc_misc:assq(enums, AL) of + {value, Enums} -> + case lists:keysort(2, Enums) of + [{_, Val}|_] -> + Val; + _ -> + 0 + end; + _ -> + 0 + end; +get_def('Counter', _, _) -> 0; +get_def('Gauge', _, _) -> 0; +get_def('TimeTicks', _, _) -> 0; +get_def('OCTET STRING', _, _) -> ""; +get_def('IpAddress', _, _) -> [0,0,0,0]; +get_def('NetworkAddress', _, _) -> [0,0,0,0]; +get_def('OBJECT IDENTIFIER', _, _) -> [0, 0]; +get_def('Opaque', _, _) -> ""; +%v2 +get_def('Integer32',Lo, _) when is_integer(Lo) -> Lo; +get_def('Integer32',_, _) -> 0; +get_def('Counter32',_, _) -> 0; +get_def('Gauge32',_, _) -> 0; +get_def('Unsigned32',_, _) -> 0; +get_def('BITS',_, _) -> 0; +get_def('Counter64',_, _) -> 0. + +check_trap_name(EnterpriseName, Line, MEs) -> + case lists:keysearch(EnterpriseName, #me.aliasname, MEs) of + false -> + error("Error in trap definition. Cannot find object '~w'.", + [EnterpriseName],Line); + {value, _} -> + true + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for table functions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%---------------------------------------------------------------------- +%% This information is needed to be able to create default instrumentation +%% functions for tables. +%%---------------------------------------------------------------------- +make_table_info(Line, _TableName, {augments,SrcTableEntry}, ColumnMEs) -> + ColMEs = lists:keysort(#me.oid, ColumnMEs), + %% Nbr_of_Cols = length(ColMEs), + MEs = ColMEs ++ (get(cdata))#cdata.mes, + Aug = case lookup(SrcTableEntry,MEs) of + false -> + print_error("Cannot AUGMENT the non-existing table entry ~p", + [SrcTableEntry],Line), + {augments, error}; + {value,ME} -> + {augments, {SrcTableEntry,translate_type(ME#me.asn1_type)}} + end, + #table_info{index_types = Aug}; +make_table_info(Line, TableName, {indexes,[]}, _ColumnMEs) -> + print_error("Table ~w lacks indexes.", [TableName],Line), + #table_info{}; +make_table_info(Line, TableName, {indexes,Indexes}, ColumnMEs) -> + ColMEs = lists:keysort(#me.oid, ColumnMEs), + NonImpliedIndexes = lists:map(fun non_implied_name/1, Indexes), + test_read_create_access(ColMEs, Line, dummy), + NonIndexCol = test_index_positions(Line, NonImpliedIndexes, ColMEs), + Nbr_of_Cols = length(ColMEs), + ASN1Indexes = find_asn1_types_for_indexes(Indexes, ColMEs, Line), + FA = first_accessible(TableName, ColMEs), + StatCol = find_status_col(Line, TableName, ColMEs), + NoAccs = list_not_accessible(NonIndexCol,ColMEs), + case lists:member(StatCol,NoAccs) of + true -> + print_error("Status column cannot be not-accessible. In table ~p.", + [TableName],Line); + false -> ok + end, + #table_info{nbr_of_cols = Nbr_of_Cols, + first_own_index = find_first_own_index(NonImpliedIndexes, + ColMEs, 1), + status_col = StatCol, + first_accessible = FA, + not_accessible = NoAccs, + index_types = ASN1Indexes}. + +%% Perkins p110 +test_read_create_access([#me{aliasname = N, access = 'read-create'}|_ColMEs], + Line, 'read-write') -> + print_error("Column ~p cannot be read-create when another is read-write.", + [N], Line); +test_read_create_access([#me{aliasname = N, access = 'read-write'}|_ColMEs], + Line, 'read-create') -> + print_error("Column ~p cannot be read-write when another is read-create.", + [N], Line); +test_read_create_access([#me{access = 'read-write'}|ColMEs], + Line, _OtherStat) -> + test_read_create_access(ColMEs,Line,'read-write'); +test_read_create_access([#me{access = 'read-create'}|ColMEs], + Line, _OtherStat) -> + test_read_create_access(ColMEs,Line,'read-create'); +test_read_create_access([_ME|ColMEs],Line,OtherStat) -> + test_read_create_access(ColMEs,Line,OtherStat); +test_read_create_access([], _Line, _) -> + ok. + +find_status_col(_Line, _TableName, []) -> + undefined; +find_status_col(_Line, _TableName, + [#me{asn1_type=#asn1_type{aliasname='RowStatus'}}|_]) -> + 1; +find_status_col(Line, TableName, [_ShitME | MEs]) -> + case find_status_col(Line, TableName, MEs) of + undefined -> undefined; + N -> 1+N + end. + +list_not_accessible(none,_) -> []; +list_not_accessible(NonIndexCol, ColMEs) when is_integer(NonIndexCol) -> + list_not_accessible(lists:nthtail(NonIndexCol - 1,ColMEs)). + +list_not_accessible([#me{access='not-accessible', oid=Col}|ColMEs]) -> + [Col | list_not_accessible(ColMEs)]; +list_not_accessible([_ColME|ColMEs]) -> + list_not_accessible(ColMEs); +list_not_accessible([]) -> + []. + +%%---------------------------------------------------------------------- +%% See definition of first_own_index in the table_info record definition. +%%---------------------------------------------------------------------- +find_first_own_index([], _ColMEs, _FOI) -> 0; +find_first_own_index([NameOfIndex | Indexes], ColMEs, FOI) -> + case lists:keysearch(NameOfIndex, #me.aliasname, ColMEs) of + {value, _ME} -> + FOI; + false -> + find_first_own_index(Indexes, ColMEs, FOI + 1) + end. + +first_accessible(TableName, []) -> + error("Table '~w' must have at least one accessible column.",[TableName]); +first_accessible(TableName, [#me{access = 'not-accessible'} | T]) -> + first_accessible(TableName, T); +first_accessible(_TableName, [#me{oid = Col} | _]) -> + Col. + +get_defvals(ColMEs) -> + lists:keysort(1, + lists:filter(fun drop_undefined/1, + lists:map(fun column_and_defval/1, ColMEs))). + +find_asn1_types_for_indexes(Indexes, ColMEs,Line) -> + MEs = ColMEs ++ (get(cdata))#cdata.mes, + test_implied(Indexes, Line), + lists:map(fun (ColumnName) -> + translate_type(get_asn1_type(ColumnName, MEs,Line)) + end, + Indexes). + +test_implied([],_) -> ok; +test_implied([{implied, _Type}, _OtherIndexElem|_], Line) -> + print_error("Implied must be last.", [], Line); +test_implied([{implied, _Type}], _Line) -> + ok; +test_implied([_H|T], Line) -> + test_implied(T, Line). + +drop_undefined({_X, undefined}) -> false; +drop_undefined({_X, _Y}) -> true; +drop_undefined(undefined) -> false; +drop_undefined(_X) -> true. + +%% returns: {ColumnNo, Defval} +column_and_defval(#me{oid = Oid, assocList = AssocList}) -> + ColumnNo = lists:last(Oid), + case snmpc_misc:assq(defval, AssocList) of + false -> {ColumnNo, undefined}; + {value, DefVal} -> {ColumnNo, DefVal} + end. + +%% returns: an asn1_type if ColME is an indexfield, otherwise undefined. +get_asn1_type({implied,ColumnName}, MEs, Line) -> + case lookup(ColumnName, MEs) of + {value,#me{asn1_type=A}} when A#asn1_type.bertype =:= + 'OCTET STRING' -> + A#asn1_type{implied = true}; + {value,#me{asn1_type=A}} when A#asn1_type.bertype =:= + 'OBJECT IDENTIFIER' -> + A#asn1_type{implied = true}; + Shit -> + print_error("Only OCTET STRINGs and OIDs can be IMPLIED.(~w)", + [Shit], Line) + end; +get_asn1_type(ColumnName, MEs, Line) -> + case lookup(ColumnName, MEs) of + {value,ME} -> ME#me.asn1_type; + false -> error("Can't find object ~p. Used as INDEX in table.", + [ColumnName],Line) + end. + +test_index_positions(Line, Indexes, ColMEs) -> + TLI = lists:filter(fun (IndexName) -> + is_table_local_index(IndexName,ColMEs) end, + Indexes), + test_index_positions_impl(Line, TLI, ColMEs). + +%% Returns the first non-index column | none +test_index_positions_impl(_Line, [], []) -> none; +test_index_positions_impl(_Line, [], [#me{oid=Col}|_ColMEs]) -> + Col; +test_index_positions_impl(Line, Indexes, + [#me{aliasname = Name, + asn1_type = Asn1} | ColMEs]) -> + case lists:member(Name, Indexes) of + true -> + if + Asn1#asn1_type.bertype =:= 'BITS' -> + print_error("Invalid data type 'BITS' for index '~w'.", + [Name],Line); + true -> true + end, + test_index_positions_impl(Line, + lists:delete(Name, Indexes), ColMEs); + false -> + ?vwarning2("Index columns must be first for " + "the default functions to work properly. " + "~w is no index column.", [Name], Line), + none + end. + +is_table_local_index(IndexName, ColMEs) -> + case lists:keysearch(IndexName, #me.aliasname, ColMEs) of + false -> false; + _Q -> true + end. + +non_implied_name({implied, IndexColumnName}) -> IndexColumnName; +non_implied_name(IndexColumnName) -> IndexColumnName. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for generationg the final mib +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% returns: {ok, +% {snmp_mib, MEs, traps, list of {TrapOid, list of oids (objects)}}} +get_final_mib(Name, Options) -> + ?vdebug("get_final_mib -> entry", []), + CDATA = get(cdata), + #cdata{mes = MEs, + mibfuncs = MibFuncs, + asn1_types = Types, + traps = Traps0, + oid_ets = OidEts} = CDATA, + + ?vdebug("get_final_mib -> resolve oids", []), + resolve_oids(OidEts), + %% Reverse so that we get report on objects earlier in the file + %% before later objects. + UMEs = update_me_oids(lists:reverse(MEs), OidEts, []), + ?vtrace("get_final_mib -> " + "~n UMEs: ~p", [UMEs]), + + Traps1 = update_trap_objects(Traps0, MEs, []), + Traps2 = update_trap_oids(Traps1, OidEts, []), + ?vtrace("get_final_mib -> " + "~n Traps2: ~p", [Traps2]), + + SortedMEs = lists:keysort(#me.oid,UMEs), + ?vdebug("get_final_mib -> search for dublettes", []), + search_for_dublettes(#me{aliasname=dummy_init}, SortedMEs), + + ?vdebug("get_final_mib -> search for oid conflicts", []), + search_for_oid_conflicts(Traps2, SortedMEs), + + ?vdebug("get_final_mib -> resolve oid", []), + %% FIXME: use list comprehension instead + MibFs = lists:keysort(1, + lists:zf(fun({module, _Mod}) -> false; + (MF) -> {true, resolve_oid(MF,SortedMEs)} + end, MibFuncs)), + ?vtrace("get_final_mib -> " + "~n MibFs: ~p", [MibFs]), + {value, DBName} = snmpc_misc:assq(db, Options), + Module = key1search(module, MibFuncs, undefined), + MEsWithMFA = insert_mfa(MibFs, SortedMEs, DBName, Module), + Misc = [{snmp_version,get(snmp_version)} + | case lists:member(no_symbolic_info,Options) of + true -> [no_symbolic_info]; + false -> [] + end], + {value, GroupBool} = snmpc_misc:assq(group_check, Options), + case GroupBool of + true -> + case get(snmp_version) =:= 2 of + true -> + ?vdebug("get_final_mib -> check object groups:" + "~n ~p", [CDATA#cdata.objectgroups]), + check_group(CDATA#cdata.mes, + CDATA#cdata.objectgroups), + ?vdebug("get_final_mib -> check notifications group:" + "~n ~p", [CDATA#cdata.notificationgroups]), + check_notification(Traps2, + CDATA#cdata.notificationgroups); + false -> + ok + end; + false -> + ok + end, + MI = module_identity(CDATA), + Mib = #mib{name = Name, + misc = Misc, + module_identity = MI, + mes = lists:map(fun(ME) -> translate_me_type(ME) end, + MEsWithMFA), + variable_infos = extract_variable_infos(MEsWithMFA), + table_infos = extract_table_infos(MEsWithMFA), + traps = lists:map(fun(T) -> translate_trap_type(T) end, + Traps2), + asn1_types = lists:map(fun(T) -> translate_type(T) end, + Types), + imports = CDATA#cdata.imports}, + ?vdebug("get_final_mib -> done", []), + {ok, Mib}. + + +module_identity(#cdata{module_identity = MI}) -> + case lists:member(module_identity, get(options)) of + true -> + MI; + false -> + undefined + end. + + +update_trap_objects([], _MEs, Acc) -> + ?vtrace("update_trap_objects -> done", []), + lists:reverse(Acc); +update_trap_objects([#trap{trapname = Name, + oidobjects = Variables} = Trap|Traps], MEs, Acc) -> + ?vtrace("update_trap_objects -> update objects for trap ~p:" + "~n ~p", [Name, Variables]), + OidObjects = + [trap_variable_info(Var, "trap", MEs) || Var <- Variables], + UpdTrap = Trap#trap{oidobjects = OidObjects}, + update_trap_objects(Traps, MEs, [UpdTrap|Acc]); +update_trap_objects([#notification{trapname = Name, + oidobjects = Variables} = Notif|Traps], + MEs, Acc) -> + ?vtrace("update_trap_objects -> update objects for notification ~p:" + "~n ~p", [Name, Variables]), + OidObjects = + [trap_variable_info(Var, "notification", MEs) || Var <- Variables], + UpdNotif = Notif#notification{oidobjects = OidObjects}, + update_trap_objects(Traps, MEs, [UpdNotif|Acc]); +update_trap_objects([_|Traps], MEs, Acc) -> + update_trap_objects(Traps, MEs, Acc). + + +%% We don't want a zillion aliases for INTEGER (etc), +%% and since they are encoded with the same tag we can treat them as +%% equivalent. +%% The reason for having them at compile time is for example that +%% Unsigned32 is allowed as INDEX but not Gauge. +%% The compiler might want to ensure this and more... +translate_me_type(ME) -> + ME#me{asn1_type = translate_type(ME#me.asn1_type)}. + +translate_trap_type(Trap) when is_record(Trap, notification) -> + translate_trap_type_notif(Trap); +translate_trap_type(Trap) when is_record(Trap, trap) -> + translate_trap_type_trap(Trap). + +translate_trap_type_notif(Trap)-> + NewOidobjects = + lists:map(fun({Oid,ASN1type}) ->{Oid,translate_type(ASN1type)} end, + Trap#notification.oidobjects), + Trap#notification{oidobjects=NewOidobjects}. + +translate_trap_type_trap(Trap)-> + NewOidobjects = + lists:map(fun({Oid,ASN1type}) -> + {Oid, translate_type(ASN1type)} + end, + Trap#trap.oidobjects), + Trap#trap{oidobjects = NewOidobjects}. + +translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'NetworkAddress' -> + ASN1type#asn1_type{bertype = 'IpAddress'}; +translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Integer32' -> + ASN1type#asn1_type{bertype = 'INTEGER'}; +translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Counter' -> + ASN1type#asn1_type{bertype = 'Counter32'}; +translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Gauge' -> + ASN1type#asn1_type{bertype = 'Unsigned32'}; +translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Gauge32' -> + ASN1type#asn1_type{bertype = 'Unsigned32'}; +translate_type(ASN1type) -> ASN1type. + +%% Check for both NOTIFICATION-GROUP and OBJECT-GROUP + +check_notification_group(Name, GroupObjects, Line, Status) -> + #cdata{traps = Traps, status_ets = Ets} = get(cdata), + Objects = get_notification_names(Traps), + check_def(notification, Name, Line, Status, GroupObjects, Objects, Ets). + +get_notification_names(Traps) when is_list(Traps) -> + [Name || #notification{trapname = Name} <- Traps]. + +check_object_group(Name, GroupObjects, Line, Status) -> + #cdata{mes = MEs, status_ets = Ets} = get(cdata), + Objects = get_object_names(MEs), + check_def(object, Name, Line, Status, GroupObjects, Objects, Ets). + +get_object_names([])->[]; +get_object_names([#me{access=A, entrytype=T, aliasname=N}|MEs]) + when (A =/= 'not-accessible') andalso (T =/= 'internal') -> + [N|get_object_names(MEs)]; +get_object_names([_ME|Rest]) -> + get_object_names(Rest). + +%% Strictly we should not need to check more then the status +%% table, but since error do happen... +check_def(Type, Name, Line, Status, [GroupObject|GroupObjects], Objects, Ets) -> + ?vdebug2("check definition of ~p [~p]: presumed member of ~p [~p]", + [GroupObject, Type, Name, Status], Line), + case lists:member(GroupObject, Objects) of + true -> + ?vtrace("~p is a member of ~p", [GroupObject, Name]), + %% Lucky so far, now lets check that the status is valid + case ets:lookup(Ets, GroupObject) of + [{GroupObject, ObjectStatus}] -> + ?vtrace("check that the object status (~p) is valid", + [ObjectStatus]), + check_group_member_status(Name, Status, + GroupObject, ObjectStatus); + _ -> + print_error("group (~w) member ~w not found" + " in status table - status check failed", + [Name, GroupObject]) + end; + false -> + %% Ok, this could be because the status is obsolete or + %% deprecated (with the deprecated flag = false) + ?vtrace("~p is not a member of ~p " + "[object status could be obsolete]", + [GroupObject, Name]), + case ets:lookup(Ets, GroupObject) of + [{GroupObject, ObjectStatus}] -> + ?vtrace("check that the object status (~p) is valid", + [ObjectStatus]), + check_group_member_status(Name, Status, + GroupObject, ObjectStatus); + _ -> + group_member_error(Type, GroupObject, Line) + end + end, + check_def(Type, Name, Line, Status, GroupObjects, Objects, Ets); +check_def(_, _, _, _, [], _, _) -> + ok. + +group_member_error(object, Name, Line) -> + print_error("OBJECT-TYPE definition missing or " + "'not-accessible' for '~w'", [Name],Line); +group_member_error(notification, Name, Line) -> + print_error("NOTIFICATION-TYPE definition missing for '~w'", + [Name], Line). + + +check_group_member_status(_GroupName, _GroupStatus, _Member, undefined) -> + ok; +check_group_member_status(_GroupName, current, _Member, current) -> + ok; +check_group_member_status(GroupName, current, Member, MemberStatus) -> + group_member_status_error(GroupName, current, Member, MemberStatus, + "current"); +check_group_member_status(_GroupName, deprecated, _Member, MemberStatus) + when (MemberStatus =:= deprecated) orelse (MemberStatus =:= current) -> + ok; +check_group_member_status(GroupName, deprecated, Member, MemberStatus) -> + group_member_status_error(GroupName, deprecated, Member, MemberStatus, + "deprecated or current"); +check_group_member_status(_GroupName, obsolete, _Member, MemberStatus) + when (MemberStatus =:= obsolete) orelse + (MemberStatus =:= deprecated) orelse + (MemberStatus =:= current) -> + ok; +check_group_member_status(GroupName, obsolete, Member, MemberStatus) -> + group_member_status_error(GroupName, obsolete, Member, MemberStatus, + "obsolete, deprecated or current"); +check_group_member_status(_GroupName, _GroupStatus, _Member, _MemberStatus) -> + ok. + +group_member_status_error(Name, Status, Member, MemberStatus, Expected) -> + snmpc_lib:print_error("Invalid status of group member ~p " + "in group ~p. " + "Group status is ~p " + "and member status is ~p " + "(should be ~s)", + [Member, Name, Status, MemberStatus, Expected]). + + + +% check_def(Objects,[GroupObject|GroupObjects],Line)-> +% case lists:member(GroupObject,Objects) of +% false -> +% print_error("OBJECT-TYPE definition missing or " +% "'not-accessible' for '~w'", [GroupObject],Line), +% check_def(Objects,GroupObjects,Line); +% true -> +% check_def(Objects,GroupObjects,Line) +% end; +% check_def(_Objects,[],_Line) -> +% ok. + +%% Checking the definition of OBJECT-GROUP + +%%----------------------------- +check_group([#me{imported = true} | T],GroupObjects)-> + ?vtrace("check_group(imported) -> skip", []), + check_group(T,GroupObjects); +check_group([],_GroupObjects) -> + ?vtrace("check_group -> done", []), + ok; +check_group([#me{access = A, + entrytype = T, + aliasname = N}|MEs], GroupObjects) + when ((A =/= 'not-accessible') andalso + (T =/= 'internal') andalso + (T =/= group)) -> + ?vtrace("check_group -> " + "~n access: ~p" + "~n entrytype: ~p" + "~n aliasname: ~p", [A, T, N]), + check_member_group(N, GroupObjects), + check_group(MEs,GroupObjects); +check_group([_|MEs],GroupObjects) -> + check_group(MEs,GroupObjects). + +check_member_group(Aliasname, [])-> + print_error("'~w' missing in OBJECT-GROUP",[Aliasname]); +check_member_group(Aliasname, [{Name,GroupObject,Line}|Tl])-> + ?vtrace2("check_member_group -> entry with" + "~n Aliasname: ~p" + "~n Name: ~p" + "~n GroupObject: ~p", [Aliasname, Name, GroupObject], Line), + case lists:member(Aliasname,GroupObject) of + true -> + ok; + false -> + check_member_group(Aliasname,Tl) + end. + + +%% Checking definition in NOTIFICATION-GROUP + +%%-------------------------- +check_notification([],_NotificationObjects) -> + ok; +check_notification([#notification{trapname=Aliasname}|Traps], + NotificationObjects) -> + check_member_notification(Aliasname, NotificationObjects), + check_notification(Traps,NotificationObjects); +check_notification([_|Traps],NotificationObjects) -> + check_notification(Traps,NotificationObjects). + +check_member_notification(Aliasname,[])-> + print_error("'~w' missing in NOTIFICATION-GROUP",[Aliasname]); +check_member_notification(Aliasname,[{_Name,NotificationObject,_Line}|Tl]) -> + case lists:member(Aliasname, NotificationObject) of + true -> + ok; + false -> + check_member_notification(Aliasname,Tl) + end. + + +%%---------------------------------------------------------------------- +%% Purpose: Resolves oids for aliasnames used in .funcs file. +%% Returns: {Oid, X} +%%---------------------------------------------------------------------- +resolve_oid({NameOrOid, X}, MEs) -> + case lookup(NameOrOid, MEs) of + {value, #me{oid=Oid,entrytype=variable}} -> {Oid, X}; + {value, #me{oid=Oid,entrytype=table}} -> {Oid, X}; + {value, #me{entrytype=table_entry}} -> + error("Cannot associate an instrumentation function with a " + "Table Entry: ~w (must be table or variable)", + [NameOrOid]); + {value, #me{entrytype=table_column}} -> + error("Cannot associate an instrumentation function with a " + "Table Column: ~w (must be table or variable)", + [NameOrOid]); + _Q -> + error("Cannot find OBJECT-TYPE definition for '~w'.", + [NameOrOid]) + end. + +%%---------------------------------------------------------------------- +%% Fs is list of {Oid, {M,F,A}} +%% returns: MEs with access-functions. +%% Pre: Fs, MEs are sorted (on Oid) (then we can traverse mib efficiently) +%%---------------------------------------------------------------------- +insert_mfa(Fs, [ME | MEs], DBName, Mod) + when ME#me.imported =:= true -> + [ME | insert_mfa(Fs, MEs, DBName, Mod)]; + +insert_mfa(Fs, [ME | MEs], DBName, Mod) + when ME#me.entrytype =:= internal -> + [ME | insert_mfa(Fs, MEs, DBName, Mod)]; + +insert_mfa(Fs, [ME|MEs], DBName, Mod) + when ME#me.entrytype =:= group -> + [ME | insert_mfa(Fs, MEs, DBName, Mod)]; + +insert_mfa([X | Fs], [ME | MEs], DBName, Mod) + when ME#me.entrytype =:= variable -> + {Oid, {M,F,A}} = X, + case ME#me.oid of + Oid -> + [ME#me{mfa = {M,F,A}} | insert_mfa(Fs, MEs, DBName, Mod)]; + _Q -> + [insert_default_mfa(ME, DBName, Mod) | + insert_mfa([X | Fs], MEs, DBName, Mod)] + end; + +insert_mfa([X | Fs], [TableME | MEs], DBName, Mod) + when TableME#me.entrytype =:= table -> + {Oid, {M,F,A}} = X, + {TableMEs, RestMEs} = collect_mes_for_table(TableME, [TableME | MEs]), + [TableEntryME | ColMEs] = tl(TableMEs), + DefVals = get_defvals(ColMEs), + {value,TableInfo} = snmpc_misc:assq(table_info,TableME#me.assocList), + NAssocList = [{table_info, TableInfo#table_info{defvals = DefVals}} | + lists:keydelete(table_info, 1, TableME#me.assocList)], + NTableME = TableME#me{assocList = NAssocList}, + case is_same_table(Oid, NTableME#me.oid) of + true -> % use mfa from .funcs + lists:append([NTableME, + TableEntryME#me{mfa = {M, F, A}} + | ColMEs], + insert_mfa(Fs, RestMEs, DBName, Mod)); + false -> + lists:append(insert_default_mfa([NTableME | tl(TableMEs)], + DBName, Mod), + insert_mfa([X|Fs], RestMEs, DBName, Mod)) + end; + +insert_mfa([], [ME|MEs], DBName, Mod) + when ME#me.entrytype =:= variable -> + [insert_default_mfa(ME, DBName, Mod) | insert_mfa([], MEs, DBName, Mod)]; + +insert_mfa([], [ME|MEs], DBName, Mod) + when ME#me.entrytype =:= table -> + {TableMEs, RestMEs} = collect_mes_for_table(ME, [ME|MEs]), + [TableME, _TableEntryME | ColMEs] = TableMEs, + DefVals = get_defvals(ColMEs), + {value,TableInfo} = snmpc_misc:assq(table_info,TableME#me.assocList), + NAssocList = [{table_info, TableInfo#table_info{defvals = DefVals}} | + lists:keydelete(table_info, 1, TableME#me.assocList)], + NTableME = TableME#me{assocList = NAssocList}, + NewTableMEs = insert_default_mfa([NTableME | tl(TableMEs)], DBName, Mod), + lists:append(NewTableMEs, insert_mfa([], RestMEs, DBName, Mod)); + +insert_mfa([], [], _DBName, _Mod) -> + []; +insert_mfa([], [ME|_MEs], _DBName, _Mod) -> + error("Missing access-functions for '~w'.",[ME#me.aliasname]). + +%%---------------------------------------------------------------------- +%% Returns: {[TableME, TableEntryME | ColumnMEs], RestMEs} +%%---------------------------------------------------------------------- +collect_mes_for_table(_TableME, []) -> + {[], []}; + +collect_mes_for_table(TableME, [ME|MEs]) -> + case is_same_table(TableME#me.oid, ME#me.oid) of + true -> + {TableMEs, RestMEs} = collect_mes_for_table(TableME, MEs), + {[ME | TableMEs], RestMEs}; + false -> + {[], [ME | MEs]} + end. + +%% returns: MibEntry with access-functions. +insert_default_mfa(ME, DBName, undefined) when is_record(ME, me)-> + case lists:member(no_defs, get(options)) of + true -> + error("Missing access function for ~s", [ME#me.aliasname]); + false -> + ?vinfo("No accessfunction for '~w' => using default", + [ME#me.aliasname]), + set_default_function(ME, DBName) + end; + +insert_default_mfa(ME, _DBName, Mod) when is_record(ME, me)-> + ME#me{mfa = {Mod, ME#me.aliasname, []}}; + +insert_default_mfa([TableME, EntryME | Columns], DBName, undefined) -> + case lists:member(no_defs, get(options)) of + true -> + error("Missing access function for ~s", [TableME#me.aliasname]); + false -> + ?vinfo("No accessfunction for '~w' => using default", + [TableME#me.aliasname]), + set_default_function([TableME, EntryME | Columns], DBName) + end; + +insert_default_mfa([TableME, EntryME | Columns], _DBName, Mod) -> + [TableME, + EntryME#me{mfa = {Mod, TableME#me.aliasname, []}} | + Columns]. + + +%% returns boolean. +is_same_table(Oid, TableOid) -> + lists:prefix(Oid, TableOid). + +%% returns false | {value, ME} +lookup(UniqName, MEs) when is_atom(UniqName) -> + lists:keysearch(UniqName, #me.aliasname, MEs); +lookup(Oid, MEs) when is_list(Oid) -> + lists:keysearch(Oid, #me.oid, MEs). + +search_for_dublettes(PrevME, [ME|_MEs]) + when PrevME#me.oid =:= ME#me.oid -> + error("Multiple used object with OBJECT IDENTIFIER '~w'. " + "Used in '~w' and '~w'.", [PrevME#me.oid, + PrevME#me.aliasname, + ME#me.aliasname]); +search_for_dublettes(PrevME, [ME|MEs]) + when ((PrevME#me.entrytype =:= variable) andalso + (ME#me.entrytype =:= variable)) -> + case lists:prefix(PrevME#me.oid, ME#me.oid) of + true -> + error("Variable '~w' (~w) defined below other " + "variable '~w' (~w). ", + [ME#me.aliasname, ME#me.oid, + PrevME#me.aliasname, PrevME#me.oid]); + false -> + search_for_dublettes(ME, MEs) + end; +search_for_dublettes(_PrevME, [ME|MEs]) -> + search_for_dublettes(ME, MEs); +search_for_dublettes(_PrevME, []) -> + ok. + + +search_for_oid_conflicts([Rec|Traps],MEs) when is_record(Rec,notification) -> + #notification{oid = Oid, trapname = Name} = Rec, + case search_for_oid_conflicts1(Oid,MEs) of + {error,ME} -> + error("Notification with OBJECT IDENTIFIER '~w'. " + "Used in '~w' and '~w'.", [Oid,Name,ME#me.aliasname]); + ok -> + search_for_oid_conflicts(Traps,MEs) + end; +search_for_oid_conflicts([_Trap|Traps],MEs) -> + search_for_oid_conflicts(Traps,MEs); +search_for_oid_conflicts([],_MEs) -> + ok. + +search_for_oid_conflicts1(_Oid,[]) -> + ok; +search_for_oid_conflicts1(Oid,[ME|_MEs]) when Oid == ME#me.oid -> + {error,ME}; +search_for_oid_conflicts1(Oid,[_ME|MEs]) -> + search_for_oid_conflicts1(Oid,MEs). + +set_default_function([TableMe, EntryMe | ColMes], DBName) -> + #me{aliasname = Name} = TableMe, + check_rowstatus(TableMe), + [TableMe, + EntryMe#me{mfa = {snmp_generic, table_func, [{Name, DBName}]}} | + ColMes]; + +set_default_function(MibEntry,DBName) when MibEntry#me.entrytype == variable -> + #me{aliasname = Aname} = MibEntry, + MibEntry#me{mfa = {snmp_generic, variable_func, [{Aname, DBName}]}}. + +check_rowstatus(TableME) -> + {value,TableInfo} = snmpc_misc:assq(table_info,TableME#me.assocList), + case TableInfo#table_info.status_col of + undefined -> + ?vwarning("No RowStatus column in table ~w => " + "The default functions won't work properly", + [TableME#me.aliasname]); + _Q -> ok + end. + +check_trap(#trap{trapname=N1, specificcode=C, enterpriseoid=E}, + #trap{trapname=N2, specificcode=C, enterpriseoid=E},Line) -> + print_error("Trap code collision. Enterprise: ~w. Trapcode: ~w, " + "Name of traps: ~w, ~w.", [E, C, N1, N2],Line), + error; +check_trap(#trap{trapname=N, specificcode=C1, enterpriseoid=E1}, + #trap{trapname=N, specificcode=C2, enterpriseoid=E2},Line) -> + print_error("Trap name collision. Name: ~w Enterprises: ~w, ~w. " + "Trapcodes: ~w, ~w", [N, E1, E2, C1, C2],Line), + error; +check_trap(_OldTrap, _ThisTrap, _Line) -> + ok. + +check_notification(Notif, Line, Notifs) -> + lists:map(fun (OtherNotif) -> + check_notification1(Notifs,OtherNotif,Line) + end, lists:delete(Notif,Notifs)). + +check_notification1(#notification{trapname=N},#notification{trapname=N},Line)-> + print_error("Trap name collision for '~w.",[N],Line); +check_notification1(#notification{oid=Oid},#notification{oid=Oid},Line)-> + print_error("Trap oid collision for '~w.",[Oid],Line); +check_notification1(_T1, _T2, _L) -> + ok. + +%%---------------------------------------------------------------------- +%% Returns: list of {VariableName, variable_info-record} +%%---------------------------------------------------------------------- +extract_variable_infos([]) -> []; +extract_variable_infos([#me{entrytype = variable, + assocList = AL, + aliasname = Name} | T]) -> + {value, VI} = snmpc_misc:assq(variable_info, AL), + [{Name, VI} | extract_variable_infos(T)]; +extract_variable_infos([_ME | T]) -> + extract_variable_infos(T). + +%%---------------------------------------------------------------------- +%% Returns: list of {TableName, table_info-record} +%%---------------------------------------------------------------------- +extract_table_infos([]) -> []; +extract_table_infos([#me{entrytype = table, + assocList = AL, + aliasname = Name} | T]) -> + {value, VI} = snmpc_misc:assq(table_info, AL), + [{Name, VI} | extract_table_infos(T)]; +extract_table_infos([_ME | T]) -> + extract_table_infos(T). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for misc useful functions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% m(M) -> {?MODULE, M}. + +set_dir(File, NewDir) -> + case string:chr(File, $/) of + 0 -> lists:append(NewDir, File); + N -> set_dir(lists:nthtail(N,File), NewDir) + end. + +%% Look up a key in a list, and if found returns the value +%% or if not found returns the default value +key1search(Key, List) -> + key1search(Key, List, undefined). + +key1search(Key, List, Default) -> + case lists:keysearch(Key, 1, List) of + {value, {Key, Val}} -> Val; + _ -> Default + end. + + +%% print the compiled mib +look_at(FileName) -> + case file:read_file(FileName) of + {ok,Bin} -> + binary_to_term(Bin); + {error,Reason} -> + {error,Reason} + end. + + +%% Data is appended to compiler information +add_cdata(OffsetInRecord, ListOfData) -> + CDATA = get(cdata), + OldData = element(OffsetInRecord, CDATA), + put(cdata, setelement(OffsetInRecord, CDATA, lists:append(ListOfData, + OldData))), + undefined. + +check_sub_ids([H | _T], Name, Line) when H < 0 -> + error("OBJECT IDENTIFIER must have all sub " + "indexes > 0. Name: '~w'. Illegal sub index: ~w.", + [Name, H], Line); +check_sub_ids([H | _T], Name, Line) when H > 4294967295 -> + error("OBJECT IDENTIFIER must have all sub " + "indexes < 4294967295. Name: '~w'. Illegal sub index: ~w.", + [Name, H], Line); +check_sub_ids([_H | T], Name, Line) -> + check_sub_ids(T, Name, Line); +check_sub_ids([], _Name, _Line) -> + ok. + + +%%----------------------------------------------------------------- +%% Handle forward references: +%% This code handles OIDs that are defined in terms of a +%% parent OID that is defined later in the file. Ex: +%% x OBJECT IDENTIFIER ::= {y 1} +%% y OBJECT IDENTIFIER ::= {enterprises 1} +%% The following alg is used to handle this: +%% Datastructure: +%% An ets table, with one entry for each object in the mib: +%% {Name, FatherName, Line, SubIndex, Children} +%% Name : aliasname +%% FatherName : aliasname of parent object +%% SubIndex : list of subindexes from parent object +%% Children : list of aliasnames for all objects registered +%% under this one +%% FatherName == 'root' => top-level object +%% 1) When an OID is found in the mib, it is registered using +%% register_oid/4. This function updates the parent entry, +%% by adding the new name to its Children list. It also +%% updates the entry for the object defined. +%% 2) When all objects are registered, the ets table contains +%% a directed graph of all objects. +%% 3) resolve_oids/1 is called. This function traverses the +%% graph, starting at 'root', and changes each entry to +%% {Name, Line, Oid}, where Oid is a list of integers. +%% 4) The list of MibEntries is traversed. Each object is +%% looked up in the ets table, and the correspsonding oid +%% is updated. The functions for this is update_me_oids/2 +%% and update_trap_oids/2. +%%----------------------------------------------------------------- +register_oid(Line, Name, ParentName, SubIndex) when Name =/= '$no_name$' -> + ?vtrace2("register_oid -> entry with" + "~n Name: ~p" + "~n ParentName: ~p" + "~n SubIndex: ~p", [Name, ParentName, SubIndex], Line), + check_sub_ids(SubIndex, Name, Line), + OidEts = (get(cdata))#cdata.oid_ets, + %% Lookup Parent - if it doesn't already exist, create it + {_ParentName, ItsParentName, ItsLine, ItsSubIndex, Children} = + case ets:lookup(OidEts, ParentName) of + [Found] -> + ?vtrace("register_oid -> parent found: " + "~n ~p", [Found]), + Found; + [] -> + ?vtrace("register_oid -> father not found: " + "~n ~p", [ets:tab2list(OidEts)]), + {ParentName, undef, undef, [], []} + end, + %% Update Father with a pointer to us + NChildren = case lists:member(Name, Children) of + true -> Children; + false -> [Name | Children] + end, + NParent = {ParentName, ItsParentName, ItsLine, ItsSubIndex, NChildren}, + ?vtrace("register_oid -> NParent: ~n~p", [NParent]), + ets:insert(OidEts, NParent), + %% Lookup ourselves - if we don't exist, create us + MyChildren = + case ets:lookup(OidEts, Name) of + [Found2] -> + ?vtrace("register_oid -> children found: " + "~n ~p", [Found2]), + element(5, Found2); + [] -> + ?vtrace("register_oid -> no children found", []), + [] + end, + %% Update ourselves + NSelf = {Name, ParentName, Line, SubIndex, MyChildren}, + ?vtrace("register_oid -> NSelf: " + "~n ~p", [NSelf]), + ets:insert(OidEts, NSelf); +register_oid(_Line, _Name, _ParentName, _SubIndex) -> + ok. + + +resolve_oids(OidEts) -> + case ets:lookup(OidEts, root) of + [{_, _, _, _, RootChildren}] -> + resolve_oids(RootChildren, [], OidEts); + [] -> + ok + end. + +resolve_oids([Name | T], FatherOid, OidEts) -> + {MyOid, MyChildren, MyLine} = + case ets:lookup(OidEts, Name) of + [{Name, Oid, Line}] -> + print_error("Circular OBJECT IDENTIFIER definitions " + "involving ~w\n", [Name], Line), + {Oid, [], Line}; + [{Name, _Father, Line, SubIndex, Children}] -> + {FatherOid ++ SubIndex, Children, Line} + end, + ets:insert(OidEts, {Name, MyOid, MyLine}), + resolve_oids(T, FatherOid, OidEts), + resolve_oids(MyChildren, MyOid, OidEts); +resolve_oids([], _, _) -> + ok. + + + +update_me_oids([], _OidEts, Acc) -> + lists:reverse(Acc); +update_me_oids([#me{aliasname = '$no_name$'} | Mes], OidEts, Acc) -> + update_me_oids(Mes, OidEts, Acc); +update_me_oids([Me | Mes], OidEts, Acc) -> + ?vtrace("update_me_oids -> entry with" + "~n Me: ~p", [Me]), + Oid = tr_oid(Me#me.aliasname, OidEts), + NMe = resolve_oid_defval(Me, OidEts), + update_me_oids(Mes, OidEts, [NMe#me{oid = Oid} | Acc]). + +update_trap_oids([], _OidEts, Acc) -> + lists:reverse(Acc); +update_trap_oids([#trap{enterpriseoid = EOid, + oidobjects = OidObjs} = Trap | Traps], + OidEts, Acc) -> + ?vtrace("update_trap_oids -> entry with" + "~n EOid: ~p", [EOid]), + NEnter = tr_oid(EOid, OidEts), + NOidObjs = tr_oid_objs(OidObjs, OidEts), + NTrap = Trap#trap{enterpriseoid = NEnter, + oidobjects = NOidObjs}, + update_trap_oids(Traps, OidEts, [NTrap|Acc]); +update_trap_oids([#notification{trapname = Name, + oidobjects = OidObjs} = Notif | Traps], + OidEts, Acc) -> + ?vtrace("update_trap_oids -> entry with" + "~n Name: ~p", [Name]), + Oid = tr_oid(Name, OidEts), + NOidObjs = tr_oid_objs(OidObjs, OidEts), + NNotif = Notif#notification{oid = Oid, oidobjects = NOidObjs}, + update_trap_oids(Traps, OidEts, [NNotif|Acc]). + +tr_oid(Name, OidEts) -> + ?vtrace("tr_oid -> entry with" + "~n Name: ~p", [Name]), + case ets:lookup(OidEts, Name) of + [{Name, MyOid, _MyLine}] -> + MyOid; + [{_Natrap, Parent, Line, SubIndex, _Children}] -> + print_error("OBJECT IDENTIFIER [~w] defined in terms " + "of undefined parent object. Parent: '~w'." + "(Sub-indexes: ~w.)", + [Name, Parent, SubIndex], Line), + ?vtrace("tr_oid -> ets:tab2list(~w): " + "~n ~p", [OidEts, ets:tab2list(OidEts)]), + rnd_oid() + end. + +tr_oid_objs([{{variable, Name}, Type} | T], OidEts) -> + ?vtrace("tr_oid_objs(variable) -> entry with" + "~n Name: ~p", [Name]), + Oid = tr_oid(Name, OidEts) ++ [0], + [{Oid, Type} | tr_oid_objs(T, OidEts)]; +tr_oid_objs([{{column, Name}, Type} | T], OidEts) -> + ?vtrace("tr_oid_objs(column) -> entry with" + "~n Name: ~p", [Name]), + Oid = tr_oid(Name, OidEts), + [{Oid, Type} | tr_oid_objs(T, OidEts)]; +tr_oid_objs([], _OidEts) -> + []. + + +resolve_oid_defval(ME, OidEts) + when (ME#me.asn1_type)#asn1_type.bertype == 'OBJECT IDENTIFIER' -> + #me{aliasname = MEName, assocList = AssocList} = ME, + case snmpc_misc:assq(defval, AssocList) of + {value, DefVal} when is_atom(DefVal) -> + case ets:lookup(OidEts, DefVal) of + [{_, Oid, _}] -> + ME#me{assocList = lists:keyreplace(defval, 1, AssocList, + {defval, Oid})}; + _ -> + print_error("Can not find OBJECT-TYPE definition for '~w' " + "Used in DEFVAL for '~w'.", [DefVal, MEName]), + ME + end; + _ -> + ME + end; +resolve_oid_defval(ME, _OidEts) -> + ME. + +rnd_oid() -> + [99,99]. %% '99' means "stop computer" in Y2Kish... + +error(FormatStr, Data) when is_list(FormatStr) -> + print_error(FormatStr,Data), + exit(error). + +error(FormatStr, Data, Line) when is_list(FormatStr) -> + print_error(FormatStr,Data,Line), + exit(error). + +print_error(FormatStr, Data) when is_list(FormatStr) -> + ok = io:format("~s: Error: " ++ FormatStr,[get(filename)|Data]), + put(errors,yes), + io:format("~n"). + +print_error(FormatStr, Data,Line) when is_list(FormatStr) -> + ok = io:format("~s: ~w: Error: " ++ FormatStr,[get(filename), Line |Data]), + put(errors,yes), + io:format("~n"). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for debug functions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +vprint(Severity, Mod, Line, MibLine, F, A) -> + case printable(Severity) of + standard when is_integer(MibLine) -> + io:format("[~s:~w][~s]: " ++ F ++ "~n", + [get(filename), MibLine, image_of_severity(Severity)|A]); + standard -> + io:format("[~s][~s]: " ++ F ++ "~n", + [get(filename), image_of_severity(Severity)|A]); + extended when is_integer(MibLine) -> + io:format("[~s:~w][~w:~w][~s]: " ++ F ++ "~n", + [get(filename), MibLine, Mod, Line, + image_of_severity(Severity)|A]); + extended -> + io:format("[~s][~w:~w][~s]: " ++ F ++ "~n", + [get(filename), Mod, Line, + image_of_severity(Severity)|A]); + _ -> + ok + end. + +printable(Severity) -> + printable(get(verbosity), Severity). + +printable(silence, _) -> none; +printable(warning, warning) -> standard; +printable(info, info) -> standard; +printable(info, warning) -> standard; +printable(log, warning) -> standard; +printable(log, info) -> standard; +printable(log, log) -> standard; +printable(debug, warning) -> standard; +printable(debug, info) -> standard; +printable(debug, log) -> standard; +printable(debug, debug) -> standard; +printable(trace, _Sev) -> extended; +printable(_Ver, _Sev) -> none. + +-spec image_of_severity(Sev :: severity()) -> string(). +image_of_severity(warning) -> "WAR"; +image_of_severity(info) -> "INF"; +image_of_severity(log) -> "LOG"; +image_of_severity(debug) -> "DBG"; +image_of_severity(trace) -> "TRC"; +image_of_severity(_) -> " - ". + + +vvalidate(silence) -> ok; +vvalidate(warning) -> ok; +vvalidate(info) -> ok; +vvalidate(log) -> ok; +vvalidate(debug) -> ok; +vvalidate(trace) -> ok; +vvalidate(V) -> exit({invalid_verbosity,V}). + + diff --git a/lib/snmp/src/compile/snmpc_lib.hrl b/lib/snmp/src/compile/snmpc_lib.hrl new file mode 100644 index 0000000000..000486e728 --- /dev/null +++ b/lib/snmp/src/compile/snmpc_lib.hrl @@ -0,0 +1,37 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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% +%% + +-ifndef(snmpc_lib). +-define(snmpc_lib, true). + +-define(vwarning(F, A), ?verbosity(warning, F, A, ignore)). +-define(vwarning2(F, A, MibLine), ?verbosity(warning, F, A, MibLine)). +-define(vinfo(F, A), ?verbosity(info, F, A, ignore)). +-define(vinfo2(F, A, MibLine), ?verbosity(info, F, A, MibLine)). +-define(vlog(F, A), ?verbosity(log, F, A, ignore)). +-define(vlog2(F, A, MibLine), ?verbosity(log, F, A, MibLine)). +-define(vdebug(F, A), ?verbosity(debug, F, A, ignore)). +-define(vdebug2(F, A, MibLine), ?verbosity(debug, F, A, MibLine)). +-define(vtrace(F, A), ?verbosity(trace, F, A, ignore)). +-define(vtrace2(F, A, MibLine), ?verbosity(trace, F, A, MibLine)). + +-define(verbosity(Severity, F, A, MibLine), + snmpc_lib:vprint(Severity, ?MODULE, ?LINE, MibLine, F, A)). + +-endif. % -ifndef(snmpc_lib). diff --git a/lib/snmp/src/compile/snmpc_mib_gram.yrl b/lib/snmp/src/compile/snmpc_mib_gram.yrl new file mode 100644 index 0000000000..1957f52936 --- /dev/null +++ b/lib/snmp/src/compile/snmpc_mib_gram.yrl @@ -0,0 +1,973 @@ +%% +%% %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% +%% + +%%---------------------------------------------------------------------- +%% Number of expected shift/reduce warnings +%% This is ugly but... +%%---------------------------------------------------------------------- + +Expect 2. + + +%% ---------------------------------------------------------------------- +Nonterminals +%% ---------------------------------------------------------------------- +accessv1 +definition +defvalpart +description +descriptionfield +displaypart +entry +namedbits +fatherobjectname +fieldname +fields +implies +import +import_stuff +imports +imports_from_one_mib +index +indexpartv1 +indextypev1 +indextypesv1 +parentintegers +listofdefinitions +listofimports +mib +mibname +nameassign +newtype +newtypename +objectidentifier +objectname +objecttypev1 +range_num +referpart +size +sizedescr +statusv1 +syntax +tableentrydefinition +traptype +type +usertype +variables +varpart + +%v2 +moduleidentity +revisionpart +revisions +listofdefinitionsv2 +mibid +last_updated +oranization +contact_info +revision +revision_string +revision_desc +v1orv2 +objectidentity +objecttypev2 +unitspart +indexpartv2 +indextypesv2 +indextypev2 +statusv2 +accessv2 +notification +objectspart +objects +definitionv2 +textualconvention +objectgroup +notificationgroup +modulecompliance +modulepart +modules +module +modulenamepart +mandatorypart +compliancepart +compliances +compliance +compliancegroup +object +syntaxpart +writesyntaxpart +accesspart +fsyntax +defbitsvalue +defbitsnames +. +%% ---------------------------------------------------------------------- +Terminals +%% ---------------------------------------------------------------------- +integer variable atom string quote '{' '}' '::=' ':' '=' ',' '.' '(' ')' ';' '|' +'ACCESS' +'BEGIN' +'BIT' +'Counter' +'DEFINITIONS' +'DEFVAL' +'DESCRIPTION' +'DISPLAY-HINT' +'END' +'ENTERPRISE' +'FROM' +'Gauge' +'IDENTIFIER' +'IMPORTS' +'INDEX' +'INTEGER' +'IpAddress' +'NetworkAddress' +'OBJECT' +'OBJECT-TYPE' +'OCTET' +'OF' +'Opaque' +'REFERENCE' +'SEQUENCE' +'SIZE' +'STATUS' +'STRING' +'SYNTAX' +'TRAP-TYPE' +'TimeTicks' +'VARIABLES' + +%v2 +'LAST-UPDATED' +'ORGANIZATION' +'CONTACT-INFO' +'MODULE-IDENTITY' +'NOTIFICATION-TYPE' +'MODULE-COMPLIANCE' +'OBJECT-GROUP' +'NOTIFICATION-GROUP' +'REVISION' +'OBJECT-IDENTITY' +'MAX-ACCESS' +'UNITS' +'AUGMENTS' +'IMPLIED' +'OBJECTS' +'TEXTUAL-CONVENTION' +'NOTIFICATIONS' +'MODULE' +'MANDATORY-GROUPS' +'GROUP' +'WRITE-SYNTAX' +'MIN-ACCESS' +'BITS' +'DisplayString' +'PhysAddress' +'MacAddress' +'TruthValue' +'TestAndIncr' +'AutonomousType' +'InstancePointer' +'VariablePointer' +'RowPointer' +'RowStatus' +'TimeStamp' +'TimeInterval' +'DateAndTime' +'StorageType' +'TDomain' +'TAddress' +. + + +Rootsymbol mib. +Endsymbol '$end'. + +% ********************************************************************** + +mib -> mibname 'DEFINITIONS' implies 'BEGIN' + import v1orv2 'END' + : {Version, Defs} = '$6', + #pdata{mib_version = Version, + mib_name = '$1', + imports = '$5', + defs = Defs}. + +v1orv2 -> moduleidentity listofdefinitionsv2 : + {v2_mib, ['$1'|lists:reverse('$2')]}. +v1orv2 -> listofdefinitions : {v1_mib, lists:reverse('$1')}. + +definition -> objectidentifier : '$1'. +definition -> objecttypev1 : '$1'. +definition -> newtype : '$1'. +definition -> tableentrydefinition : '$1'. +definition -> traptype : '$1'. + +listofdefinitions -> definition : ['$1'] . +listofdefinitions -> listofdefinitions definition : ['$2' | '$1']. + +import -> '$empty' : []. +import -> 'IMPORTS' imports ';' : '$2'. + +imports -> imports_from_one_mib : ['$1']. +imports -> imports_from_one_mib imports : ['$1' | '$2']. + +imports_from_one_mib -> listofimports 'FROM' variable : + {{val('$3'), lists:reverse('$1')}, line_of('$2')}. + +listofimports -> import_stuff : ['$1']. +listofimports -> listofimports ',' import_stuff : ['$3' | '$1']. + +import_stuff -> 'OBJECT-TYPE' : {builtin, 'OBJECT-TYPE'}. +import_stuff -> 'TRAP-TYPE' : {builtin, 'TRAP-TYPE'}. +import_stuff -> 'NetworkAddress' : {builtin, 'NetworkAddress'}. +import_stuff -> 'TimeTicks' : {builtin, 'TimeTicks'}. +import_stuff -> 'IpAddress' : {builtin, 'IpAddress'}. +import_stuff -> 'Counter' : {builtin, 'Counter'}. +import_stuff -> 'Gauge' : {builtin, 'Gauge'}. +import_stuff -> 'Opaque' : {builtin, 'Opaque'}. +import_stuff -> variable : filter_v2imports(get(snmp_version), val('$1')). +import_stuff -> atom : {node, val('$1')}. +%%v2 +import_stuff -> 'MODULE-IDENTITY' + : ensure_ver(2,'$1'), {builtin, 'MODULE-IDENTITY'}. +import_stuff -> 'NOTIFICATION-TYPE' + : ensure_ver(2,'$1'), {builtin, 'NOTIFICATION-TYPE'}. +import_stuff -> 'MODULE-COMPLIANCE' + : ensure_ver(2,'$1'), {builtin, 'MODULE-COMPLIANCE'}. +import_stuff -> 'NOTIFICATION-GROUP' + : ensure_ver(2,'$1'), {builtin, 'NOTIFICATION-GROUP'}. +import_stuff -> 'OBJECT-GROUP' + : ensure_ver(2,'$1'), {builtin, 'OBJECT-GROUP'}. +import_stuff -> 'OBJECT-IDENTITY' + : ensure_ver(2,'$1'), {builtin, 'OBJECT-IDENTITY'}. +import_stuff -> 'TEXTUAL-CONVENTION' + : ensure_ver(2,'$1'), {builtin, 'TEXTUAL-CONVENTION'}. +import_stuff -> 'DisplayString' + : ensure_ver(2,'$1'), {builtin, 'DisplayString'}. +import_stuff -> 'PhysAddress' + : ensure_ver(2,'$1'), {builtin, 'PhysAddress'}. +import_stuff -> 'MacAddress' + : ensure_ver(2,'$1'), {builtin, 'MacAddress'}. +import_stuff -> 'TruthValue' + : ensure_ver(2,'$1'), {builtin, 'TruthValue'}. +import_stuff -> 'TestAndIncr' + : ensure_ver(2,'$1'), {builtin, 'TestAndIncr'}. +import_stuff -> 'AutonomousType' + : ensure_ver(2,'$1'), {builtin, 'AutonomousType'}. +import_stuff -> 'InstancePointer' + : ensure_ver(2,'$1'), {builtin, 'InstancePointer'}. +import_stuff -> 'VariablePointer' + : ensure_ver(2,'$1'), {builtin, 'VariablePointer'}. +import_stuff -> 'RowPointer' + : ensure_ver(2,'$1'), {builtin, 'RowPointer'}. +import_stuff -> 'RowStatus' + : ensure_ver(2,'$1'), {builtin, 'RowStatus'}. +import_stuff -> 'TimeStamp' + : ensure_ver(2,'$1'), {builtin, 'TimeStamp'}. +import_stuff -> 'TimeInterval' + : ensure_ver(2,'$1'), {builtin, 'TimeInterval'}. +import_stuff -> 'DateAndTime' + : ensure_ver(2,'$1'), {builtin, 'DateAndTime'}. +import_stuff -> 'StorageType' + : ensure_ver(2,'$1'), {builtin, 'StorageType'}. +import_stuff -> 'TDomain' + : ensure_ver(2,'$1'), {builtin, 'TDomain'}. +import_stuff -> 'TAddress' + : ensure_ver(2,'$1'), {builtin, 'TAddress'}. + +traptype -> objectname 'TRAP-TYPE' 'ENTERPRISE' objectname varpart + description referpart implies integer : + Trap = make_trap('$1', '$4', lists:reverse('$5'), + '$6', '$7', val('$9')), + {Trap, line_of('$2')}. + +% defines a name to an internal node. +objectidentifier -> objectname 'OBJECT' 'IDENTIFIER' nameassign : + {Parent, SubIndex} = '$4', + Int = make_internal('$1', dummy, Parent, SubIndex), + {Int, line_of('$2')}. + +% defines name, access and type for a variable. +objecttypev1 -> objectname 'OBJECT-TYPE' + 'SYNTAX' syntax + 'ACCESS' accessv1 + 'STATUS' statusv1 + 'DESCRIPTION' descriptionfield + referpart indexpartv1 defvalpart + nameassign : + Kind = kind('$13', '$12'), + OT = make_object_type('$1', '$4', '$6', '$8', '$10', + '$11', Kind, '$14'), + {OT, line_of('$2')}. + +newtype -> newtypename implies syntax : + NT = make_new_type('$1', dummy, '$3'), + {NT, line_of('$2')}. + +tableentrydefinition -> newtypename implies 'SEQUENCE' '{' fields '}' : + Seq = make_sequence('$1', lists:reverse('$5')), + {Seq, line_of('$3')}. + +% returns: list of {<fieldname>, <asn1_type>} +fields -> fieldname fsyntax : + [{val('$1'), '$2'}]. + +fields -> fields ',' fieldname fsyntax : [{val('$3'), '$4'} | '$1']. + +fsyntax -> 'BITS' : {{bits,[{dummy,0}]},line_of('$1')}. +fsyntax -> syntax : '$1'. + +fieldname -> atom : '$1'. + +syntax -> usertype : {{type, val('$1')}, line_of('$1')}. +syntax -> type : {{type, cat('$1')},line_of('$1')}. +syntax -> type size : {{type_with_size, cat('$1'), '$2'},line_of('$1')}. +syntax -> usertype size : {{type_with_size,val('$1'), '$2'},line_of('$1')}. +syntax -> 'INTEGER' '{' namedbits '}' : + {{integer_with_enum, 'INTEGER', '$3'}, line_of('$1')}. +syntax -> 'BITS' '{' namedbits '}' : + ensure_ver(2,'$1'), + {{bits, '$3'}, line_of('$1')}. +syntax -> 'SEQUENCE' 'OF' usertype : + {{sequence_of,val('$3')},line_of('$1')}. + +size -> '(' sizedescr ')' : make_range('$2'). +size -> '(' 'SIZE' '(' sizedescr ')' ')' : make_range('$4'). + +%% Returns a list of integers describing a range. +sizedescr -> range_num '.' '.' range_num : ['$1', '$4']. +sizedescr -> range_num '.' '.' range_num sizedescr :['$1', '$4' |'$5']. +sizedescr -> range_num : ['$1']. +sizedescr -> sizedescr '|' sizedescr : ['$1', '$3']. + +range_num -> integer : val('$1') . +range_num -> quote atom : make_range_integer(val('$1'), val('$2')) . +range_num -> quote variable : make_range_integer(val('$1'), val('$2')) . + +namedbits -> atom '(' integer ')' : [{val('$1'), val('$3')}]. +namedbits -> namedbits ',' atom '(' integer ')' : + [{val('$3'), val('$5')} | '$1']. + +usertype -> variable : '$1'. + +type -> 'OCTET' 'STRING' : {'OCTET STRING', line_of('$1')}. +type -> 'BIT' 'STRING' : {'BIT STRING', line_of('$1')}. +type -> 'OBJECT' 'IDENTIFIER' : {'OBJECT IDENTIFIER', line_of('$1')}. +type -> 'INTEGER' : '$1'. +type -> 'NetworkAddress' : '$1'. +type -> 'IpAddress' : '$1'. +type -> 'Counter' : ensure_ver(1,'$1'),'$1'. +type -> 'Gauge' : ensure_ver(1,'$1'),'$1'. +type -> 'TimeTicks' : '$1'. +type -> 'Opaque' : '$1'. +type -> 'DisplayString' : ensure_ver(2,'$1'), '$1'. +type -> 'PhysAddress' : ensure_ver(2,'$1'), '$1'. +type -> 'MacAddress' : ensure_ver(2,'$1'), '$1'. +type -> 'TruthValue' : ensure_ver(2,'$1'), '$1'. +type -> 'TestAndIncr' : ensure_ver(2,'$1'), '$1'. +type -> 'AutonomousType' : ensure_ver(2,'$1'), '$1'. +type -> 'InstancePointer' : ensure_ver(2,'$1'), '$1'. +type -> 'VariablePointer' : ensure_ver(2,'$1'), '$1'. +type -> 'RowPointer' : ensure_ver(2,'$1'), '$1'. +type -> 'RowStatus' : ensure_ver(2,'$1'), '$1'. +type -> 'TimeStamp' : ensure_ver(2,'$1'), '$1'. +type -> 'TimeInterval' : ensure_ver(2,'$1'), '$1'. +type -> 'DateAndTime' : ensure_ver(2,'$1'), '$1'. +type -> 'StorageType' : ensure_ver(2,'$1'), '$1'. +type -> 'TDomain' : ensure_ver(2,'$1'), '$1'. +type -> 'TAddress' : ensure_ver(2,'$1'), '$1'. + +% Returns: {FatherName, SubIndex} (the parent) +nameassign -> implies '{' fatherobjectname parentintegers '}' : {'$3', '$4' }. +nameassign -> implies '{' parentintegers '}' : { root, '$3'}. + + +varpart -> '$empty' : []. +varpart -> 'VARIABLES' '{' variables '}' : '$3'. +variables -> objectname : ['$1']. +variables -> variables ',' objectname : ['$3' | '$1']. + +implies -> '::=' : '$1'. +implies -> ':' ':' '=' : w("Sloppy asignment on line ~p", [line_of('$1')]), '$1'. +descriptionfield -> string : lists:reverse(val('$1')). +descriptionfield -> '$empty' : undefined. +description -> 'DESCRIPTION' string : lists:reverse(val('$2')). +description -> '$empty' : undefined. + +displaypart -> 'DISPLAY-HINT' string : display_hint('$2') . +displaypart -> '$empty' : undefined . + +% returns: {indexes, undefined} +% | {indexes, IndexList} where IndexList is a list of aliasnames. +indexpartv1 -> 'INDEX' '{' indextypesv1 '}' : {indexes, lists:reverse('$3')}. +indexpartv1 -> '$empty' : {indexes, undefined}. + +indextypesv1 -> indextypev1 : ['$1']. +indextypesv1 -> indextypesv1 ',' indextypev1 : ['$3' | '$1']. + +indextypev1 -> index : '$1'. + +index -> objectname : '$1'. + +parentintegers -> integer : [val('$1')]. +parentintegers -> atom '(' integer ')' : [val('$3')]. +parentintegers -> integer parentintegers : [val('$1') | '$2']. +parentintegers -> atom '(' integer ')' parentintegers : [val('$3') | '$5']. + +defvalpart -> 'DEFVAL' '{' integer '}' : {defval, val('$3')}. +defvalpart -> 'DEFVAL' '{' atom '}' : {defval, val('$3')}. +defvalpart -> 'DEFVAL' '{' '{' defbitsvalue '}' '}' : {defval, '$4'}. +defvalpart -> 'DEFVAL' '{' quote atom '}' + : {defval, make_defval_for_string(line_of('$1'), lists:reverse(val('$3')), + val('$4'))}. +defvalpart -> 'DEFVAL' '{' quote variable '}' + : {defval, make_defval_for_string(line_of('$1'), lists:reverse(val('$3')), + val('$4'))}. +defvalpart -> 'DEFVAL' '{' string '}' + : {defval, lists:reverse(val('$3'))}. +defvalpart -> '$empty' : undefined. + +defbitsvalue -> defbitsnames : '$1'. +defbitsvalue -> '$empty' : []. + +defbitsnames -> atom : [val('$1')]. +defbitsnames -> defbitsnames ',' atom : [val('$3') | '$1']. + +objectname -> atom : val('$1'). +mibname -> variable : val('$1'). +fatherobjectname -> objectname : '$1'. +newtypename -> variable : val('$1'). + +accessv1 -> atom: accessv1('$1'). + +statusv1 -> atom : statusv1('$1'). + +referpart -> 'REFERENCE' string : lists:reverse(val('$2')). +referpart -> '$empty' : undefined. + + +%%---------------------------------------------------------------------- +%% SNMPv2 grammatics +%%v2 +%%---------------------------------------------------------------------- +moduleidentity -> mibid 'MODULE-IDENTITY' + 'LAST-UPDATED' last_updated + 'ORGANIZATION' oranization + 'CONTACT-INFO' contact_info + 'DESCRIPTION' descriptionfield + revisionpart nameassign : + MI = make_module_identity('$1', '$4', '$6', '$8', + '$10', '$11', '$12'), + {MI, line_of('$2')}. + +mibid -> atom : val('$1'). +last_updated -> string : lists:reverse(val('$1')) . +oranization -> string : lists:reverse(val('$1')) . +contact_info -> string : lists:reverse(val('$1')) . + +revisionpart -> '$empty' : [] . +revisionpart -> revisions : lists:reverse('$1') . + +revisions -> revision : ['$1'] . +revisions -> revisions revision : ['$2' | '$1'] . +revision -> 'REVISION' revision_string 'DESCRIPTION' revision_desc : + make_revision('$2', '$4') . + +revision_string -> string : lists:reverse(val('$1')) . +revision_desc -> string : lists:reverse(val('$1')) . + +definitionv2 -> objectidentifier : '$1'. +definitionv2 -> objecttypev2 : '$1'. +definitionv2 -> textualconvention : '$1'. +definitionv2 -> objectidentity : '$1'. +definitionv2 -> newtype : '$1'. +definitionv2 -> tableentrydefinition : '$1'. +definitionv2 -> notification : '$1'. +definitionv2 -> objectgroup : '$1'. +definitionv2 -> notificationgroup : '$1'. +definitionv2 -> modulecompliance : '$1'. + +listofdefinitionsv2 -> '$empty' : [] . +listofdefinitionsv2 -> listofdefinitionsv2 definitionv2 : ['$2' | '$1']. + +textualconvention -> newtypename implies 'TEXTUAL-CONVENTION' displaypart + 'STATUS' statusv2 description referpart 'SYNTAX' syntax : + NT = make_new_type('$1', 'TEXTUAL-CONVENTION', '$4', + '$6', '$7', '$8', '$10'), + {NT, line_of('$3')}. + +objectidentity -> objectname 'OBJECT-IDENTITY' 'STATUS' statusv2 + 'DESCRIPTION' string referpart nameassign : + {Parent, SubIndex} = '$8', + Int = make_internal('$1', 'OBJECT-IDENTITY', + Parent, SubIndex), + {Int, line_of('$2')}. + +objectgroup -> objectname 'OBJECT-GROUP' objectspart + 'STATUS' statusv2 description referpart nameassign : + OG = make_object_group('$1', '$3', '$5', '$6', '$7', '$8'), + {OG, line_of('$2')}. + +notificationgroup -> objectname 'NOTIFICATION-GROUP' 'NOTIFICATIONS' '{' + objects '}' 'STATUS' statusv2 description referpart + nameassign : + NG = make_notification_group('$1', '$5', '$8', '$9', + '$10', '$11'), + {NG, line_of('$2')}. + +modulecompliance -> objectname 'MODULE-COMPLIANCE' 'STATUS' statusv2 + description referpart modulepart nameassign : + MC = make_module_compliance('$1', '$4', '$5', '$6', + '$7', '$8'), + {MC, line_of('$2')}. + +modulepart -> '$empty'. +modulepart -> modules. + +modules -> module. +modules -> modules module. + +module -> 'MODULE' modulenamepart mandatorypart compliancepart. + +modulenamepart -> mibname. +modulenamepart -> '$empty'. + +mandatorypart -> 'MANDATORY-GROUPS' '{' objects '}'. +mandatorypart -> '$empty'. + +compliancepart -> compliances. +compliancepart -> '$empty'. + +compliances -> compliance. +compliances -> compliances compliance. + +compliance -> compliancegroup. +compliance -> object. + +compliancegroup -> 'GROUP' objectname description. + +object -> 'OBJECT' objectname syntaxpart writesyntaxpart accesspart description. + +syntaxpart -> 'SYNTAX' syntax. +syntaxpart -> '$empty'. + +writesyntaxpart -> 'WRITE-SYNTAX' syntax. +writesyntaxpart -> '$empty'. + +accesspart -> 'MIN-ACCESS' accessv2. +accesspart -> '$empty'. + +objecttypev2 -> objectname 'OBJECT-TYPE' + 'SYNTAX' syntax + unitspart + 'MAX-ACCESS' accessv2 + 'STATUS' statusv2 + 'DESCRIPTION' descriptionfield + referpart indexpartv2 defvalpart + nameassign : + Kind = kind('$14', '$13'), + OT = make_object_type('$1', '$4', '$5', '$7', '$9', + '$11', '$12', Kind, '$15'), + {OT, line_of('$2')}. + +indexpartv2 -> 'INDEX' '{' indextypesv2 '}' : {indexes, lists:reverse('$3')}. +indexpartv2 -> 'AUGMENTS' '{' entry '}' : {augments, '$3'}. +indexpartv2 -> '$empty' : {indexes, undefined}. + +indextypesv2 -> indextypev2 : ['$1']. +indextypesv2 -> indextypesv2 ',' indextypev2 : ['$3' | '$1']. + +indextypev2 -> 'IMPLIED' index : {implied,'$2'}. +indextypev2 -> index : '$1'. + +entry -> objectname : '$1'. + +unitspart -> '$empty' : undefined. +unitspart -> 'UNITS' string : units('$2') . + +statusv2 -> atom : statusv2('$1'). + +accessv2 -> atom: accessv2('$1'). + +notification -> objectname 'NOTIFICATION-TYPE' objectspart + 'STATUS' statusv2 'DESCRIPTION' descriptionfield referpart + nameassign : + Not = make_notification('$1','$3','$5', '$7', '$8', '$9'), + {Not, line_of('$2')}. + +objectspart -> 'OBJECTS' '{' objects '}' : lists:reverse('$3'). +objectspart -> '$empty' : []. + +objects -> objectname : ['$1']. +objects -> objects ',' objectname : ['$3'|'$1']. + +%%---------------------------------------------------------------------- +Erlang code. +%%---------------------------------------------------------------------- + +-include("snmp_types.hrl"). +-include("snmpc_lib.hrl"). +-include("snmpc.hrl"). + +% value +val(Token) -> element(3, Token). + +line_of(Token) -> element(2, Token). + +%% category +cat(Token) -> element(1, Token). + +statusv1(Tok) -> + case val(Tok) of + mandatory -> mandatory; + optional -> optional; + obsolete -> obsolete; + deprecated -> deprecated; + Else -> return_error(line_of(Tok), + "syntax error before: " ++ atom_to_list(Else)) + end. + +statusv2(Tok) -> + case val(Tok) of + current -> current; + deprecated -> deprecated; + obsolete -> obsolete; + Else -> return_error(line_of(Tok), + "syntax error before: " ++ atom_to_list(Else)) + end. + +accessv1(Tok) -> + case val(Tok) of + 'read-only' -> 'read-only'; + 'read-write' -> 'read-write'; + 'write-only' -> 'write-only'; + 'not-accessible' -> 'not-accessible'; + Else -> return_error(line_of(Tok), + "syntax error before: " ++ atom_to_list(Else)) + end. + +accessv2(Tok) -> + case val(Tok) of + 'not-accessible' -> 'not-accessible'; + 'accessible-for-notify' -> 'accessible-for-notify'; + 'read-only' -> 'read-only'; + 'read-write' -> 'read-write'; + 'read-create' -> 'read-create'; + Else -> return_error(line_of(Tok), + "syntax error before: " ++ atom_to_list(Else)) + end. + +%% --------------------------------------------------------------------- +%% Various basic record build functions +%% --------------------------------------------------------------------- + +make_module_identity(Name, LU, Org, CI, Desc, Revs, NA) -> + #mc_module_identity{name = Name, + last_updated = LU, + organization = Org, + contact_info = CI, + description = Desc, + revisions = Revs, + name_assign = NA}. + +make_revision(Rev, Desc) -> + #mc_revision{revision = Rev, + description = Desc}. + +make_object_type(Name, Syntax, MaxAcc, Status, Desc, Ref, Kind, NA) -> + #mc_object_type{name = Name, + syntax = Syntax, + max_access = MaxAcc, + status = Status, + description = Desc, + reference = Ref, + kind = Kind, + name_assign = NA}. + +make_object_type(Name, Syntax, Units, MaxAcc, Status, Desc, Ref, Kind, NA) -> + #mc_object_type{name = Name, + syntax = Syntax, + units = Units, + max_access = MaxAcc, + status = Status, + description = Desc, + reference = Ref, + kind = Kind, + name_assign = NA}. + +make_new_type(Name, Macro, Syntax) -> + #mc_new_type{name = Name, + macro = Macro, + syntax = Syntax}. + +make_new_type(Name, Macro, DisplayHint, Status, Desc, Ref, Syntax) -> + #mc_new_type{name = Name, + macro = Macro, + status = Status, + description = Desc, + reference = Ref, + display_hint = DisplayHint, + syntax = Syntax}. + +make_trap(Name, Ent, Vars, Desc, Ref, Num) -> + #mc_trap{name = Name, + enterprise = Ent, + vars = Vars, + description = Desc, + reference = Ref, + num = Num}. + +make_notification(Name, Vars, Status, Desc, Ref, NA) -> + #mc_notification{name = Name, + vars = Vars, + status = Status, + description = Desc, + reference = Ref, + name_assign = NA}. + +make_module_compliance(Name, Status, Desc, Ref, Mod, NA) -> + #mc_module_compliance{name = Name, + status = Status, + description = Desc, + reference = Ref, + module = Mod, + name_assign = NA}. + +make_object_group(Name, Objs, Status, Desc, Ref, NA) -> + #mc_object_group{name = Name, + objects = Objs, + status = Status, + description = Desc, + reference = Ref, + name_assign = NA}. + +make_notification_group(Name, Objs, Status, Desc, Ref, NA) -> + #mc_notification_group{name = Name, + objects = Objs, + status = Status, + description = Desc, + reference = Ref, + name_assign = NA}. + +make_sequence(Name, Fields) -> + #mc_sequence{name = Name, + fields = Fields}. + +make_internal(Name, Macro, Parent, SubIdx) -> + #mc_internal{name = Name, + macro = Macro, + parent = Parent, + sub_index = SubIdx}. + + + +%% --------------------------------------------------------------------- + + +%%---------------------------------------------------------------------- +%% Purpose: Find how much room needs to be allocated for the data type +%% (when sending it in a PDU (the maximum difference will be +%% the size allocated)). +%% This is applicable for OCTET STRINGs and OBJECT IDENTIFIERs. +%% +%% Or : Find the range of integers in the integer list. +%% This is applicable for INTEGERs +%% +%% Arg: A list of integers. +%%---------------------------------------------------------------------- + +make_range_integer(RevHexStr, h) -> + erlang:list_to_integer(lists:reverse(RevHexStr), 16); +make_range_integer(RevHexStr, 'H') -> + erlang:list_to_integer(lists:reverse(RevHexStr), 16); +make_range_integer(RevBitStr, b) -> + erlang:list_to_integer(lists:reverse(RevBitStr), 2); +make_range_integer(RevBitStr, 'B') -> + erlang:list_to_integer(lists:reverse(RevBitStr), 2); +make_range_integer(RevStr, Base) -> + throw({error, {invalid_base, Base, lists:reverse(RevStr)}}). + +make_range(XIntList) -> + IntList = lists:flatten(XIntList), + {range, lists:min(IntList), lists:max(IntList)}. + +make_defval_for_string(Line, Str, Atom) -> + case lists:member(Atom, [h, 'H', b, 'B']) of + true -> + case catch make_defval_for_string2(Str, Atom) of + Defval when is_list(Defval) -> + Defval; + {error, ErrStr} -> + snmpc_lib:print_error("Bad DEFVAL ~w string ~p - ~s", + [Atom, Str, ErrStr], + Line), + ""; + _Else -> + snmpc_lib:print_error("Bad DEFVAL ~w string ~p", + [Atom, Str], + Line), + "" + end; + false -> + snmpc_lib:print_error("Bad DEFVAL string type ~w for ~p", + [Atom, Str], + Line), + "" + end. + + +make_defval_for_string2([], h) -> []; +make_defval_for_string2([X16,X|HexString], h) -> + lists:append(hex_to_bytes(snmpc_misc:to_upper([X16,X])), + make_defval_for_string2(HexString, h)); +make_defval_for_string2([_Odd], h) -> + throw({error, "odd number of bytes in hex string"}); +make_defval_for_string2(HexString, 'H') -> + make_defval_for_string2(HexString,h); + +make_defval_for_string2(BitString, 'B') -> + bits_to_bytes(BitString); +make_defval_for_string2(BitString, b) -> + make_defval_for_string2(BitString, 'B'). + +bits_to_bytes(BitStr) -> + lists:reverse(bits_to_bytes(lists:reverse(BitStr), 1, 0)). + +bits_to_bytes([], 1, _Byte) -> % empty bitstring + []; +bits_to_bytes([], 256, _Byte) -> % correct; multiple of 8 + []; +% If we are to support arbitrary length of bitstrings. This migth +% be needed in the new SMI. +%bits_to_bytes([], N, Byte) -> +% [Byte]; +bits_to_bytes([], _N, _Byte) -> + throw({error, "not a multiple of eight bits in bitstring"}); +bits_to_bytes(Rest, 256, Byte) -> + [Byte | bits_to_bytes(Rest, 1, 0)]; +bits_to_bytes([$1 | T], N, Byte) -> + bits_to_bytes(T, N*2, N + Byte); +bits_to_bytes([$0 | T], N, Byte) -> + bits_to_bytes(T, N*2, Byte); +bits_to_bytes([_BadChar | _T], _N, _Byte) -> + throw({error, "bad character in bit string"}). + +%%---------------------------------------------------------------------- +%% These HEX conversion routines are stolen from module asn1_bits by +%% I didn't want to ship the entire asn1-compiler so I used cut-and-paste. +%%---------------------------------------------------------------------- + +%% hex_to_bytes(HexNumber) when is_atom(HexNumber) -> +%% hex_to_bytes(atom_to_list(HexNumber)); + +hex_to_bytes(HexNumber) -> + case length(HexNumber) rem 2 of + 1 -> %% Odd + hex_to_bytes(lists:append(HexNumber,[$0]),[]); + 0 -> %% even + hex_to_bytes(HexNumber,[]) + end. + +hex_to_bytes([],R) -> + lists:reverse(R); +hex_to_bytes([Hi,Lo|Rest],Res) -> + hex_to_bytes(Rest,[hex_to_byte(Hi,Lo)|Res]). + +hex_to_four_bits(Hex) -> + if + Hex == $0 -> 0; + Hex == $1 -> 1; + Hex == $2 -> 2; + Hex == $3 -> 3; + Hex == $4 -> 4; + Hex == $5 -> 5; + Hex == $6 -> 6; + Hex == $7 -> 7; + Hex == $8 -> 8; + Hex == $9 -> 9; + Hex == $A -> 10; + Hex == $B -> 11; + Hex == $C -> 12; + Hex == $D -> 13; + Hex == $E -> 14; + Hex == $F -> 15; + true -> throw({error, "bad hex character"}) + end. + +hex_to_byte(Hi,Lo) -> + (hex_to_four_bits(Hi) bsl 4) bor hex_to_four_bits(Lo). + +kind(DefValPart,IndexPart) -> + case DefValPart of + undefined -> + case IndexPart of + {indexes, undefined} -> {variable, []}; + {indexes, Indexes} -> + {table_entry, {indexes, Indexes}}; + {augments,Table} -> + {table_entry,{augments,Table}} + end; + {defval, DefVal} -> {variable, [{defval, DefVal}]} + end. + +display_hint(Val) -> + case val(Val) of + Str when is_list(Str) -> + lists:reverse(Str); + _ -> + throw({error, {invalid_display_hint, Val}}) + end. + +units(Val) -> + case val(Val) of + Str when is_list(Str) -> + lists:reverse(Str); + _ -> + throw({error, {invalid_units, Val}}) + end. + +ensure_ver(Ver, Line, What) -> + case get(snmp_version) of + Ver -> ok; + _Other -> + snmpc_lib:print_error( + "~s is only allowed in SNMPv~p.",[What,Ver],Line) + end. + + +ensure_ver(Ver,Token) -> + ensure_ver(Ver,line_of(Token), atom_to_list(cat(Token))). + +filter_v2imports(2,'Integer32') -> {builtin, 'Integer32'}; +filter_v2imports(2,'Counter32') -> {builtin, 'Counter32'}; +filter_v2imports(2,'Gauge32') -> {builtin, 'Gauge32'}; +filter_v2imports(2,'Unsigned32') -> {builtin, 'Unsigned32'}; +filter_v2imports(2,'Counter64') -> {builtin, 'Counter64'}; +filter_v2imports(_,Type) -> {type, Type}. + +w(F, A) -> + ?vwarning(F, A). + +%i(F, A) -> +% io:format("~w:" ++ F ++ "~n", [?MODULE|A]). + diff --git a/lib/snmp/src/compile/snmpc_mib_to_hrl.erl b/lib/snmp/src/compile/snmpc_mib_to_hrl.erl new file mode 100644 index 0000000000..07bd29231b --- /dev/null +++ b/lib/snmp/src/compile/snmpc_mib_to_hrl.erl @@ -0,0 +1,391 @@ +%% +%% %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(snmpc_mib_to_hrl). + +-include_lib("stdlib/include/erl_compile.hrl"). +-include("snmp_types.hrl"). +-include("snmpc_lib.hrl"). + +%% External exports +-export([convert/1, 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). + + + + diff --git a/lib/snmp/src/compile/snmpc_misc.erl b/lib/snmp/src/compile/snmpc_misc.erl new file mode 100644 index 0000000000..557f3e0f6b --- /dev/null +++ b/lib/snmp/src/compile/snmpc_misc.erl @@ -0,0 +1,173 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-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(snmpc_misc). + +%% need definition of mib record +-include("snmp_types.hrl"). +-include("snmpc_misc.hrl"). + + +-export([assq/2, + bits_to_int/2, + ensure_trailing_dir_delimiter/1, + foreach/3, + is_string/1, + read_mib/1, + read_noexit/2, + strip_extension_from_filename/2, + to_upper/1]). + + +%%-------------------------------------------------- +%% Not a real assq, but what the heck, it's useful. +%%-------------------------------------------------- +assq(Key, List) -> + case lists:keysearch(Key, 1, List) of + {value, {Key, Val}} -> {value, Val}; + _ -> false + end. + + +%%---------------------------------------------------------------------- +%% Converts a list of named bits to the integer value. +%% Returns: integer()|error +%%---------------------------------------------------------------------- +bits_to_int(Val,Kibbles) -> + bits_to_int(Val,Kibbles,0). + +bits_to_int([],_Kibbles,Res) -> Res; +bits_to_int([Kibble|Ks],Kibbles,Res) -> + case snmp_misc:assq(Kibble,Kibbles) of + {value,V} -> + bits_to_int(Ks,Kibbles,Res + round(math:pow(2,V))); + _ -> + error + end. + + +ensure_trailing_dir_delimiter([]) -> "/"; +ensure_trailing_dir_delimiter(DirSuggestion) -> + case lists:last(DirSuggestion) of + $/ -> DirSuggestion; + _ -> lists:append(DirSuggestion,"/") + end. + + +strip_extension_from_filename(FileName, Ext) when is_atom(FileName) -> + strip_extension_from_filename(atom_to_list(FileName), Ext); + +strip_extension_from_filename(FileName, Ext) when is_list(FileName) -> + case lists:suffix(Ext, FileName) of + true -> lists:sublist(FileName, 1, length(FileName) - length(Ext)); + false -> FileName + end. + + +to_upper([C|Cs]) when (C >= $a) andalso (C =< $z) -> [C-($a-$A)|to_upper(Cs)]; +to_upper([C|Cs]) -> [C|to_upper(Cs)]; +to_upper([]) -> []. + + +is_string([]) -> true; +is_string([Tkn | Str]) + when is_integer(Tkn) andalso (Tkn >= 0) andalso (Tkn =< 255) -> + is_string(Str); +is_string(_) -> false. + + +foreach(Function, ExtraArgs, [H | T]) -> + apply(Function, [H | ExtraArgs]), + foreach(Function, ExtraArgs, T); +foreach(_Function, _ExtraArgs, []) -> + true. + + + +%%---------------------------------------------------------------------- +%% Returns: {ok, Mib}|{error, Reason} +%% The reason for having the function if this module is: +%% The compiler package and the agent package are separated, this is +%% the only common module. +%%---------------------------------------------------------------------- +read_mib(FileName) -> + (catch do_read_mib(FileName)). + +do_read_mib(FileName) -> + ?read_mib(FileName). + + +%% Ret. {ok, Res} | {error, Line, Error} | {error, open_file} +read_noexit(File, CheckFunc) -> + case file:open(File, [read]) of + {ok, Fd} -> + case loop(Fd, [], CheckFunc, 1, File) of + {error, Line, R} -> + file:close(Fd), + {error, Line, R}; + Res -> + file:close(Fd), + {ok, Res} + end; + _Error -> + {error, open_file} + end. + + +%%----------------------------------------------------------------- +%% Ret: {error, Line, Reason} | Row +%%----------------------------------------------------------------- +loop(Fd, Res, Func, StartLine, File) -> + case read(Fd, "", StartLine) of + {ok, Row, EndLine} -> + case (catch apply(Func, [Row])) of + {ok, NewRow} -> + loop(Fd, [NewRow | Res], Func, EndLine, File); + true -> + loop(Fd, [Row | Res], Func, EndLine, File); + Error -> + {error, EndLine, Error} + end; + {error, EndLine, Error} -> + {error, EndLine, Error}; + eof -> + Res + end. + + +%%----------------------------------------------------------------- +%% io:read modified to give us line numbers. +%%----------------------------------------------------------------- +read(Io, Prompt, StartLine) -> + case io:request(Io, {get_until, Prompt, erl_scan, tokens, [StartLine]}) of + {ok, Toks, EndLine} -> + case erl_parse:parse_term(Toks) of + {ok, Term} -> + {ok, Term, EndLine}; + {error, {Line, erl_parse, Error}} -> + {error, Line, {parse_error, Error}} + end; + {error, E, EndLine} -> + {error, EndLine, E}; + {eof, _EndLine} -> + eof; + Other -> + Other + end. + diff --git a/lib/snmp/src/compile/snmpc_misc.hrl b/lib/snmp/src/compile/snmpc_misc.hrl new file mode 100644 index 0000000000..c29f2eb9e5 --- /dev/null +++ b/lib/snmp/src/compile/snmpc_misc.hrl @@ -0,0 +1,74 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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% +%% + +-define(verify_format_version(VFV_Ver1,VFV_Ver2), + fun(VFV_V,VFV_V) -> + ok; + (VFV_V1,VFV_V2) when is_list(VFV_V1) andalso is_list(VFV_V2) -> + Toks1 = string:tokens(VFV_V1, [$.]), + [Major1|_] = case (catch [list_to_integer(I) || I <- Toks1]) of + Nums when is_list(Nums) -> + Nums; + _ -> + {error, {invalid_version_format, VFV_V1}} + end, + Toks2 = string:tokens(VFV_V2, [$.]), + case (catch [list_to_integer(I) || I <- Toks2]) of + [Major1|_] -> + ok; + [_Major2|_] -> + {error, wrong_version}; + _ -> + {error, {invalid_version_format, VFV_V2}} + end; + (VFV_V1,VFV_V2) -> + {error, {invalid_format, VFV_V1, VFV_V2}} + end(VFV_Ver1,VFV_Ver2)). + +-define(read_mib(RM_FN), + RM_Bin = case file:read_file(RM_FN) of + {ok, RM_B} -> + RM_B; + RM_Error -> + throw(RM_Error) + end, + RM_Mib = case (catch binary_to_term(RM_Bin)) of + RM_M when is_record(RM_M, mib) -> + RM_M; + _ -> + throw({error, bad_mib_format}) + end, + #mib{mib_format_version = RM_V1} = #mib{}, + case RM_Mib of + #mib{mib_format_version = RM_V2, + misc = RM_X} when is_integer(RM_X) -> + case (catch ?verify_format_version(RM_V1, RM_V2)) of + ok -> + {ok, RM_Mib#mib{misc = []}}; + _ -> + throw({error, {wrong_mib_format_version_tag, RM_V2}}) + end; + #mib{mib_format_version = RM_V2} -> + case (catch ?verify_format_version(RM_V1, RM_V2)) of + ok -> + {ok, RM_Mib#mib{misc = []}}; + _ -> + throw({error, {wrong_mib_format_version_tag, RM_V2}}) + end + end). diff --git a/lib/snmp/src/compile/snmpc_tok.erl b/lib/snmp/src/compile/snmpc_tok.erl new file mode 100644 index 0000000000..6b99e7ae43 --- /dev/null +++ b/lib/snmp/src/compile/snmpc_tok.erl @@ -0,0 +1,357 @@ +%% +%% %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(snmpc_tok). + +%% c(snmpc_tok). + +%%---------------------------------------------------------------------- +%% Generic (?) Tokenizer. +%%---------------------------------------------------------------------- +%% Token: {Category, Line, Value}|{Category, Line} +%% (Category == <KeyWord> ==> 2-tuple (otherwise 3-tuple) +%% Category: integer | quote | string +%% | variable | atom | <keyword> | <single_char> +%%---------------------------------------------------------------------- + +%% API +-export([start_link/2, stop/1, get_token/1, get_all_tokens/1, tokenize/2]). + +%% Internal exports +-export([null_get_line/0, format_error/1, terminate/2, handle_call/3, init/1, + test/0]). + + +%%---------------------------------------------------------------------- +%% Reserved_words: list of KeyWords. Example: ['IF', 'BEGIN', ..., 'GOTO'] +%% Options: list of Option +%% Option: {file, <filename>}, +%% or: {get_line_function, {Mod, Func, Arg} (default io, get_line, [Fid])}, +%% get_line_function shall behave as io:get_line. +%% {print_lineno, L} +%% Returns: {ok, Pid} | {error, Reason} +%%---------------------------------------------------------------------- +start_link(Reserved_words, Options) -> + case lists:keysearch(file, 1, Options) of + {value, {file, Filename}} -> + case file:open(Filename, [read]) of + {ok, Fid} -> + gen_server:start_link(?MODULE, + {Reserved_words, Options, + {io, get_line, [Fid, prompt]}}, []); + Error -> + Str = format_error({"Cannot open file '~s' (~800p).~n", + [Filename, Error]}), + {error,Str} + end; + false -> + MFA = case lists:keysearch(get_line_function, 1, Options) of + {value, {get_line_function, {M, F, A}}} -> + {M,F,A}; + false -> {?MODULE, null_get_line, []} + end, + gen_server:start_link(?MODULE,{Reserved_words,Options,MFA}, []) + end. + +%%-------------------------------------------------- +%% Returns: +%% {ok, [Token], LineNo} | {eof, LineNo} | {error, Error_description, Endline} +%% For more information, see manual page for yecc (and its requirements on a +%% tokenizer). +%%-------------------------------------------------- +get_token(TokPid) -> + V = gen_server:call(TokPid, get_token, infinity), + %% io:format("tok:~w~n", [V]), + V. + +get_all_tokens(TokPid) -> + V = gen_server:call(TokPid, get_all_tokens, infinity), + %% io:format("tok:~w~n", [V]), + V. + + + +%%-------------------------------------------------- +%% Returns: {ok, Tokens, EndLine} | {error, Error_description, Endline} +%% Comment: Tokeniser must be started since all options reside in +%% the process dictionary of the tokeniser process. +%%-------------------------------------------------- +tokenize(TokPid, String) -> + gen_server:call(TokPid, {tokenize, String}, infinity). + +stop(TokPid) -> + gen_server:call(TokPid,stop, infinity). + +%%---------------------------------------------------------------------- +%% Implementation +%%---------------------------------------------------------------------- +insert_keywords_into_ets(_DB, []) -> done; +insert_keywords_into_ets(DB, [Word | T]) -> + ets:insert(DB, {Word, reserved_word}), + insert_keywords_into_ets(DB, T). + +reserved_word(X) -> + case ets:lookup(get(db), X) of + [{X, reserved_word}] -> + true; + _ -> + false + end. + +%% If you only need to tokenize strings +null_get_line() -> eof. + +format_error({Format, Data}) -> + io_lib:format(lists:append("Tokeniser error: ", Format), Data). + +test() -> + start_link(['AUGMENTS','BEGIN','CONTACT-INFO','DEFINITIONS','DEFVAL', + 'DESCRIPTION','DISPLAY-HINT','END','IDENTIFIER','IMPLIED', + 'INDEX','INTEGER','LAST-UPDATED','MAX-ACCESS','MODULE-IDENTITY', + 'NOTIFICATION-TYPE','OBJECT','OBJECT-IDENTITY','OBJECT-TYPE', + 'OBJECTS','ORGANIZATION','REFERENCE','REVISION', + 'SIZE','STATUS','SYNTAX','TEXTUAL-CONVENTION','UNITS', + 'current','deprecated','not-accessible','obsolete', + 'read-create','read-only','read-write', 'IMPORTS', 'FROM', + 'MODULE-COMPLIANCE', + 'DisplayString', + 'PhysAddress', + 'MacAddress', + 'TruthValue', + 'TestAndIncr', + 'AutonomousType', + 'InstancePointer', + 'VariablePointer', + 'RowPointer', + 'RowStatus', + 'TimeStamp', + 'TimeInterval', + 'DateAndTime', + 'StorageType', + 'TDomain', + 'TAddress'], + [{file, "modemmib.mib"}]). + +init({Reserved_words, Options, GetLineMFA}) -> + put(get_line_function,GetLineMFA), + put(print_lineno, + case lists:keysearch(print_lineno, 1, Options) of + {value, {print_lineno, L}} -> L; + false -> undefined + end), + DB = ets:new(reserved_words, [set, private]), + insert_keywords_into_ets(DB, Reserved_words), + put(db, DB), + put(line, 0), + {ok, ""}. + +getLine() -> + OldLine = put(line, 1 + get(line)), + {M,F,A} = get(get_line_function), + case get(print_lineno) of + undefined -> true; + X -> case OldLine rem X of + 0 -> io:format('~w..',[OldLine]); + _ -> true + end + end, + apply(M,F,A). + +%% You can only do this when no file is open. +handle_call({tokenizeString, String}, _From, "") -> + {reply, safe_tokenize_whole_string(String), ""}; + +handle_call(get_token, _From, String) -> + {ReplyToken, RestChars} = safe_tokenise(String), + {reply, ReplyToken, RestChars}; + +handle_call(get_all_tokens, _From, String) -> + Toks = get_all_tokens(String,[]), + {reply, Toks, []}; + + +handle_call(stop, _From, String) -> + {stop, normal, ok, String}. + +terminate(_Reason, _State) -> + ok. + +%% ErrorInfo = {ErrorLine, Module, ErrorDescriptor} +%% will be used as +%% apply(Module, format_error, [ErrorDescriptor]). shall return a string. + +%% Returns a reply +tokenize_whole_string(eof) -> []; +tokenize_whole_string(String) -> + {Token, RestChars} = tokenise(String), + [Token | tokenize_whole_string(RestChars)]. + +safe_tokenize_whole_string(String) -> + case catch tokenize_whole_string(String) of + {error, ErrorInfo} -> {error, ErrorInfo, get(line)}; + Tokens -> {ok, Tokens, get(line)} + end. + +%% throw({error, {get(line), ?MODULE, "Unexpected eof~n"}}). + +%% Returns: {ReplyToken, NewState} +safe_tokenise(eof) -> {{eof, get(line)}, eof}; +safe_tokenise(Chars) when is_list(Chars) -> + case catch tokenise(Chars) of + {error, ErrorInfo} -> {{error, ErrorInfo, get(line)}, {[], eof}}; + {Token, RestChars} when is_tuple(Token) -> + {{ok, [Token], get(line)}, RestChars} + end. + +get_all_tokens(eof,Toks) -> + lists:reverse(Toks); +get_all_tokens(Str,Toks) -> + case catch tokenise(Str) of + {error, ErrorInfo} -> {error, ErrorInfo}; + {Token, RestChars} when is_tuple(Token) -> + get_all_tokens(RestChars, [Token|Toks]) + end. + + + +%%-------------------------------------------------- +%% Returns: {Token, Rest} +%%-------------------------------------------------- +tokenise([H|T]) when ($a =< H) andalso (H =< $z) -> + get_name(atom, [H], T); + +tokenise([H|T]) when ($A =< H) andalso (H =< $Z) -> + get_name(variable, [H], T); + +tokenise([$:,$:,$=|T]) -> + {{'::=', get(line)}, T}; + +tokenise([$-,$-|T]) -> + tokenise(skip_comment(T)); + +tokenise([$-,H|T]) when ($0 =< H ) andalso (H =< $9) -> + {Val, Rest} = get_integer(T, [H]), + {{integer, get(line), -1 * Val}, Rest}; + +tokenise([H|T]) when ($0 =< H) andalso (H =< $9) -> + {Val, Rest} = get_integer(T, [H]), + {{integer, get(line), Val}, Rest}; + +tokenise([$"|T]) -> + collect_string($", T, []); + +tokenise([$'|T]) -> + collect_string($', T, []); + +%% Read away white spaces +tokenise([9| T]) -> tokenise(T); +tokenise([10| T]) -> tokenise(T); +tokenise([13| T]) -> tokenise(T); +tokenise([32| T]) -> tokenise(T); + +%% Handle singe characters like { } [ ] + = ... +tokenise([Ch | T]) -> + Atm = list_to_atom([Ch]), + {{Atm, get(line)}, T}; + +tokenise([]) -> + tokenise(getLine()); + +tokenise(eof) -> + {{'$end', get(line)}, eof}. + +collect_string($", [$"|T],BackwardsStr) -> + {{string, get(line), BackwardsStr}, T}; + +collect_string($', [$'|T],BackwardsStr) -> + {{quote, get(line), BackwardsStr}, T}; + +collect_string(StopChar, [Ch|T], Str) -> + collect_string(StopChar,T,[Ch|Str]); + +collect_string(StopChar, [],Str) -> + collect_string(StopChar, getLine(), Str); + +collect_string(StopChar, eof, Str) -> + throw({error, {get(line), ?MODULE, + {"Missing ~s in string:~n \"~s\"~n", + [[StopChar], lists:reverse(Str)]}}}). + +get_name(Category, Name, [Char|T]) -> + case isInName(Char) of + true -> + get_name(Category, [Char|Name], T); + false -> + makeNameRespons(Category, Name, [Char | T]) + end; +get_name(Category, Name, []) -> + makeNameRespons(Category, Name, []). + +makeNameRespons(Category, Name, RestChars) -> + Atm = list_to_atom(lists:reverse(Name)), + case reserved_word(Atm) of + true -> {{Atm, get(line)}, RestChars}; + false -> {{Category, get(line), Atm}, RestChars} + end. + +isInName($-) -> true; +isInName(Ch) -> isalnum(Ch). + +isalnum(H) when ($A =< H) andalso (H =< $Z) -> + true; +isalnum(H) when ($a =< H) andalso (H =< $z) -> + true; +isalnum(H) when ($0 =< H) andalso (H =< $9) -> + true; +isalnum(_) -> + false. + +isdigit(H) when ($0 =< H) andalso (H =< $9) -> + true; +isdigit(_) -> + false. + +get_integer([H|T], "0") -> + case isdigit(H) of + true -> + throw({error, {get(line), ?MODULE, + {"Unexpected ~w~n", + [list_to_atom([H])]}}}); + false -> + {0, [H|T]} + end; +get_integer([H|T], L) -> + case isdigit(H) of + true -> + get_integer(T, [H|L]); + false -> + {list_to_integer(lists:reverse(L)), [H|T]} + end; +get_integer([], L) -> + {list_to_integer(lists:reverse(L)), []}. + +%%-------------------------------------------------- +%% ASN.1 type of comments. "--" is comment to eoln or next "--" +%%-------------------------------------------------- +skip_comment([]) -> + []; +skip_comment([$-,$-|T]) -> + T; +skip_comment([_|T]) -> + skip_comment(T). |