1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1996-2020. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20-module(snmpa_set_lib). 21 22-include("snmp_types.hrl"). 23 24-define(VMODULE,"SETLIB"). 25-include("snmp_verbosity.hrl"). 26 27 28%% External exports 29-export([is_varbinds_ok/1, consistency_check/1, 30 undo_varbinds/1, try_set/1]). 31 32%%%----------------------------------------------------------------- 33%%% This module implements functions common to all different set 34%%% mechanisms. 35%%%----------------------------------------------------------------- 36%% Phase 1 of the set-operation. (see rfc1905:4.2.5) 37%% Input is a sorted Varbinds list. 38%% Output is either ok or {ErrIndex, ErrCode}. 39%%* 1) IF the variable is outside the mib view THEN noAccess. 40%%* 2a) IF the varaible doesn't exist THEN notWritable. 41%% 2b) IF mib isn't able to create instances of the variable (for 42%% example in a table) THEN noCreation. 43%%* 3) IF the new value provided is of the wrong ASN.1 type THEN wrongType. 44%%* 4) IF then new value is of wrong length THEN wrongLength. 45%%# 5) IF value is incorrectly encoded THEN wrongEncoding. [nyi] 46%%* 6) IF value is outside the acceptable range THEN wrongValue. 47%% 7) IF variable does not exist and could not ever be created 48%% THEN noCreation. 49%% 8) IF variable cannot be created now THEN inconsistentName. 50%% 9) IF value cannot be set now THEN inconsistentValue. 51%%* 9) IF instances of the variable cannot be modified THEN notWritable. 52%% 10) IF an unavailable resource is needed THEN resourceUnavailable. 53%% 11) IF any other error THEN genErr. 54%% 12) Otherwise ok! 55%% The Agent takes care of *-marked. (# should be done by the agent but is 56%% nyi.) 57%% The rest is taken care of in consistency_check that communicates with the mib 58%% (through the is_set_ok-function (for each variable)). 59%% 60%% SNMPv1 (see rfc1157:4.1.5) 61%%* 1) IF variable not available for set in the mibview THEN noSuchName. 62%%* 2) IF variable exists, but doesn't permit writeing THEN readOnly. 63%%* 3) IF provided value doesn't match ASN.1 type THEN badValue. 64%% 4) IF any other error THEN genErr. 65%% 5) Otherwise ok! 66%% 67%% Internally we use snmpv2 messages, and convert them to the appropriate 68%% v1 msg. 69%%----------------------------------------------------------------- 70%% Func: is_varbinds_ok/2 71%% Purpose: Call is_varbind_ok for each varbind 72%% Returns: {noError, 0} | {ErrorStatus, ErrorIndex} 73%%----------------------------------------------------------------- 74is_varbinds_ok([{_TableOid, TableVbs} | IVarbinds]) -> 75 is_varbinds_ok(lists:append(TableVbs, IVarbinds)); 76is_varbinds_ok([IVarbind | IVarbinds]) -> 77 case catch is_varbind_ok(IVarbind) of 78 true -> is_varbinds_ok(IVarbinds); 79 ErrorStatus -> 80 Varbind = IVarbind#ivarbind.varbind, 81 ?vtrace("varbinds erroneous: ~p -> ~p", 82 [Varbind#varbind.org_index,ErrorStatus]), 83 {ErrorStatus, Varbind#varbind.org_index} 84 end; 85is_varbinds_ok([]) -> 86 ?vtrace("varbinds ok",[]), 87 {noError, 0}. 88 89%%----------------------------------------------------------------- 90%% Func: is_varbind_ok/1 91%% Purpose: Check everything we can check about the varbind against 92%% the MIB. Here we don't call any instrumentation functions. 93%% Returns: true | 94%% Fails: with an <error-atom>. 95%%----------------------------------------------------------------- 96is_varbind_ok(#ivarbind{status = Status, 97 mibentry = MibEntry, 98 varbind = Varbind}) -> 99 variableExists(Status, MibEntry, Varbind), 100 % If we get here, MibEntry /= false 101 variableIsWritable(MibEntry, Varbind), 102 checkASN1Type(MibEntry, Varbind), 103 checkValEncoding(MibEntry, Varbind), 104 true. 105 106variableExists(noError, false, _Varbind) -> throw(notWritable); 107variableExists(noError, _MibEntry, _Varbind) -> true; 108%% ErrorStatus == noSuchObject | noSuchInstance 109variableExists(noSuchObject, _MibEntry, _Varbind) -> throw(notWritable); 110variableExists(_ErrorStatus, _MibEntry, _Varbind) -> throw(noCreation). 111 112variableIsWritable(#me{access = 'read-write'}, _Varbind) -> true; 113variableIsWritable(#me{access = 'read-create'}, _Varbind) -> true; 114variableIsWritable(#me{access = 'write-only'}, _Varbind) -> true; 115variableIsWritable(_MibEntry, _Varbind) -> throw(notWritable). 116 117%% Uses make_value_a_correct_value to check type, length and range. 118%% Returns: true | <error-atom> 119checkASN1Type(#me{asn1_type = ASN1Type}, 120 #varbind{variabletype = Type, value = Value}) 121 when ASN1Type#asn1_type.bertype =:= Type -> 122 case make_value_a_correct_value({value, Value}, ASN1Type, 123 undef) of 124 {value, Type, Value} -> true; 125 {error, Error} when is_atom(Error) -> throw(Error) 126 end; 127 128checkASN1Type(_,_) -> throw(wrongType). 129 130 131%% tricky... 132checkValEncoding(_MibEntry, _Varbind) -> true. 133 134%%----------------------------------------------------------------- 135%% Func: consistency_check/1 136%% Purpose: Scans all vbs, and checks whether there is a is_set_ok 137%% function or not. If it exists, it is called with the 138%% vb, to check if the set-op is ok. If it doesn't exist, 139%% it is considered ok to set the variable. 140%% Returns: {noError, 0} | {ErrorStatus, ErrorIndex] 141%% PRE: #ivarbind.status == noError for each varbind 142%%----------------------------------------------------------------- 143consistency_check(Varbinds) -> 144 consistency_check(Varbinds, []). 145consistency_check([{TableOid, TableVbs} | Varbinds], Done) -> 146 ?vtrace("consistency_check -> entry with" 147 "~n TableOid: ~p" 148 "~n TableVbs: ~p", [TableOid, TableVbs]), 149 TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), 150 [#ivarbind{mibentry = MibEntry}|_] = TableVbs, 151 case is_set_ok_table(MibEntry, TableOpsWithShortOids) of 152 {noError, 0} -> 153 consistency_check(Varbinds, [{TableOid, TableVbs} | Done]); 154 {Reason, Idx} -> 155 case undo_varbinds(Done) of 156 {noError, 0} -> {Reason, find_org_index(Idx, TableVbs)}; 157 Error -> Error 158 end 159 end; 160consistency_check([IVarbind | Varbinds], Done) -> 161 ?vtrace("consistency_check -> entry with" 162 "~n IVarbind: ~p", [IVarbind]), 163 #ivarbind{varbind = Varbind, mibentry = MibEntry} = IVarbind, 164 #varbind{value = Value, org_index = OrgIndex} = Varbind, 165 case is_set_ok_variable(MibEntry, Value) of 166 noError -> consistency_check(Varbinds, [IVarbind | Done]); 167 Reason -> 168 case undo_varbinds(Done) of 169 {noError, 0} -> {Reason, OrgIndex}; 170 Error -> Error 171 end 172 end; 173consistency_check([], _Done) -> 174 ?vtrace("consistency_check -> done",[]), 175 {noError, 0}. 176 177deletePrefixes(Prefix, [#ivarbind{varbind = Varbind} | Vbs]) -> 178 #varbind{oid = Oid, value = Value} = Varbind, 179 [{snmp_misc:diff(Oid, Prefix), Value} | deletePrefixes(Prefix, Vbs)]; 180deletePrefixes(_Prefix, []) -> []. 181 182%% Val = <a-value> 183is_set_ok_variable(#me{mfa = {Module, Func, Args} = MFA}, Val) -> 184 ?vtrace("is_set_ok_variable -> entry with" 185 "~n MFA: ~p" 186 "~n Val: ~p",[MFA,Val]), 187 case dbg_apply(Module, Func, [is_set_ok, Val | Args]) of 188 {'EXIT', {hook_undef, _}} -> noError; 189 {'EXIT', {hook_function_clause, _}} -> noError; 190 Result -> 191 ?vtrace("is_set_ok_variable -> Result: ~n ~p", [Result]), 192 validate_err(is_set_ok, Result, {Module, Func, Args}) 193 end. 194 195%% ValueArg: <list-of-simple-tableops> 196is_set_ok_table(#me{mfa = {Module, Func, Args} = MFA}, ValueArg) -> 197 ?vtrace("is_set_ok_table -> entry with" 198 "~n MFA: ~p" 199 "~n ValueArg: ~p",[MFA,ValueArg]), 200 is_set_ok_all_rows(Module, Func, Args, sort_varbinds_rows(ValueArg), []). 201 202%% Try one row at a time. Sort varbinds to table-format. 203is_set_ok_all_rows(Module, Func, Args, [Row | Rows], Done) -> 204 ?vtrace("is_set_ok_all_rows -> entry with" 205 "~n MFA: ~p" 206 "~n Row: ~p" 207 "~n Done: ~p",[{Module,Func,Args},Row,Done]), 208 [{RowIndex, Cols}] = delete_org_index([Row]), 209 case dbg_apply(Module, Func, [is_set_ok, RowIndex, Cols | Args]) of 210 {'EXIT', {hook_undef, _}} -> 211 is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]); 212 {'EXIT', {hook_function_clause, _}} -> 213 is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]); 214 Result -> 215 case validate_err(table_is_set_ok, Result, {Module, Func, Args}) of 216 {noError, 0} -> 217 is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]); 218 {ErrorStatus, ColNumber} -> 219 case undo_all_rows(Module, Func, Args, Done) of 220 {noError, 0} -> 221 {RowIndex, OrgCols} = Row, 222 validate_err(row_is_set_ok, 223 {ErrorStatus, 224 col_to_orgindex(ColNumber,OrgCols)}, 225 {Module, Func, Args}); 226 Error -> Error 227 end 228 end 229 end; 230is_set_ok_all_rows(_Module, _Func, _Args, [], _Done) -> 231 ?vtrace("is_set_ok_all_rows -> done",[]), 232 {noError, 0}. 233 234undo_varbinds([{TableOid, TableVbs} | Varbinds]) -> 235 TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), 236 [#ivarbind{mibentry = MibEntry}|_] = TableVbs, 237 case undo_table(MibEntry, TableOpsWithShortOids) of 238 {noError, 0} -> 239 undo_varbinds(Varbinds); 240 {Reason, Idx} -> 241 undo_varbinds(Varbinds), 242 {Reason, Idx} 243 end; 244undo_varbinds([IVarbind | Varbinds]) -> 245 #ivarbind{varbind = Varbind, mibentry = MibEntry} = IVarbind, 246 #varbind{value = Value, org_index = OrgIndex} = Varbind, 247 case undo_variable(MibEntry, Value) of 248 noError -> undo_varbinds(Varbinds); 249 Reason -> 250 undo_varbinds(Varbinds), 251 {Reason, OrgIndex} 252 end; 253undo_varbinds([]) -> {noError, 0}. 254 255%% Val = <a-value> 256undo_variable(#me{mfa = {Module, Func, Args}}, Val) -> 257 case dbg_apply(Module, Func, [undo, Val | Args]) of 258 {'EXIT', {hook_undef, _}} -> noError; 259 {'EXIT', {hook_function_clause, _}} -> noError; 260 Result -> validate_err(undo, Result, {Module, Func, Args}) 261 end. 262 263%% ValueArg: <list-of-simple-tableops> 264undo_table(#me{mfa = {Module, Func, Args}}, ValueArg) -> 265 undo_all_rows(Module, Func, Args, sort_varbinds_rows(ValueArg)). 266 267undo_all_rows(Module, Func, Args, [Row | Rows]) -> 268 [{RowIndex, Cols}] = delete_org_index([Row]), 269 case dbg_apply(Module, Func, [undo, RowIndex, Cols | Args]) of 270 {'EXIT', {hook_undef, _}} -> 271 undo_all_rows(Module, Func, Args, Rows); 272 {'EXIT', {hook_function_clause, _}} -> 273 undo_all_rows(Module, Func, Args, Rows); 274 Result -> 275 case validate_err(table_undo, Result, {Module, Func, Args}) of 276 {noError, 0} -> undo_all_rows(Module, Func, Args, Rows); 277 {ErrorStatus, ColNumber} -> 278 {RowIndex, OrgCols} = Row, 279 undo_all_rows(Module, Func, Args, Rows), 280 OrgIdx = col_to_orgindex(ColNumber, OrgCols), 281 validate_err(row_undo, {ErrorStatus, OrgIdx}, 282 {Module, Func, Args}) 283 end 284 end; 285undo_all_rows(_Module, _Func, _Args, []) -> 286 {noError, 0}. 287 288try_set([{TableOid, TableVbs} | Varbinds]) -> 289 TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), 290 [#ivarbind{mibentry = MibEntry}|_] = TableVbs, 291 case set_value_to_tab_mibentry(MibEntry, TableOpsWithShortOids) of 292 {noError, 0} -> 293 try_set(Varbinds); 294 {ErrorStatus, Index} -> 295 undo_varbinds(Varbinds), 296 {ErrorStatus, find_org_index(Index, TableVbs)} 297 end; 298try_set([#ivarbind{varbind = Varbind, mibentry = MibEntry} | Varbinds]) -> 299 #varbind{value = Value, org_index = Index} = Varbind, 300 case set_value_to_var_mibentry(MibEntry, Value) of 301 noError -> try_set(Varbinds); 302 Error -> 303 undo_varbinds(Varbinds), 304 {Error, Index} 305 end; 306try_set([]) -> {noError, 0}. 307 308 309%% returns: ErrMsg 310set_value_to_var_mibentry(#me{mfa = {Module, Func, Args}}, 311 SetArgs) -> 312 Result = dbg_apply(Module, Func, [set, SetArgs | Args]), 313 validate_err(set, Result, {Module, Func, Args}). 314 315%% returns: {ErrMsg, Idx} 316set_value_to_tab_mibentry(#me{mfa = {Module, Func, Args}}, 317 SetArgs) -> 318 set_value_all_rows(Module, Func, Args, 319 sort_varbinds_rows(SetArgs)). 320 321 322set_value_all_rows(_Module, _Func, _Args, []) -> {noError, 0}; 323set_value_all_rows(Module, Func, Args, [Row | Rows]) -> 324 [{RowIndex, Cols}] = delete_org_index([Row]), 325 Res = dbg_apply(Module, Func, [set, RowIndex, Cols | Args]), 326 case validate_err(table_set, Res, {Module, Func, Args}) of 327 {noError, 0} -> set_value_all_rows(Module, Func, Args, Rows); 328 {ErrCode, ColNumber} -> 329 {RowIndex, OrgCols} = Row, 330 OrgIndex = col_to_orgindex(ColNumber, OrgCols), 331 validate_err(row_set, {ErrCode, OrgIndex}, {Module, Func, Args}) 332 end. 333 334sort_varbinds_rows(Varbinds) -> 335 snmpa_svbl:sort_varbinds_rows(Varbinds). 336delete_org_index(Indexes) -> 337 snmpa_svbl:delete_org_index(Indexes). 338col_to_orgindex(ColNumber, OrgCols) -> 339 snmpa_svbl:col_to_orgindex(ColNumber, OrgCols). 340 341find_org_index(ExternIndex, _SortedVarbinds) when ExternIndex =:= 0 -> 0; 342find_org_index(ExternIndex, SortedVarbinds) -> 343 VBs = lists:flatten(SortedVarbinds), 344 case length(VBs) of 345 Len when (ExternIndex =< Len) andalso (ExternIndex >= 1) -> 346 IVB = lists:nth(ExternIndex, VBs), 347 VB = IVB#ivarbind.varbind, 348 VB#varbind.org_index; 349 _Else -> 350 0 351 end. 352 353validate_err(Type, Error, Mfa) -> 354 snmpa_agent:validate_err(Type, Error, Mfa). 355make_value_a_correct_value(Value, ASN1Type, Mfa) -> 356 snmpa_agent:make_value_a_correct_value(Value, ASN1Type, Mfa). 357 358%%----------------------------------------------------------------- 359%% Runtime debug support 360%%----------------------------------------------------------------- 361 362%% XYZ: This function match on the exakt return codes from EXIT 363%% messages. As of this writing it was not decided if this is 364%% the right way so don't blindly do things this way. 365%% 366%% We fake a real EXIT signal as the return value because the 367%% result is passed to the function snmpa_agent:validate_err() 368%% that expect it. 369 370dbg_apply(M,F,A) -> 371 case maybe_verbose_apply(M, F, A) of 372 %% <Future proofing> 373 %% As of R15 we get extra info containing, 374 %% among other things, line numbers. 375 {'EXIT', {undef, [{M, F, A, _} | _]}} -> 376 {'EXIT', {hook_undef, {M, F, A}}}; 377 {'EXIT', {function_clause, [{M, F, A, _} | _]}} -> 378 {'EXIT', {hook_function_clause, {M, F, A}}}; 379 380 %% This is really overkill, but just to be on the safe side... 381 {'EXIT', {undef, {M, F, A, _}}} -> 382 {'EXIT', {hook_undef, {M, F, A}}}; 383 {'EXIT', {function_clause, {M, F, A, _}}} -> 384 {'EXIT', {hook_function_clause, {M, F, A}}}; 385 %% </Future proofing> 386 387 %% Old format format for compatibility 388 {'EXIT', {undef, [{M, F, A} | _]}} -> 389 {'EXIT', {hook_undef, {M, F, A}}}; 390 {'EXIT', {function_clause, [{M, F, A} | _]}} -> 391 {'EXIT', {hook_function_clause, {M, F, A}}}; 392 393 %% XYZ: Older format for compatibility 394 {'EXIT', {undef, {M, F, A}}} -> 395 {'EXIT', {hook_undef, {M, F, A}}}; 396 {'EXIT', {function_clause, {M, F, A}}} -> 397 {'EXIT', {hook_function_clause, {M, F, A}}}; 398 399 Result -> 400 Result 401 end. 402 403maybe_verbose_apply(M, F, A) -> 404 case get(verbosity) of 405 false -> 406 (catch apply(M,F,A)); 407 _ -> 408 ?vlog("~n apply: ~w,~w,~p~n", [M,F,A]), 409 Res = (catch apply(M,F,A)), 410 ?vlog("~n returned: ~p", [Res]), 411 Res 412 end. 413