diff options
Diffstat (limited to 'lib/snmp/src/agent/snmpa_set_lib.erl')
-rw-r--r-- | lib/snmp/src/agent/snmpa_set_lib.erl | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/lib/snmp/src/agent/snmpa_set_lib.erl b/lib/snmp/src/agent/snmpa_set_lib.erl new file mode 100644 index 0000000000..191029f6db --- /dev/null +++ b/lib/snmp/src/agent/snmpa_set_lib.erl @@ -0,0 +1,395 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(snmpa_set_lib). + +-include("snmp_types.hrl"). + +-define(VMODULE,"SETLIB"). +-include("snmp_verbosity.hrl"). + + +%% External exports +-export([is_varbinds_ok/1, consistency_check/1, + undo_varbinds/1, try_set/1]). + +%%%----------------------------------------------------------------- +%%% This module implements functions common to all different set +%%% mechanisms. +%%%----------------------------------------------------------------- +%% Phase 1 of the set-operation. (see rfc1905:4.2.5) +%% Input is a sorted Varbinds list. +%% Output is either ok or {ErrIndex, ErrCode}. +%%* 1) IF the variable is outside the mib view THEN noAccess. +%%* 2a) IF the varaible doesn't exist THEN notWritable. +%% 2b) IF mib isn't able to create instances of the variable (for +%% example in a table) THEN noCreation. +%%* 3) IF the new value provided is of the wrong ASN.1 type THEN wrongType. +%%* 4) IF then new value is of wrong length THEN wrongLength. +%%# 5) IF value is incorrectly encoded THEN wrongEncoding. [nyi] +%%* 6) IF value is outside the acceptable range THEN wrongValue. +%% 7) IF variable does not exist and could not ever be created +%% THEN noCreation. +%% 8) IF variable can not be created now THEN inconsistentName. +%% 9) IF value can not be set now THEN inconsistentValue. +%%* 9) IF instances of the variable can not be modified THEN notWritable. +%% 10) IF an unavailable resource is needed THEN resourceUnavailable. +%% 11) IF any other error THEN genErr. +%% 12) Otherwise ok! +%% The Agent takes care of *-marked. (# should be done by the agent but is +%% nyi.) +%% The rest is taken care of in consistency_check that communicates with the mib +%% (through the is_set_ok-function (for each variable)). +%% +%% SNMPv1 (see rfc1157:4.1.5) +%%* 1) IF variable not available for set in the mibview THEN noSuchName. +%%* 2) IF variable exists, but doesn't permit writeing THEN readOnly. +%%* 3) IF provided value doesn't match ASN.1 type THEN badValue. +%% 4) IF any other error THEN genErr. +%% 5) Otherwise ok! +%% +%% Internally we use snmpv2 messages, and convert them to the appropriate +%% v1 msg. +%%----------------------------------------------------------------- +%% Func: is_varbinds_ok/2 +%% Purpose: Call is_varbind_ok for each varbind +%% Returns: {noError, 0} | {ErrorStatus, ErrorIndex} +%%----------------------------------------------------------------- +is_varbinds_ok([{_TableOid, TableVbs} | IVarbinds]) -> + is_varbinds_ok(lists:append(TableVbs, IVarbinds)); +is_varbinds_ok([IVarbind | IVarbinds]) -> + case catch is_varbind_ok(IVarbind) of + true -> is_varbinds_ok(IVarbinds); + ErrorStatus -> + Varbind = IVarbind#ivarbind.varbind, + ?vtrace("varbinds erroneous: ~p -> ~p", + [Varbind#varbind.org_index,ErrorStatus]), + {ErrorStatus, Varbind#varbind.org_index} + end; +is_varbinds_ok([]) -> + ?vtrace("varbinds ok",[]), + {noError, 0}. + +%%----------------------------------------------------------------- +%% Func: is_varbind_ok/1 +%% Purpose: Check everything we can check about the varbind against +%% the MIB. Here we don't call any instrumentation functions. +%% Returns: true | +%% Fails: with an <error-atom>. +%%----------------------------------------------------------------- +is_varbind_ok(#ivarbind{status = Status, + mibentry = MibEntry, + varbind = Varbind}) -> + variableExists(Status, MibEntry, Varbind), + % If we get here, MibEntry /= false + variableIsWritable(MibEntry, Varbind), + checkASN1Type(MibEntry, Varbind), + checkValEncoding(MibEntry, Varbind), + true. + +variableExists(noError, false, _Varbind) -> throw(notWritable); +variableExists(noError, _MibEntry, _Varbind) -> true; +%% ErrorStatus == noSuchObject | noSuchInstance +variableExists(noSuchObject, _MibEntry, _Varbind) -> throw(notWritable); +variableExists(_ErrorStatus, _MibEntry, _Varbind) -> throw(noCreation). + +variableIsWritable(#me{access = 'read-write'}, _Varbind) -> true; +variableIsWritable(#me{access = 'read-create'}, _Varbind) -> true; +variableIsWritable(#me{access = 'write-only'}, _Varbind) -> true; +variableIsWritable(_MibEntry, _Varbind) -> throw(notWritable). + +%% Uses make_value_a_correct_value to check type, length and range. +%% Returns: true | <error-atom> +checkASN1Type(#me{asn1_type = ASN1Type}, + #varbind{variabletype = Type, value = Value}) + when ASN1Type#asn1_type.bertype =:= Type -> + case make_value_a_correct_value({value, Value}, ASN1Type, + undef) of + {value, Type, Value} -> true; + {error, Error} when is_atom(Error) -> throw(Error) + end; + +checkASN1Type(_,_) -> throw(wrongType). + + +%% tricky... +checkValEncoding(_MibEntry, _Varbind) -> true. + +%%----------------------------------------------------------------- +%% Func: consistency_check/1 +%% Purpose: Scans all vbs, and checks whether there is a is_set_ok +%% function or not. If it exists, it is called with the +%% vb, to check if the set-op is ok. If it doesn't exist, +%% it is considered ok to set the variable. +%% Returns: {noError, 0} | {ErrorStatus, ErrorIndex] +%% PRE: #ivarbind.status == noError for each varbind +%%----------------------------------------------------------------- +consistency_check(Varbinds) -> + consistency_check(Varbinds, []). +consistency_check([{TableOid, TableVbs} | Varbinds], Done) -> + ?vtrace("consistency_check -> entry with" + "~n TableOid: ~p" + "~n TableVbs: ~p",[TableOid,TableVbs]), + TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), + [#ivarbind{mibentry = MibEntry}|_] = TableVbs, + case is_set_ok_table(MibEntry, TableOpsWithShortOids) of + {noError, 0} -> + consistency_check(Varbinds, [{TableOid, TableVbs} | Done]); + {Reason, Idx} -> + case undo_varbinds(Done) of + {noError, 0} -> {Reason, find_org_index(Idx, TableVbs)}; + Error -> Error + end + end; +consistency_check([IVarbind | Varbinds], Done) -> + ?vtrace("consistency_check -> entry with" + "~n IVarbind: ~p",[IVarbind]), + #ivarbind{varbind = Varbind, mibentry = MibEntry} = IVarbind, + #varbind{value = Value, org_index = OrgIndex} = Varbind, + case is_set_ok_variable(MibEntry, Value) of + noError -> consistency_check(Varbinds, [IVarbind | Done]); + Reason -> + case undo_varbinds(Done) of + {noError, 0} -> {Reason, OrgIndex}; + Error -> Error + end + end; +consistency_check([], _Done) -> + ?vtrace("consistency_check -> done",[]), + {noError, 0}. + +deletePrefixes(Prefix, [#ivarbind{varbind = Varbind} | Vbs]) -> + #varbind{oid = Oid, value = Value} = Varbind, + [{snmp_misc:diff(Oid, Prefix), Value} | deletePrefixes(Prefix, Vbs)]; +deletePrefixes(_Prefix, []) -> []. + +%% Val = <a-value> +is_set_ok_variable(#me{mfa = {Module, Func, Args} = MFA}, Val) -> + ?vtrace("is_set_ok_variable -> entry with" + "~n MFA: ~p" + "~n Val: ~p",[MFA,Val]), + case dbg_apply(Module, Func, [is_set_ok, Val | Args]) of + {'EXIT', {hook_undef, _}} -> noError; + {'EXIT', {hook_function_clause, _}} -> noError; + Result -> + ?vtrace("is_set_ok_variable -> Result: ~n ~p", [Result]), + validate_err(is_set_ok, Result, {Module, Func, Args}) + end. + +%% ValueArg: <list-of-simple-tableops> +is_set_ok_table(#me{mfa = {Module, Func, Args} = MFA}, ValueArg) -> + ?vtrace("is_set_ok_table -> entry with" + "~n MFA: ~p" + "~n ValueArg: ~p",[MFA,ValueArg]), + is_set_ok_all_rows(Module, Func, Args, sort_varbinds_rows(ValueArg), []). + +%% Try one row at a time. Sort varbinds to table-format. +is_set_ok_all_rows(Module, Func, Args, [Row | Rows], Done) -> + ?vtrace("is_set_ok_all_rows -> entry with" + "~n MFA: ~p" + "~n Row: ~p" + "~n Done: ~p",[{Module,Func,Args},Row,Done]), + [{RowIndex, Cols}] = delete_org_index([Row]), + case dbg_apply(Module, Func, [is_set_ok, RowIndex, Cols | Args]) of + {'EXIT', {hook_undef, _}} -> + is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]); + {'EXIT', {hook_function_clause, _}} -> + is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]); + Result -> + case validate_err(table_is_set_ok, Result, {Module, Func, Args}) of + {noError, 0} -> + is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]); + {ErrorStatus, ColNumber} -> + case undo_all_rows(Module, Func, Args, Done) of + {noError, 0} -> + {RowIndex, OrgCols} = Row, + validate_err(row_is_set_ok, + {ErrorStatus, + col_to_orgindex(ColNumber,OrgCols)}, + {Module, Func, Args}); + Error -> Error + end + end + end; +is_set_ok_all_rows(_Module, _Func, _Args, [], _Done) -> + ?vtrace("is_set_ok_all_rows -> done",[]), + {noError, 0}. + +undo_varbinds([{TableOid, TableVbs} | Varbinds]) -> + TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), + [#ivarbind{mibentry = MibEntry}|_] = TableVbs, + case undo_table(MibEntry, TableOpsWithShortOids) of + {noError, 0} -> + undo_varbinds(Varbinds); + {Reason, Idx} -> + undo_varbinds(Varbinds), + {Reason, Idx} + end; +undo_varbinds([IVarbind | Varbinds]) -> + #ivarbind{varbind = Varbind, mibentry = MibEntry} = IVarbind, + #varbind{value = Value, org_index = OrgIndex} = Varbind, + case undo_variable(MibEntry, Value) of + noError -> undo_varbinds(Varbinds); + Reason -> + undo_varbinds(Varbinds), + {Reason, OrgIndex} + end; +undo_varbinds([]) -> {noError, 0}. + +%% Val = <a-value> +undo_variable(#me{mfa = {Module, Func, Args}}, Val) -> + case dbg_apply(Module, Func, [undo, Val | Args]) of + {'EXIT', {hook_undef, _}} -> noError; + {'EXIT', {hook_function_clause, _}} -> noError; + Result -> validate_err(undo, Result, {Module, Func, Args}) + end. + +%% ValueArg: <list-of-simple-tableops> +undo_table(#me{mfa = {Module, Func, Args}}, ValueArg) -> + undo_all_rows(Module, Func, Args, sort_varbinds_rows(ValueArg)). + +undo_all_rows(Module, Func, Args, [Row | Rows]) -> + [{RowIndex, Cols}] = delete_org_index([Row]), + case dbg_apply(Module, Func, [undo, RowIndex, Cols | Args]) of + {'EXIT', {hook_undef, _}} -> + undo_all_rows(Module, Func, Args, Rows); + {'EXIT', {hook_function_clause, _}} -> + undo_all_rows(Module, Func, Args, Rows); + Result -> + case validate_err(table_undo, Result, {Module, Func, Args}) of + {noError, 0} -> undo_all_rows(Module, Func, Args, Rows); + {ErrorStatus, ColNumber} -> + {RowIndex, OrgCols} = Row, + undo_all_rows(Module, Func, Args, Rows), + OrgIdx = col_to_orgindex(ColNumber, OrgCols), + validate_err(row_undo, {ErrorStatus, OrgIdx}, + {Module, Func, Args}) + end + end; +undo_all_rows(_Module, _Func, _Args, []) -> + {noError, 0}. + +try_set([{TableOid, TableVbs} | Varbinds]) -> + TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), + [#ivarbind{mibentry = MibEntry}|_] = TableVbs, + case set_value_to_tab_mibentry(MibEntry, TableOpsWithShortOids) of + {noError, 0} -> + try_set(Varbinds); + {ErrorStatus, Index} -> + undo_varbinds(Varbinds), + {ErrorStatus, find_org_index(Index, TableVbs)} + end; +try_set([#ivarbind{varbind = Varbind, mibentry = MibEntry} | Varbinds]) -> + #varbind{value = Value, org_index = Index} = Varbind, + case set_value_to_var_mibentry(MibEntry, Value) of + noError -> try_set(Varbinds); + Error -> + undo_varbinds(Varbinds), + {Error, Index} + end; +try_set([]) -> {noError, 0}. + + +%% returns: ErrMsg +set_value_to_var_mibentry(#me{mfa = {Module, Func, Args}}, + SetArgs) -> + Result = dbg_apply(Module, Func, [set, SetArgs | Args]), + validate_err(set, Result, {Module, Func, Args}). + +%% returns: {ErrMsg, Idx} +set_value_to_tab_mibentry(#me{mfa = {Module, Func, Args}}, + SetArgs) -> + set_value_all_rows(Module, Func, Args, + sort_varbinds_rows(SetArgs)). + + +set_value_all_rows(_Module, _Func, _Args, []) -> {noError, 0}; +set_value_all_rows(Module, Func, Args, [Row | Rows]) -> + [{RowIndex, Cols}] = delete_org_index([Row]), + Res = dbg_apply(Module, Func, [set, RowIndex, Cols | Args]), + case validate_err(table_set, Res, {Module, Func, Args}) of + {noError, 0} -> set_value_all_rows(Module, Func, Args, Rows); + {ErrCode, ColNumber} -> + {RowIndex, OrgCols} = Row, + OrgIndex = col_to_orgindex(ColNumber, OrgCols), + validate_err(row_set, {ErrCode, OrgIndex}, {Module, Func, Args}) + end. + +sort_varbinds_rows(Varbinds) -> + snmpa_svbl:sort_varbinds_rows(Varbinds). +delete_org_index(Indexes) -> + snmpa_svbl:delete_org_index(Indexes). +col_to_orgindex(ColNumber, OrgCols) -> + snmpa_svbl:col_to_orgindex(ColNumber, OrgCols). + +find_org_index(ExternIndex, _SortedVarbinds) when ExternIndex =:= 0 -> 0; +find_org_index(ExternIndex, SortedVarbinds) -> + VBs = lists:flatten(SortedVarbinds), + case length(VBs) of + Len when (ExternIndex =< Len) andalso (ExternIndex >= 1) -> + IVB = lists:nth(ExternIndex, VBs), + VB = IVB#ivarbind.varbind, + VB#varbind.org_index; + _Else -> + 0 + end. + +validate_err(Type, Error, Mfa) -> + snmpa_agent:validate_err(Type, Error, Mfa). +make_value_a_correct_value(Value, ASN1Type, Mfa) -> + snmpa_agent:make_value_a_correct_value(Value, ASN1Type, Mfa). + +%%----------------------------------------------------------------- +%% Runtime debug support +%%----------------------------------------------------------------- + +% XXX: This function match on the exakt return codes from EXIT +% messages. As of this writing it was not decided if this is +% the right way so don't blindly do things this way. +% +% We fake a real EXIT signal as the return value because the +% result is passed to the function snmpa_agent:validate_err() +% that expect it. + +dbg_apply(M,F,A) -> + Result = + case get(verbosity) of + false -> + (catch apply(M,F,A)); + _ -> + ?vlog("~n apply: ~w,~w,~p~n", [M,F,A]), + Res = (catch apply(M,F,A)), + ?vlog("~n returned: ~p", [Res]), + Res + end, + case Result of + {'EXIT', {undef, [{M, F, A} | _]}} -> + {'EXIT', {hook_undef, {M, F, A}}}; + {'EXIT', {function_clause, [{M, F, A} | _]}} -> + {'EXIT', {hook_function_clause, {M, F, A}}}; + + % XXX: Old format for compatibility + {'EXIT', {undef, {M, F, A}}} -> + {'EXIT', {hook_undef, {M, F, A}}}; + {'EXIT', {function_clause, {M, F, A}}} -> + {'EXIT', {hook_function_clause, {M, F, A}}}; + + Result -> + Result + end. + |