1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1996-2016. 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(snmp_generic_mnesia). 21 22-export([variable_get/1, variable_set/2, variable_inc/2]). 23-export([table_func/2, table_func/4, 24 table_set_cols/4, table_set_element/4, table_set_elements/3, 25 table_get_elements/4, table_get_row/2, table_get_row/3, 26 table_next/2,table_set_status/7, 27 table_try_make_consistent/2, 28 table_delete_row/2]). 29 30-include("STANDARD-MIB.hrl"). 31-include("snmp_types.hrl"). 32%% -include("snmp_generic.hrl"). 33 34%%%----------------------------------------------------------------- 35%%% Generic functions for implementing software tables 36%%% and variables. Mnesia is used. 37%%%----------------------------------------------------------------- 38 39%%------------------------------------------------------------------ 40%% Theses functions could be in the MIB for simple 41%% variables or tables, i.e. vars without complex 42%% set-operations. If there are complex set op, an 43%% extra layer-function should be added, and that 44%% function should be in the MIB, and it can call these 45%% functions. 46%%------------------------------------------------------------------ 47 48%%------------------------------------------------------------------ 49%% Variables 50%%------------------------------------------------------------------ 51%%------------------------------------------------------------------ 52%% This is the default function for variables. 53%%------------------------------------------------------------------ 54variable_get(Name) -> 55 case mnesia:dirty_read({snmp_variables, Name}) of 56 [{_Db, _Name, Val}] -> {value, Val}; 57 _ -> undefined 58 end. 59 60variable_set(Name, Val) -> 61 mnesia:dirty_write({snmp_variables, Name, Val}), 62 true. 63 64variable_inc(Name, N) -> 65 case mnesia:dirty_update_counter({snmp_variables, Name}, N) of 66 NewVal when NewVal < 4294967296 -> 67 ok; 68 NewVal -> 69 mnesia:dirty_write({snmp_variables, Name, NewVal rem 4294967296}) 70 end. 71 72%%------------------------------------------------------------------ 73%% Tables 74%% Assumes the RowStatus is the last column in the 75%% table. 76%%------------------------------------------------------------------ 77%%------------------------------------------------------------------ 78%% This is the default function for tables. 79%% 80%% Name is the name of the table (atom) 81%% RowIndex is a flat list of the indexes for the row. 82%% Cols is a list of column numbers. 83%%------------------------------------------------------------------ 84table_func(new, _Name) -> 85 ok; 86 87table_func(delete, _Name) -> 88 ok. 89 90table_func(get, RowIndex, Cols, Name) -> 91 TableInfo = snmp_generic:table_info(Name), 92 snmp_generic:handle_table_get({Name, mnesia}, RowIndex, Cols, 93 TableInfo#table_info.first_own_index); 94 95%%------------------------------------------------------------------ 96%% Returns: List of endOfTable | {NextOid, Value}. 97%% Implements the next operation, with the function 98%% handle_table_next. Next should return the next accessible 99%% instance, which cannot be a key (well, it could, but it 100%% shouldn't). 101%%------------------------------------------------------------------ 102table_func(get_next, RowIndex, Cols, Name) -> 103 #table_info{first_accessible = FirstCol, first_own_index = FOI, 104 nbr_of_cols = LastCol} = snmp_generic:table_info(Name), 105 snmp_generic:handle_table_next({Name,mnesia},RowIndex,Cols, 106 FirstCol, FOI, LastCol); 107 108table_func(is_set_ok, RowIndex, Cols, Name) -> 109 snmp_generic:table_try_row({Name, mnesia}, nofunc, RowIndex, Cols); 110 111%%------------------------------------------------------------------ 112%% Cols is here a list of {ColumnNumber, NewValue} 113%% This function must only be used by tables with a RowStatus col! 114%% Other tables should use table_set_cols/4. 115%% All set functionality is handled within a transaction. 116%% 117%% GenericMnesia uses its own table_set_status and own table_try_make_consistent 118%% for performance reasons. 119%%------------------------------------------------------------------ 120table_func(set, RowIndex, Cols, Name) -> 121 case mnesia:transaction( 122 fun() -> 123 snmp_generic:table_set_row( 124 {Name, mnesia}, nofunc, 125 fun table_try_make_consistent/2, 126 RowIndex, Cols) 127 end) of 128 {atomic, Value} -> 129 Value; 130 {aborted, Reason} -> 131 user_err("set transaction aborted. Tab ~w, RowIndex" 132 " ~w, Cols ~w. Reason ~w", 133 [Name, RowIndex, Cols, Reason]), 134 {Col, _Val} = hd(Cols), 135 {commitFailed, Col} 136 end; 137 138table_func(undo, _RowIndex, _Cols, _Name) -> 139 {noError, 0}. 140 141 142table_get_row(Name, RowIndex) -> 143 case mnesia:snmp_get_row(Name, RowIndex) of 144 {ok, DbRow} -> 145 TableInfo = snmp_generic:table_info(Name), 146 make_row(DbRow, TableInfo#table_info.first_own_index); 147 undefined -> 148 undefined 149 end. 150table_get_row(Name, RowIndex, FOI) -> 151 case mnesia:snmp_get_row(Name, RowIndex) of 152 {ok, DbRow} -> 153 make_row(DbRow, FOI); 154 undefined -> 155 undefined 156 end. 157 158%%----------------------------------------------------------------- 159%% Returns: [Val | noacc | noinit] | undefined 160%%----------------------------------------------------------------- 161table_get_elements(Name, RowIndex, Cols, FirstOwnIndex) -> 162 case mnesia:snmp_get_row(Name, RowIndex) of 163 {ok, DbRow} -> 164 Row = make_row(DbRow, FirstOwnIndex), 165 get_elements(Cols, Row); 166 undefined -> 167 undefined 168 end. 169 170get_elements([Col | Cols], Row) -> 171 [element(Col, Row) | get_elements(Cols, Row)]; 172get_elements([], _Row) -> []. 173 174%%----------------------------------------------------------------- 175%% Args: DbRow is a mnesia row ({name, Keys, Cols, ...}). 176%% Returns: A tuple with a SNMP-table row. Each SNMP-col is one 177%% element, list or int. 178%%----------------------------------------------------------------- 179make_row(DbRow, 0) -> 180 [_Name, _Keys | Cols] = tuple_to_list(DbRow), 181 list_to_tuple(Cols); 182make_row(DbRow, FirstOwnIndex) -> 183 list_to_tuple(make_row2(make_row_list(DbRow), FirstOwnIndex)). 184make_row2(RowList, 1) -> RowList; 185make_row2([_OtherIndex | RowList], N) -> 186 make_row2(RowList, N-1). 187 188make_row_list(Row) -> 189 make_row_list(size(Row), Row, []). 190make_row_list(N, Row, Acc) when N > 2 -> 191 make_row_list(N-1, Row, [element(N, Row) | Acc]); 192make_row_list(2, Row, Acc) -> 193 case element(2, Row) of 194 Keys when is_tuple(Keys) -> 195 lists:append(tuple_to_list(Keys), Acc); 196 Key -> 197 [Key | Acc] 198 end. 199 200%% createAndGo 201table_set_status(Name, RowIndex, ?'RowStatus_createAndGo', _StatusCol, Cols, 202 ChangedStatusFunc, _ConsFunc) -> 203 Row = table_construct_row(Name, RowIndex, ?'RowStatus_active', Cols), 204 mnesia:write(Row), 205 snmp_generic:try_apply(ChangedStatusFunc, [Name, ?'RowStatus_createAndGo', 206 RowIndex, Cols]); 207 208%%------------------------------------------------------------------ 209%% createAndWait - set status to notReady, and try to 210%% make row consistent. 211%%------------------------------------------------------------------ 212table_set_status(Name, RowIndex, ?'RowStatus_createAndWait', _StatusCol, 213 Cols, ChangedStatusFunc, ConsFunc) -> 214 Row = table_construct_row(Name, RowIndex, ?'RowStatus_notReady', Cols), 215 mnesia:write(Row), 216 case snmp_generic:try_apply(ConsFunc, [RowIndex, Row]) of 217 {noError, 0} -> snmp_generic:try_apply(ChangedStatusFunc, 218 [Name, ?'RowStatus_createAndWait', 219 RowIndex, Cols]); 220 Error -> Error 221 end; 222 223%% destroy 224table_set_status(Name, RowIndex, ?'RowStatus_destroy', _StatusCol, Cols, 225 ChangedStatusFunc, _ConsFunc) -> 226 case snmp_generic:try_apply(ChangedStatusFunc, 227 [Name, ?'RowStatus_destroy', RowIndex, Cols]) of 228 {noError, 0} -> 229 #table_info{index_types = Indexes} = snmp_generic:table_info(Name), 230 Key = 231 case snmp_generic:split_index_to_keys(Indexes, RowIndex) of 232 [Key1] -> Key1; 233 KeyList -> list_to_tuple(KeyList) 234 end, 235 mnesia:delete({Name, Key}), 236 {noError, 0}; 237 Error -> Error 238 end; 239 240%% Otherwise, active or notInService 241table_set_status(Name, RowIndex, Val, _StatusCol, Cols, 242 ChangedStatusFunc, ConsFunc) -> 243 table_set_cols(Name, RowIndex, Cols, ConsFunc), 244 snmp_generic:try_apply(ChangedStatusFunc, [Name, Val, RowIndex, Cols]). 245 246table_delete_row(Name, RowIndex) -> 247 case mnesia:snmp_get_mnesia_key(Name, RowIndex) of 248 {ok, Key} -> 249 mnesia:delete({Name, Key}); 250 undefined -> 251 ok 252 end. 253 254 255%%------------------------------------------------------------------ 256%% This function is a simple consistency check 257%% function which could be used by the user-defined 258%% table functions. 259%% Check if the row has all information needed to 260%% make row notInService (from notReady). This is 261%% a simple check, which just checks if some col 262%% in the row has the value 'noinit'. 263%% If it has the information, the status is changed 264%% to notInService. 265%%------------------------------------------------------------------ 266table_try_make_consistent(RowIndex, NewDbRow) -> 267 Name = element(1, NewDbRow), 268 #table_info{first_own_index = FirstOwnIndex, 269 status_col = StatusCol, index_types = IT} = 270 snmp_generic:table_info(Name), 271 if 272 is_integer(StatusCol) -> 273 NewRow = make_row(NewDbRow, FirstOwnIndex), 274 StatusVal = element(StatusCol, NewRow), 275 AddCol = if 276 FirstOwnIndex == 0 -> 2; 277 true -> 1 + FirstOwnIndex - length(IT) 278 end, 279 table_try_make_consistent(Name, RowIndex, NewRow, NewDbRow, 280 AddCol, StatusCol, StatusVal); 281 true -> 282 {noError, 0} 283 end. 284 285 286table_try_make_consistent(Name, RowIndex, NewRow, NewDbRow, 287 AddCol, StatusCol, ?'RowStatus_notReady') -> 288 case lists:member(noinit, tuple_to_list(NewRow)) of 289 true -> {noError, 0}; 290 false -> 291 table_set_element(Name, RowIndex, StatusCol, 292 ?'RowStatus_notInService'), 293 NewDbRow2 = set_new_row([{StatusCol, ?'RowStatus_notInService'}], 294 AddCol, NewDbRow), 295 mnesia:write(NewDbRow2), 296 {noError, 0} 297 end; 298 299table_try_make_consistent(_Name, _RowIndex, _NewRow, _NewDBRow, 300 _AddCol, _StatusCol, _StatusVal) -> 301 {noError, 0}. 302 303%%------------------------------------------------------------------ 304%% Constructs a row that is to be stored in Mnesia, i.e. 305%% {Name, Key, Col1, ...} | 306%% {Name, {Key1, Key2, ..}, ColN, ColN+1...} 307%% dynamic key values are stored without length first. 308%% RowIndex is a list of the first elements. RowStatus is needed, 309%% because the provided value may not be stored, e.g. createAndGo 310%% should be active. If a value isn't specified in the Col list, 311%% then the corresponding value will be noinit. 312%%------------------------------------------------------------------ 313table_construct_row(Name, RowIndex, Status, Cols) -> 314 #table_info{nbr_of_cols = LastCol, index_types = Indexes, 315 defvals = Defs, status_col = StatusCol, 316 first_own_index = FirstOwnIndex, not_accessible = NoAccs} = 317 snmp_generic:table_info(Name), 318 KeyList = snmp_generic:split_index_to_keys(Indexes, RowIndex), 319 OwnKeyList = snmp_generic:get_own_indexes(FirstOwnIndex, KeyList), 320 StartCol = length(OwnKeyList) + 1, 321 RowList = snmp_generic:table_create_rest(StartCol, LastCol, 322 StatusCol, Status, Cols, NoAccs), 323 L = snmp_generic:init_defaults(Defs, RowList, StartCol), 324 Keys = case KeyList of 325 [H] -> H; 326 _ -> list_to_tuple(KeyList) 327 end, 328 list_to_tuple([Name, Keys | L]). 329 330%%------------------------------------------------------------------ 331%% table_set_cols/4 332%% can be used by the set procedure of all tables 333%% to set all columns in Cols, one at a time. 334%% ConsFunc is a check-consistency function, which will 335%% be called with the RowIndex of this row, when 336%% all columns are set. This is useful when the RowStatus 337%% could change, e.g. if the manager has provided all 338%% mandatory columns in this set operation. 339%% If ConsFunc is nofunc, no function will be called after all 340%% sets. 341%% Returns: {noError, 0} | {Error, Col} 342%%------------------------------------------------------------------ 343table_set_cols(Name, RowIndex, Cols, ConsFunc) -> 344 table_set_elements(Name, RowIndex, Cols, ConsFunc). 345 346%%----------------------------------------------------------------- 347%% Col is _not_ a key column. A row in the db is stored as 348%% {Name, {Key1, Key2,...}, Col1, Col2, ...} 349%%----------------------------------------------------------------- 350table_set_element(Name, RowIndex, Col, NewVal) -> 351 #table_info{index_types = Indexes, first_own_index = FirstOwnIndex} = 352 snmp_generic:table_info(Name), 353 DbCol = if 354 FirstOwnIndex == 0 -> Col + 2; 355 true -> 1 + FirstOwnIndex - length(Indexes) + Col 356 end, 357 case mnesia:snmp_get_row(Name, RowIndex) of 358 {ok, DbRow} -> 359 NewDbRow = setelement(DbCol, DbRow, NewVal), 360 mnesia:write(NewDbRow), 361 true; 362 undefined -> 363 false 364 end. 365 366table_set_elements(Name, RowIndex, Cols) -> 367 case table_set_elements(Name, RowIndex, Cols, nofunc) of 368 {noError, 0} -> true; 369 _ -> false 370 end. 371table_set_elements(Name, RowIndex, Cols, ConsFunc) -> 372 #table_info{index_types = Indexes, 373 first_own_index = FirstOwnIndex} = 374 snmp_generic:table_info(Name), 375 AddCol = if 376 FirstOwnIndex == 0 -> 2; 377 true -> 1 + FirstOwnIndex - length(Indexes) 378 end, 379 case mnesia:snmp_get_row(Name, RowIndex) of 380 {ok, DbRow} -> 381 NewDbRow = set_new_row(Cols, AddCol, DbRow), 382 mnesia:write(NewDbRow), 383 snmp_generic:try_apply(ConsFunc, [RowIndex, NewDbRow]); 384 undefined -> 385 {Col, _Val} = hd(Cols), 386 {commitFailed, Col} 387 end. 388 389set_new_row([{Col, Val} | Cols], AddCol, Row) -> 390 set_new_row(Cols, AddCol, setelement(Col+AddCol, Row, Val)); 391set_new_row([], _AddCol, Row) -> 392 Row. 393 394table_next(Name, RestOid) -> 395 case mnesia:snmp_get_next_index(Name, RestOid) of 396 {ok, NextIndex} -> NextIndex; 397 endOfTable -> endOfTable 398 end. 399 400 401user_err(F, A) -> 402 snmpa_error:user_err(F, A). 403