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