1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2019. 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_local_db).
21
22-include_lib("kernel/include/file.hrl").
23-include("snmpa_internal.hrl").
24-include("snmp_types.hrl").
25-include("STANDARD-MIB.hrl").
26
27%% -define(VMODULE, "LDB").
28-include("snmp_verbosity.hrl").
29
30
31%% External exports
32-export([start_link/3, start_link/4, stop/0, info/0, verbosity/1]).
33-export([dump/0, backup/1,
34	 register_notify_client/2, unregister_notify_client/1]).
35-export([table_func/2, table_func/4,
36	 variable_get/1, variable_set/2, variable_delete/1, variable_inc/2,
37	 table_create/1, table_exists/1, table_delete/1,
38         table_create_row/3, table_create_row/4, table_construct_row/4,
39	 table_delete_row/2,
40	 table_get_row/2,
41         table_get_element/3, table_get_elements/4,
42	 table_set_elements/3, table_set_status/7,
43         table_next/2,
44	 table_max_col/2,
45	 table_get/1]).
46
47-export([get_elements/2]).
48
49-export([match/2]).
50
51%% Debug exports
52-export([print/0, print/1, print/2]).
53
54%% Internal exports
55-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
56	 code_change/3]).
57
58
59-define(BACKUP_TAB, snmpa_local_backup).
60-define(DETS_TAB,   snmpa_local_db1).
61-define(ETS_TAB,    snmpa_local_db2).
62-define(SERVER,     ?MODULE).
63
64-record(state, {dets, ets, notify_clients = [], backup}).
65-record(dets,  {tab, shadow}).
66
67%% -define(snmp_debug,true).
68-include("snmp_debug.hrl").
69
70
71-ifdef(snmp_debug).
72-define(GS_START_LINK(Prio, Dir, DbInitError, Opts),
73        gen_server:start_link({local, ?SERVER}, ?MODULE,
74			      [Prio, Dir, DbInitError, Opts],
75                              [{debug,[trace]}])).
76-else.
77-define(GS_START_LINK(Prio, Dir, DbInitError, Opts),
78        gen_server:start_link({local, ?SERVER}, ?MODULE,
79			      [Prio, Dir, DbInitError, Opts],
80			      [])).
81-endif.
82
83
84%%%-----------------------------------------------------------------
85%%% Implements a general database in which its possible
86%%% to store variables and tables. Provide functions for
87%%% tableaccess by row or by element, and for next.
88%%%
89%%% Opts = [Opt]
90%%% Opt = {auto_repair, false | true | true_verbose} |
91%%%       {verbosity,silence | log | debug}
92%%%-----------------------------------------------------------------
93start_link(Prio, DbDir, Opts) when is_list(Opts) ->
94    start_link(Prio, DbDir, terminate, Opts).
95
96start_link(Prio, DbDir, DbInitError, Opts) when is_list(Opts) ->
97    ?d("start_link -> entry with"
98	"~n   Prio:        ~p"
99	"~n   DbDir:       ~p"
100	"~n   DbInitError: ~p"
101	"~n   Opts:        ~p", [Prio, DbDir, DbInitError, Opts]),
102    ?GS_START_LINK(Prio, DbDir, DbInitError, Opts).
103
104stop() ->
105    call(stop).
106
107register_notify_client(Client,Module) ->
108    call({register_notify_client,Client,Module}).
109
110
111unregister_notify_client(Client) ->
112    call({unregister_notify_client,Client}).
113
114backup(BackupDir) ->
115    call({backup, BackupDir}).
116
117dump() ->
118    call(dump).
119
120info() ->
121    call(info).
122
123verbosity(Verbosity) ->
124    cast({verbosity,Verbosity}).
125
126
127%%%-----------------------------------------------------------------
128
129init([Prio, DbDir, DbInitError, Opts]) ->
130    ?d("init -> entry with"
131	"~n   Prio:        ~p"
132	"~n   DbDir:       ~p"
133	"~n   DbInitError: ~p"
134	"~n   Opts:        ~p", [Prio, DbDir, DbInitError, Opts]),
135    case (catch do_init(Prio, DbDir, DbInitError, Opts)) of
136	{ok, State} ->
137	    ?vdebug("started",[]),
138	    {ok, State};
139	{error, Reason} ->
140	    config_err("failed starting local-db: ~n~p", [Reason]),
141	    {stop, {error, Reason}};
142	Error ->
143	    config_err("failed starting local-db: ~n~p", [Error]),
144	    {stop, {error, Error}}
145    end.
146
147do_init(Prio, DbDir, DbInitError, Opts) ->
148    process_flag(priority, Prio),
149    process_flag(trap_exit, true),
150    put(sname,     get_opt(sname, Opts, ldb)),
151    put(verbosity, get_opt(verbosity, Opts, ?default_verbosity)),
152    ?vlog("starting",[]),
153    Dets = dets_open(DbDir, DbInitError, Opts),
154    Ets  = ets:new(?ETS_TAB, [set, protected]),
155    ?vdebug("started",[]),
156    put(started,   snmp_misc:formated_timestamp()),
157    {ok, #state{dets = Dets, ets = Ets}}.
158
159dets_open(DbDir, DbInitError, Opts) ->
160    Name     = ?DETS_TAB,
161    Filename = dets_filename(Name, DbDir),
162    case file:read_file_info(Filename) of
163	{ok, _} ->
164	    %% File exists
165	    case do_dets_open(Name, Filename, Opts) of
166		{ok, Dets} ->
167		    ?vdebug("dets open done",[]),
168		    Shadow = ets:new(snmp_local_db1_shadow, [set, protected]),
169		    dets:to_ets(Dets, Shadow),
170		    ?vtrace("shadow table created and populated",[]),
171		    #dets{tab = Dets, shadow = Shadow};
172		{error, Reason1} ->
173                    user_err("Corrupt local database: ~p", [Filename]),
174		    case DbInitError of
175			terminate ->
176			    throw({error, {failed_open_dets, Reason1}});
177			_ ->
178			    Saved = Filename ++ ".saved",
179			    file:rename(Filename, Saved),
180			    case do_dets_open(Name, Filename, Opts) of
181				{ok, Dets} ->
182				    Shadow = ets:new(snmp_local_db1_shadow,
183						     [set, protected]),
184				    #dets{tab = Dets, shadow = Shadow};
185				{error, Reason2} ->
186				    user_err("Could not create local "
187					     "database: ~p"
188					     "~n   ~p"
189					     "~n   ~p",
190					     [Filename, Reason1, Reason2]),
191				    throw({error, {failed_open_dets, Reason2}})
192			    end
193		    end
194	    end;
195	_ ->
196	    case DbInitError of
197		create_db_and_dir ->
198		    ok = filelib:ensure_dir(Filename);
199		_ ->
200		    ok
201	    end,
202	    case do_dets_open(Name, Filename, Opts) of
203		{ok, Dets} ->
204		    ?vdebug("dets open done",[]),
205		    Shadow = ets:new(snmp_local_db1_shadow, [set, protected]),
206		    ?vtrace("shadow table created",[]),
207		    #dets{tab = Dets, shadow = Shadow};
208		{error, Reason} ->
209		    user_err("Could not create local database ~p"
210			     "~n   ~p", [Filename, Reason]),
211		    throw({error, {failed_open_dets, Reason}})
212	    end
213    end.
214
215do_dets_open(Name, Filename, Opts) ->
216    Repair   = get_opt(repair, Opts, true),
217    AutoSave = get_opt(auto_save, Opts, 5000),
218    Args = [{auto_save, AutoSave},
219	    {file,      Filename},
220	    {repair,    Repair}],
221    dets:open_file(Name, Args).
222
223
224dets_filename(Name, Dir) when is_atom(Name) ->
225    dets_filename(atom_to_list(Name), Dir);
226dets_filename(Name, Dir) ->
227    filename:join(dets_filename1(Dir), Name).
228
229dets_filename1([])  -> ".";
230dets_filename1(Dir) -> Dir.
231
232
233%%-----------------------------------------------------------------
234%% Interface functions.
235%%-----------------------------------------------------------------
236
237%%-----------------------------------------------------------------
238%% Functions for debugging.
239%%-----------------------------------------------------------------
240print()          -> call(print).
241print(Table)     -> call({print,Table,volatile}).
242print(Table, Db) -> call({print,Table,Db}).
243
244variable_get({Name, Db}) ->
245    call({variable_get, Name, Db});
246variable_get(Name) ->
247    call({variable_get, Name, volatile}).
248
249variable_set({Name, Db}, Val) ->
250    call({variable_set, Name, Db, Val});
251variable_set(Name, Val) ->
252    call({variable_set, Name, volatile, Val}).
253
254variable_inc({Name, Db}, N) ->
255    cast({variable_inc, Name, Db, N});
256variable_inc(Name, N) ->
257    cast({variable_inc, Name, volatile, N}).
258
259variable_delete({Name, Db}) ->
260    call({variable_delete, Name, Db});
261variable_delete(Name) ->
262    call({variable_delete, Name, volatile}).
263
264
265table_create({Name, Db}) ->
266    call({table_create, Name, Db});
267table_create(Name) ->
268    call({table_create, Name, volatile}).
269
270table_exists({Name, Db}) ->
271    call({table_exists, Name, Db});
272table_exists(Name) ->
273    call({table_exists, Name, volatile}).
274
275table_delete({Name, Db}) ->
276    call({table_delete, Name, Db});
277table_delete(Name) ->
278    call({table_delete, Name, volatile}).
279
280table_delete_row({Name, Db}, RowIndex) ->
281    call({table_delete_row, Name, Db, RowIndex});
282table_delete_row(Name, RowIndex) ->
283    call({table_delete_row, Name, volatile, RowIndex}).
284
285table_get_row({Name, Db}, RowIndex) ->
286    call({table_get_row, Name, Db, RowIndex});
287table_get_row(Name, RowIndex) ->
288    call({table_get_row, Name, volatile, RowIndex}).
289
290table_get_element({Name, Db}, RowIndex, Col) ->
291    call({table_get_element, Name, Db, RowIndex, Col});
292table_get_element(Name, RowIndex, Col) ->
293    call({table_get_element, Name, volatile, RowIndex, Col}).
294
295table_set_elements({Name, Db}, RowIndex, Cols) ->
296    call({table_set_elements, Name, Db, RowIndex, Cols});
297table_set_elements(Name, RowIndex, Cols) ->
298    call({table_set_elements, Name, volatile, RowIndex, Cols}).
299
300table_next({Name, Db}, RestOid) ->
301    call({table_next, Name, Db, RestOid});
302table_next(Name, RestOid) ->
303    call({table_next, Name, volatile, RestOid}).
304
305table_max_col({Name, Db}, Col) ->
306    call({table_max_col, Name, Db, Col});
307table_max_col(Name, Col) ->
308    call({table_max_col, Name, volatile, Col}).
309
310table_create_row({Name, Db}, RowIndex, Row) ->
311    call({table_create_row, Name, Db,RowIndex, Row});
312table_create_row(Name, RowIndex, Row) ->
313    call({table_create_row, Name, volatile, RowIndex, Row}).
314table_create_row(NameDb, RowIndex, Status, Cols) ->
315    Row = table_construct_row(NameDb, RowIndex, Status, Cols),
316    table_create_row(NameDb, RowIndex, Row).
317
318match({Name, Db}, Pattern) ->
319    call({match, Name, Db, Pattern});
320match(Name, Pattern) ->
321    call({match, Name, volatile, Pattern}).
322
323
324table_get(Table) ->
325    table_get(Table, [], []).
326
327table_get(Table, Idx, Acc) ->
328    case table_next(Table, Idx) of
329	endOfTable ->
330            lists:reverse(Acc);
331	NextIdx ->
332	    case table_get_row(Table, NextIdx) of
333		undefined ->
334		    {error, {failed_get_row, NextIdx, lists:reverse(Acc)}};
335		Row ->
336		    NewAcc = [{NextIdx, Row}|Acc],
337		    table_get(Table, NextIdx, NewAcc)
338	    end
339    end.
340
341
342%%-----------------------------------------------------------------
343%% Implements the variable functions.
344%%-----------------------------------------------------------------
345handle_call({variable_get, Name, Db}, _From, State) ->
346    ?vlog("variable get: ~p [~p]",[Name, Db]),
347    {reply, lookup(Db, Name, State), State};
348
349handle_call({variable_set, Name, Db, Val}, _From, State) ->
350    ?vlog("variable ~p set [~p]: "
351	  "~n   Val:  ~p",[Name, Db, Val]),
352    {reply, insert(Db, Name, Val, State), State};
353
354handle_call({variable_delete, Name, Db}, _From, State) ->
355    ?vlog("variable delete: ~p [~p]",[Name, Db]),
356    {reply, delete(Db, Name, State), State};
357
358
359%%-----------------------------------------------------------------
360%% Implements the table functions.
361%%-----------------------------------------------------------------
362%% Entry in ets for a tablerow:
363%% Key = {<tableName>, <(flat) list of indexes>}
364%% Val = {{Row}, <Prev>, <Next>}
365%% Where Prev and Next = <list of indexes>; "pointer to prev/next"
366%% Each table is a double linked list, with a head-element, with
367%% direct access to each individual element.
368%% Head-el: Key = {<tableName>, first}
369%% Operations:
370%% table_create_row(<tableName>, <list of indexes>, <row>)   O(n)
371%% table_delete_row(<tableName>, <list of indexes>)          O(1)
372%% get(<tableName>, <list of indexes>, Col)            O(1)
373%% set(<tableName>, <list of indexes>, Col, Val)       O(1)
374%% next(<tableName>, <list of indexes>)   if Row exist O(1), else O(n)
375%%-----------------------------------------------------------------
376handle_call({table_create, Name, Db}, _From, State) ->
377    ?vlog("table create: ~p [~p]",[Name, Db]),
378    catch handle_delete(Db, Name, State),
379    {reply, insert(Db, {Name, first}, {undef, first, first}, State), State};
380
381handle_call({table_exists, Name, Db}, _From, State) ->
382    ?vlog("table exist: ~p [~p]",[Name, Db]),
383    Res =
384	case lookup(Db, {Name, first}, State) of
385	    {value, _} -> true;
386	    undefined -> false
387	end,
388    ?vdebug("table exist result: "
389	    "~n   ~p",[Res]),
390    {reply, Res, State};
391
392handle_call({table_delete, Name, Db}, _From, State) ->
393    ?vlog("table delete: ~p [~p]",[Name, Db]),
394    catch handle_delete(Db, Name, State),
395    {reply, true, State};
396
397handle_call({table_create_row, Name, Db, Indexes, Row}, _From, State) ->
398    ?vlog("table create row [~p]: "
399	  "~n   Name:    ~p"
400	  "~n   Indexes: ~p"
401	  "~n   Row:     ~p",[Db, Name, Indexes, Row]),
402    Res =
403	case catch handle_create_row(Db, Name, Indexes, Row, State) of
404	    {'EXIT', _} -> false;
405	    _ -> true
406	end,
407    ?vdebug("table create row result: "
408	    "~n   ~p",[Res]),
409    {reply, Res, State};
410
411handle_call({table_delete_row, Name, Db, Indexes}, _From, State) ->
412    ?vlog("table delete row [~p]: "
413	  "~n   Name:    ~p"
414	  "~n   Indexes: ~p", [Db, Name, Indexes]),
415    Res =
416	case catch handle_delete_row(Db, Name, Indexes, State) of
417	    {'EXIT', _} -> false;
418	    _ -> true
419	end,
420    ?vdebug("table delete row result: "
421	    "~n   ~p",[Res]),
422    {reply, Res, State};
423
424handle_call({table_get_row, Name, Db, Indexes}, _From, State) ->
425    ?vlog("table get row [~p]: "
426	  "~n   Name:    ~p"
427	  "~n   Indexes: ~p",[Db, Name, Indexes]),
428    Res = case lookup(Db, {Name, Indexes}, State) of
429	      undefined ->
430		  undefined;
431	      {value, {Row, _Prev, _Next}} ->
432		  Row
433	  end,
434    ?vdebug("table get row result: "
435	    "~n   ~p",[Res]),
436    {reply, Res, State};
437
438handle_call({table_get_element, Name, Db, Indexes, Col}, _From, State) ->
439    ?vlog("table ~p get element [~p]: "
440	  "~n   Indexes: ~p"
441	  "~n   Col:     ~p", [Name, Db, Indexes, Col]),
442    Res = case lookup(Db, {Name, Indexes}, State) of
443	      undefined -> undefined;
444	      {value, {Row, _Prev, _Next}} -> {value, element(Col, Row)}
445	  end,
446    ?vdebug("table get element result: "
447	    "~n   ~p",[Res]),
448    {reply, Res, State};
449
450handle_call({table_set_elements, Name, Db, Indexes, Cols}, _From, State) ->
451    ?vlog("table ~p set element [~p]: "
452	  "~n   Indexes: ~p"
453	  "~n   Cols:    ~p", [Name, Db, Indexes, Cols]),
454    Res =
455	case catch handle_set_elements(Db, Name, Indexes, Cols, State) of
456	    {'EXIT', _} -> false;
457	    _ -> true
458	end,
459    ?vdebug("table set element result: "
460	    "~n   ~p",[Res]),
461    {reply, Res, State};
462
463handle_call({table_next, Name, Db, []}, From, State) ->
464    ?vlog("table next: ~p [~p]",[Name, Db]),
465    handle_call({table_next, Name, Db, first}, From, State);
466
467handle_call({table_next, Name, Db, Indexes}, _From, State) ->
468    ?vlog("table ~p next [~p]: "
469	  "~n   Indexes: ~p",[Name, Db, Indexes]),
470    Res = case lookup(Db, {Name, Indexes}, State) of
471	      {value, {_Row, _Prev, Next}} ->
472		  if
473		      Next =:= first -> endOfTable;
474		      true -> Next
475		  end;
476	      undefined ->
477		  table_search_next(Db, Name, Indexes, State)
478	  end,
479    ?vdebug("table next result: "
480	    "~n   ~p",[Res]),
481    {reply, Res, State};
482
483handle_call({table_max_col, Name, Db, Col}, _From, State) ->
484    ?vlog("table ~p max col [~p]: "
485	  "~n   Col: ~p",[Name, Db, Col]),
486    Res = table_max_col(Db, Name, Col, 0, first, State),
487    ?vdebug("table max col result: "
488	    "~n   ~p",[Res]),
489    {reply, Res, State};
490
491handle_call({match, Name, Db, Pattern}, _From, State) ->
492    ?vlog("match ~p [~p]:"
493	"~n   Pat: ~p", [Name, Db, Pattern]),
494    L1 = match(Db, Name, Pattern, State),
495    {reply, lists:delete([undef], L1), State};
496
497%% This check (that there is no backup already in progress) is also
498%% done in the master agent process, but just in case a user issues
499%% a backup call to this process directly, we add a similar check here.
500handle_call({backup, BackupDir}, From,
501	    #state{backup = undefined, dets = Dets} = State) ->
502    ?vlog("backup: ~p",[BackupDir]),
503    Pid = self(),
504    V   = get(verbosity),
505    case file:read_file_info(BackupDir) of
506	{ok, #file_info{type = directory}} ->
507	    BackupServer =
508		erlang:spawn_link(
509		  fun() ->
510			  put(sname, albs),
511			  put(verbosity, V),
512			  Dir   = filename:join([BackupDir]),
513			  #dets{tab = Tab} = Dets,
514			  Reply = handle_backup(Tab, Dir),
515			  Pid ! {backup_done, Reply},
516			  unlink(Pid)
517		  end),
518	    ?vtrace("backup server: ~p", [BackupServer]),
519	    {noreply, State#state{backup = {BackupServer, From}}};
520	{ok, _} ->
521	    {reply, {error, not_a_directory}, State};
522	Error ->
523	    {reply, Error, State}
524    end;
525
526handle_call({backup, _BackupDir}, _From, #state{backup = Backup} = S) ->
527    ?vinfo("backup already in progress: ~p", [Backup]),
528    {reply, {error, backup_in_progress}, S};
529
530handle_call(dump, _From, #state{dets = Dets} = State) ->
531    ?vlog("dump",[]),
532    dets_sync(Dets),
533    {reply, ok, State};
534
535handle_call(info, _From, #state{dets = Dets, ets = Ets} = State) ->
536    ?vlog("info",[]),
537    Info = get_info(Dets, Ets),
538    {reply, Info, State};
539
540handle_call(print, _From, #state{dets = Dets, ets = Ets} = State) ->
541    ?vlog("print",[]),
542    L1 = ets:tab2list(Ets),
543    L2 = dets_match_object(Dets, '_'),
544    {reply, {{ets, L1}, {dets, L2}}, State};
545
546handle_call({print, Table, Db}, _From, State) ->
547    ?vlog("print: ~p [~p]", [Table, Db]),
548    L = match(Db, Table, '$1', State),
549    {reply, lists:delete([undef], L), State};
550
551handle_call({register_notify_client, Client, Module}, _From, State) ->
552    ?vlog("register_notify_client: "
553	"~n   Client: ~p"
554	"~n   Module: ~p", [Client, Module]),
555    Nc = State#state.notify_clients,
556    case lists:keysearch(Client,1,Nc) of
557	{value,{Client,Mod}} ->
558	    ?vlog("register_notify_client: already registered to: ~p",
559		  [Module]),
560	    {reply, {error,{already_registered,Mod}}, State};
561	false ->
562	    {reply, ok, State#state{notify_clients = [{Client,Module}|Nc]}}
563    end;
564
565handle_call({unregister_notify_client, Client}, _From, State) ->
566    ?vlog("unregister_notify_client: ~p",[Client]),
567    Nc = State#state.notify_clients,
568    case lists:keysearch(Client,1,Nc) of
569	{value,{Client,_Module}} ->
570	    Nc1 = lists:keydelete(Client,1,Nc),
571	    {reply, ok, State#state{notify_clients = Nc1}};
572	false ->
573	    ?vlog("unregister_notify_client: not registered",[]),
574	    {reply, {error,not_registered}, State}
575    end;
576
577handle_call(stop, _From, State) ->
578    ?vlog("stop",[]),
579    {stop, normal, stopped, State};
580
581handle_call(Req, _From, State) ->
582    warning_msg("received unknown request: ~n~p", [Req]),
583    Reply = {error, {unknown, Req}},
584    {reply, Reply, State}.
585
586
587handle_cast({variable_inc, Name, Db, N}, State) ->
588    ?vlog("variable ~p inc"
589	  "~n   N: ~p", [Name, N]),
590    M = case lookup(Db, Name, State) of
591	    {value, Val} -> Val;
592	    _ -> 0
593	end,
594    insert(Db, Name, (M+N) rem 4294967296, State),
595    {noreply, State};
596
597handle_cast({verbosity,Verbosity}, State) ->
598    ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]),
599    put(verbosity,?vvalidate(Verbosity)),
600    {noreply, State};
601
602handle_cast(Msg, State) ->
603    warning_msg("received unknown message: ~n~p", [Msg]),
604    {noreply, State}.
605
606
607handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) ->
608    ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]),
609    gen_server:reply(From, {error, Reason}),
610    {noreply, S#state{backup = undefined}};
611
612handle_info({'EXIT', Pid, Reason}, S) ->
613    %% The only other processes we should be linked to are
614    %% either the master agent or our supervisor, so die...
615    {stop, {received_exit, Pid, Reason}, S};
616
617handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) ->
618    ?vlog("backup done:"
619	  "~n   Reply: ~p", [Reply]),
620    gen_server:reply(From, Reply),
621    {noreply, S#state{backup = undefined}};
622
623handle_info(Info, State) ->
624    warning_msg("received unknown info: ~n~p", [Info]),
625    {noreply, State}.
626
627
628terminate(Reason, State) ->
629    ?vlog("terminate: ~p", [Reason]),
630    close(State).
631
632
633%%----------------------------------------------------------
634%% Code change
635%%----------------------------------------------------------
636
637%% downgrade
638%%
639code_change({down, _Vsn}, S1, downgrade_to_pre_4_11) ->
640    #state{dets = D} = S1,
641    #dets{tab = Dets, shadow = Shadow} = D,
642    ets:delete(Shadow),
643    S2 = S1#state{dets = Dets},
644    {ok, S2};
645
646%% upgrade
647%%
648code_change(_Vsn, S1, upgrade_from_pre_4_11) ->
649    #state{dets = D} = S1,
650    Shadow = ets:new(snmp_local_db1_shadow, [set, protected]),
651    dets:to_ets(D, Shadow),
652    Dets =  #dets{tab = D, shadow = Shadow},
653    S2 = S1#state{dets = Dets},
654    {ok, S2};
655
656code_change(_Vsn, State, _Extra) ->
657    {ok, State}.
658
659
660
661%%----------------------------------------------------------
662%% Backup
663%%----------------------------------------------------------
664
665handle_backup(D, BackupDir) ->
666    %% First check that we do not wrote to the corrent db-dir...
667    ?vtrace("handle_backup -> entry with"
668	"~n   D:         ~p"
669	"~n   BackupDir: ~p", [D, BackupDir]),
670    case dets:info(D, filename) of
671	undefined ->
672	    ?vinfo("handle_backup -> no file to backup", []),
673	    {error, no_file};
674	Filename ->
675	    ?vinfo("handle_backup -> file to backup: ~n   ~p", [Filename]),
676	    case filename:dirname(Filename) of
677		BackupDir ->
678		    ?vinfo("handle_backup -> backup dir and db dir the same",
679			   []),
680		    {error, db_dir};
681		_ ->
682		    case file:read_file_info(BackupDir) of
683			{ok, #file_info{type = directory}} ->
684			    ?vdebug("handle_backup -> backup dir ok", []),
685			    %% All well so far...
686			    Type = dets:info(D, type),
687			    KP   = dets:info(D, keypos),
688			    dets_backup(D,
689					filename:basename(Filename),
690					BackupDir, Type, KP);
691			{ok, _} ->
692			    ?vinfo("handle_backup -> backup dir not a dir",
693				   []),
694			    {error, not_a_directory};
695			Error ->
696			    ?vinfo("handle_backup -> Error: ~p", [Error]),
697			    Error
698		    end
699	    end
700    end.
701
702dets_backup(D, Filename, BackupDir, Type, KP) ->
703    ?vtrace("dets_backup -> entry with"
704	    "~n   D:         ~p"
705	    "~n   Filename:  ~p"
706	    "~n   BackupDir: ~p", [D, Filename, BackupDir]),
707    BackupFile = filename:join(BackupDir, Filename),
708    ?vtrace("dets_backup -> "
709	    "~n   BackupFile: ~p", [BackupFile]),
710    Opts = [{file, BackupFile}, {type, Type}, {keypos, KP}],
711    case dets:open_file(?BACKUP_TAB, Opts) of
712	{ok, B} ->
713	    F = fun(Arg) ->
714			dets_backup(Arg, start, D, B)
715		end,
716	    ?vtrace("dets_backup -> fix table", []),
717	    dets:safe_fixtable(D, true),
718	    ?vtrace("dets_backup -> copy table", []),
719	    Res = dets:init_table(?BACKUP_TAB, F, [{format, bchunk}]),
720	    ?vtrace("dets_backup -> unfix table", []),
721	    dets:safe_fixtable(D, false),
722	    ?vtrace("dets_backup -> Res: ~p", [Res]),
723	    Res;
724	Error ->
725	    ?vinfo("dets_backup -> open_file failed: "
726		   "~n   ~p", [Error]),
727	    Error
728    end.
729
730
731dets_backup(close, _Cont, _D, B) ->
732    dets:close(B),
733    ok;
734dets_backup(read, Cont1, D, B) ->
735    case dets:bchunk(D, Cont1) of
736	{error, _} = ERROR ->
737	    ERROR;
738	'$end_of_table' ->
739	    dets:close(B),
740	    end_of_input;
741	{Cont2, Data} ->
742	    F = fun(Arg) ->
743			dets_backup(Arg, Cont2, D, B)
744		end,
745	    {Data, F}
746    end.
747
748
749%%-----------------------------------------------------------------
750%% All handle_ functions exists so we can catch the call to
751%% them, because we don't want to crash the server if a user
752%% forgets to call e.g. table_create.
753%%-----------------------------------------------------------------
754%% Find larger element and insert before.
755handle_create_row(Db, Name, Indexes, Row, State) ->
756    case table_find_first_after_maybe_same(Db, Name, Indexes, State) of
757	{{Name, Next}, {NRow, NPrev, NNext}} ->
758	    {value, {PRow, PPrev, _PNext}} = lookup(Db, {Name, NPrev}, State),
759	    if
760		Next =:= NPrev ->
761		    % Insert before first
762		    insert(Db, {Name, NPrev}, {PRow, Indexes, Indexes}, State);
763		true ->
764		    insert(Db, {Name, NPrev}, {PRow, PPrev, Indexes}, State),
765		    insert(Db, {Name, Next}, {NRow, Indexes, NNext}, State)
766	    end,
767	    insert(Db, {Name, Indexes}, {Row, NPrev, Next}, State);
768	{same_row, {Prev, Next}} ->
769	    insert(Db, {Name, Indexes}, {Row, Prev, Next}, State)
770    end.
771
772handle_delete_row(Db, Name, Indexes, State) ->
773    {value, {_, Prev, Next}} = lookup(Db, {Name, Indexes}, State),
774    {value, {PRow, PPrev, Indexes}} = lookup(Db, {Name, Prev}, State),
775    insert(Db, {Name, Prev}, {PRow, PPrev, Next}, State),
776    {value, {NRow, Indexes, NNext}} = lookup(Db, {Name, Next}, State),
777    insert(Db, {Name, Next}, {NRow, Prev, NNext}, State),
778    delete(Db, {Name, Indexes}, State).
779
780handle_set_elements(Db, Name, Indexes, Cols, State) ->
781    {value, {Row, Prev, Next}} = lookup(Db, {Name, Indexes}, State),
782    NewRow = set_new_row(Cols, Row),
783    insert(Db, {Name, Indexes}, {NewRow, Prev, Next}, State).
784
785set_new_row([{Col, Val} | Cols], Row) ->
786    set_new_row(Cols, setelement(Col, Row, Val));
787set_new_row([], Row) ->
788    Row.
789
790handle_delete(Db, Name, State) ->
791    {value, {_, _, Next}} = lookup(Db, {Name, first}, State),
792    delete(Db, {Name, first}, State),
793    handle_delete(Db, Name, Next, State).
794handle_delete(_Db, _Name, first, _State) -> true;
795handle_delete(Db, Name, Indexes, State) ->
796    {value, {_, _, Next}} = lookup(Db, {Name, Indexes}, State),
797    delete(Db, {Name, Indexes}, State),
798    handle_delete(Db, Name, Next, State).
799
800%%-----------------------------------------------------------------
801%% Implementation of next.
802%%-----------------------------------------------------------------
803table_search_next(Db, Name, Indexes, State) ->
804    case catch table_find_first_after(Db, Name, Indexes, State) of
805	{{Name, Key}, {_, _, _Next}} ->
806	    case Key of
807		first -> endOfTable;
808		_ -> Key
809	    end;
810	{'EXIT', _} -> endOfTable
811    end.
812
813table_find_first_after(Db, Name, Indexes, State) ->
814    {value, {_Row, _Prev, Next}} = lookup(Db, {Name, first}, State),
815    table_loop(Db, Name, Indexes, Next, State).
816
817table_loop(Db, Name, _Indexes, first, State) ->
818    {value, FirstVal} = lookup(Db, {Name, first}, State),
819    {{Name, first}, FirstVal};
820
821table_loop(Db, Name, Indexes, Cur, State) ->
822    {value, {Row, Prev, Next}} = lookup(Db, {Name, Cur}, State),
823    if
824	Cur > Indexes ->
825	    {{Name, Cur}, {Row, Prev, Next}};
826	true ->
827	    table_loop(Db, Name, Indexes, Next, State)
828    end.
829
830table_find_first_after_maybe_same(Db, Name, Indexes, State) ->
831    {value, {_Row, _Prev, Next}} = lookup(Db, {Name, first}, State),
832    table_loop2(Db, Name, Indexes, Next, State).
833
834table_loop2(Db, Name, _Indexes, first, State) ->
835    {value, FirstVal} = lookup(Db, {Name, first}, State),
836    {{Name, first}, FirstVal};
837
838table_loop2(Db, Name, Indexes, Cur, State) ->
839    {value, {Row, Prev, Next}} = lookup(Db, {Name, Cur}, State),
840    if
841	Cur > Indexes ->
842	    {{Name, Cur}, {Row, Prev, Next}};
843	Cur =:= Indexes ->
844	    {same_row, {Prev, Next}};
845	true ->
846	    table_loop2(Db, Name, Indexes, Next, State)
847    end.
848
849%%-----------------------------------------------------------------
850%% Implementation of max.
851%% The value in a column could be noinit or undefined,
852%% so we must check only those with an integer.
853%%-----------------------------------------------------------------
854table_max_col(Db, Name, Col, Max, Indexes, State) ->
855    case lookup(Db, {Name, Indexes}, State) of
856	{value, {Row, _Prev, Next}} ->
857	    if
858		Next =:= first ->
859		    if
860			is_integer(element(Col, Row)) andalso
861			(element(Col, Row) > Max) ->
862			    element(Col, Row);
863			true ->
864			    Max
865		    end;
866		is_integer(element(Col, Row)) andalso
867		(element(Col, Row) > Max) ->
868		    table_max_col(Db,Name, Col,element(Col, Row),Next, State);
869		true ->
870		    table_max_col(Db, Name, Col, Max, Next, State)
871	    end;
872	undefined -> table_search_next(Db, Name, Indexes, State)
873    end.
874
875%%-----------------------------------------------------------------
876%% Interface to Pets.
877%%-----------------------------------------------------------------
878insert(volatile, Key, Val, #state{ets = Ets}) ->
879    ?vtrace("insert(volatile) -> entry with"
880	    "~n   Key: ~p"
881	    "~n   Val: ~p",
882	    [Key,Val]),
883    ets:insert(Ets, {Key, Val}),
884    true;
885insert(persistent, Key, Val, #state{dets = Dets, notify_clients = NC}) ->
886    ?vtrace("insert(persistent) -> entry with"
887	    "~n   Key: ~p"
888	    "~n   Val: ~p",
889	    [Key,Val]),
890    case dets_insert(Dets, {Key, Val}) of
891	ok ->
892	    notify_clients(insert,NC),
893	    true;
894	{error, Reason} ->
895	    error_msg("DETS (persistent) insert failed for {~w,~w}: ~n~w",
896		      [Key, Val, Reason]),
897	    false
898    end;
899insert(permanent, Key, Val, #state{dets = Dets, notify_clients = NC}) ->
900    ?vtrace("insert(permanent) -> entry with"
901	    "~n   Key: ~p"
902	    "~n   Val: ~p",
903	    [Key,Val]),
904    case dets_insert(Dets, {Key, Val}) of
905	ok ->
906	    notify_clients(insert,NC),
907	    true;
908	{error, Reason} ->
909	    error_msg("DETS (permanent) insert failed for {~w,~w}: ~n~w",
910		      [Key, Val, Reason]),
911	    false
912    end;
913insert(UnknownDb, Key, Val, _) ->
914    error_msg("Tried to insert ~w = ~w into unknown db ~w",
915	      [Key, Val, UnknownDb]),
916    false.
917
918delete(volatile, Key, State) ->
919    ets:delete(State#state.ets, Key),
920    true;
921delete(persistent, Key, #state{dets = Dets, notify_clients = NC}) ->
922    case dets_delete(Dets, Key) of
923	ok ->
924	    notify_clients(delete,NC),
925	    true;
926	{error, Reason} ->
927	    error_msg("DETS (persistent) delete failed for ~w: ~n~w",
928		      [Key, Reason]),
929	    false
930    end;
931delete(permanent, Key, #state{dets = Dets, notify_clients = NC}) ->
932    case dets_delete(Dets, Key) of
933	ok ->
934	    notify_clients(delete,NC),
935	    true;
936	{error, Reason} ->
937	    error_msg("DETS (permanent) delete failed for ~w: ~n~w",
938		      [Key, Reason]),
939	    false
940    end;
941delete(UnknownDb, Key, _) ->
942    error_msg("Tried to delete ~w from unknown db ~w",
943	      [Key, UnknownDb]),
944    false.
945
946
947match(volatile, Name, Pattern, #state{ets = Ets}) ->
948    ets:match(Ets, {{Name,'_'},{Pattern,'_','_'}});
949match(persistent, Name, Pattern, #state{dets = Dets}) ->
950    dets_match(Dets, {{Name,'_'},{Pattern,'_','_'}});
951match(permanent, Name, Pattern, #state{dets = Dets}) ->
952    dets_match(Dets, {{Name,'_'},{Pattern,'_','_'}});
953match(UnknownDb, Name, Pattern, _) ->
954    error_msg("Tried to match [~p,~p] from unknown db ~w",
955	      [Name, Pattern, UnknownDb]),
956    [].
957
958lookup(volatile, Key, #state{ets = Ets}) ->
959    case ets:lookup(Ets, Key) of
960	[{_, Val}] ->
961	    {value, Val};
962	[] ->
963	    undefined
964    end;
965lookup(persistent, Key, #state{dets = Dets}) ->
966    case dets_lookup(Dets, Key) of
967	[{_, Val}] ->
968	    {value, Val};
969	[] ->
970	    undefined
971    end;
972lookup(permanent, Key, #state{dets = Dets}) ->
973    case dets_lookup(Dets, Key) of
974	[{_, Val}] ->
975	    {value, Val};
976	[] ->
977	    undefined
978    end;
979lookup(UnknownDb, Key, _) ->
980    error_msg("Tried to lookup ~w in unknown db ~w", [Key, UnknownDb]),
981    false.
982
983close(#state{dets = Dets, ets = Ets}) ->
984    ets:delete(Ets),
985    dets_close(Dets).
986
987
988%%-----------------------------------------------------------------
989%% Notify clients interface
990%%-----------------------------------------------------------------
991notify_clients(Event, Clients) ->
992    F = fun(Client) -> notify_client(Client, Event) end,
993    lists:foreach(F, Clients).
994
995notify_client({Client,Module}, Event) ->
996    (catch Module:notify(Client,Event)).
997
998
999%%------------------------------------------------------------------
1000%%  Constructs a row with first elements the own part of RowIndex,
1001%%  and last element RowStatus. All values are stored "as is", i.e.
1002%%  dynamic key values are stored without length first.
1003%%  RowIndex is a list of the
1004%%  first elements. RowStatus is needed, because the
1005%%  provided value may not be stored, e.g. createAndGo
1006%%  should be active.
1007%%  Returns a tuple of values for the row. If a value
1008%%  isn't specified in the Col list, then the
1009%%  corresponding value will be noinit.
1010%%------------------------------------------------------------------
1011table_construct_row(Name, RowIndex, Status, Cols) ->
1012    #table_info{nbr_of_cols = LastCol, index_types = Indexes,
1013		defvals = Defs, status_col = StatusCol,
1014		first_own_index = FirstOwnIndex, not_accessible = NoAccs} =
1015	snmp_generic:table_info(Name),
1016    ?vtrace(
1017       "table_construct_row Indexes: ~p~n"
1018       "    RowIndex: ~p",
1019       [Indexes, RowIndex]),
1020    Keys = snmp_generic:split_index_to_keys(Indexes, RowIndex),
1021    OwnKeys = snmp_generic:get_own_indexes(FirstOwnIndex, Keys),
1022    Row = OwnKeys ++ snmp_generic:table_create_rest(length(OwnKeys) + 1,
1023						    LastCol, StatusCol,
1024						    Status, Cols, NoAccs),
1025    L = snmp_generic:init_defaults(Defs, Row),
1026    list_to_tuple(L).
1027
1028
1029table_get_elements(NameDb, RowIndex, Cols, _FirstOwnIndex) ->
1030    get_elements(Cols, table_get_row(NameDb, RowIndex)).
1031
1032get_elements(_Cols, undefined) ->
1033    undefined;
1034get_elements([Col | Cols], Row) when is_tuple(Row) and (size(Row) >= Col) ->
1035    [element(Col, Row) | get_elements(Cols, Row)];
1036get_elements([], _Row) ->
1037    [];
1038get_elements(Cols, Row) ->
1039    erlang:error({bad_arguments, Cols, Row}).
1040
1041
1042%%----------------------------------------------------------------------
1043%% This should/could be a generic function, but since Mnesia implements
1044%% its own and this version still is local_db dependent, it's not generic yet.
1045%%----------------------------------------------------------------------
1046%% createAndGo
1047table_set_status(NameDb, RowIndex, ?'RowStatus_createAndGo', StatusCol, Cols,
1048		 ChangedStatusFunc, _ConsFunc) ->
1049    case table_create_row(NameDb, RowIndex, ?'RowStatus_active', Cols) of
1050	true -> snmp_generic:try_apply(ChangedStatusFunc,
1051				       [NameDb, ?'RowStatus_createAndGo',
1052					RowIndex, Cols]);
1053	_ -> {commitFailed, StatusCol}
1054    end;
1055
1056%%------------------------------------------------------------------
1057%% createAndWait - set status to notReady, and try to
1058%% make row consistent.
1059%%------------------------------------------------------------------
1060table_set_status(NameDb, RowIndex, ?'RowStatus_createAndWait', StatusCol, Cols,
1061		 ChangedStatusFunc, ConsFunc) ->
1062    case table_create_row(NameDb, RowIndex, ?'RowStatus_notReady', Cols) of
1063	true ->
1064	    case snmp_generic:try_apply(ConsFunc, [NameDb, RowIndex, Cols]) of
1065		{noError, 0} ->
1066		    snmp_generic:try_apply(ChangedStatusFunc,
1067					   [NameDb, ?'RowStatus_createAndWait',
1068					    RowIndex, Cols]);
1069		Error -> Error
1070	    end;
1071	_ -> {commitFailed, StatusCol}
1072    end;
1073
1074%% destroy
1075table_set_status(NameDb, RowIndex, ?'RowStatus_destroy', _StatusCol, Cols,
1076		 ChangedStatusFunc, _ConsFunc) ->
1077    case snmp_generic:try_apply(ChangedStatusFunc,
1078				[NameDb, ?'RowStatus_destroy',
1079				 RowIndex, Cols]) of
1080	{noError, 0} ->
1081	    table_delete_row(NameDb, RowIndex),
1082	    {noError, 0};
1083	Error -> Error
1084    end;
1085
1086%% Otherwise, active or notInService
1087table_set_status(NameDb, RowIndex, Val, _StatusCol, Cols,
1088		 ChangedStatusFunc, ConsFunc) ->
1089    snmp_generic:table_set_cols(NameDb, RowIndex, Cols, ConsFunc),
1090    snmp_generic:try_apply(ChangedStatusFunc, [NameDb, Val, RowIndex, Cols]).
1091
1092table_func(new, NameDb) ->
1093    case table_exists(NameDb) of
1094	true -> ok;
1095	_ -> table_create(NameDb)
1096    end;
1097
1098table_func(delete, _NameDb) ->
1099    ok.
1100
1101table_func(get, RowIndex, Cols, NameDb) ->
1102    TableInfo = snmp_generic:table_info(NameDb),
1103    snmp_generic:handle_table_get(NameDb, RowIndex, Cols,
1104				  TableInfo#table_info.first_own_index);
1105
1106%%------------------------------------------------------------------
1107%% Returns: List of endOfTable | {NextOid, Value}.
1108%% Implements the next operation, with the function
1109%% handle_table_next. Next should return the next accessible
1110%% instance, which cannot be a key.
1111%%------------------------------------------------------------------
1112table_func(get_next, RowIndex, Cols, NameDb) ->
1113    #table_info{first_accessible = FirstCol, first_own_index = FOI,
1114		nbr_of_cols = LastCol} = snmp_generic:table_info(NameDb),
1115    snmp_generic:handle_table_next(NameDb, RowIndex, Cols,
1116				   FirstCol, FOI, LastCol);
1117
1118%%-----------------------------------------------------------------
1119%% This function must only be used by tables with a RowStatus col!
1120%% Other tables must check if row exist themselves.
1121%%-----------------------------------------------------------------
1122table_func(is_set_ok, RowIndex, Cols, NameDb) ->
1123    snmp_generic:table_try_row(NameDb, nofunc, RowIndex, Cols);
1124
1125%%------------------------------------------------------------------
1126%% Cols is here a list of {ColumnNumber, NewValue}
1127%% This function must only be used by tables with a RowStatus col!
1128%% Other tables should use table_set_cols/3,4.
1129%%------------------------------------------------------------------
1130table_func(set, RowIndex, Cols, NameDb) ->
1131    snmp_generic:table_set_row(NameDb,
1132			       nofunc,
1133			       fun snmp_generic:table_try_make_consistent/3,
1134			       RowIndex,
1135			       Cols);
1136
1137table_func(undo, _RowIndex, _Cols, _NameDb) ->
1138    {noError, 0}.
1139
1140
1141
1142%%------------------------------------------------------------------
1143%% This functions retrieves option values from the Options list.
1144%%------------------------------------------------------------------
1145get_opt(Key, Opts, Def) ->
1146    snmp_misc:get_option(Key, Opts, Def).
1147
1148
1149%%------------------------------------------------------------------
1150
1151get_info(Dets, Ets) ->
1152    ProcSize = proc_mem(self()),
1153    DetsSz   = dets_size(Dets),
1154    EtsSz    = ets_size(Ets),
1155    [{process_memory, ProcSize},
1156     {db_memory, [{persistent, DetsSz}, {volatile, EtsSz}]}].
1157
1158proc_mem(P) when is_pid(P) ->
1159    case (catch erlang:process_info(P, memory)) of
1160	{memory, Sz} ->
1161	    Sz;
1162	_ ->
1163	    undefined
1164    end.
1165%% proc_mem(_) ->
1166%%     undefined.
1167
1168dets_size(#dets{tab = Tab, shadow = Shadow}) ->
1169    TabSz =
1170	case (catch dets:info(Tab, file_size)) of
1171	    Sz when is_integer(Sz) ->
1172		Sz;
1173	    _ ->
1174		undefined
1175	end,
1176    ShadowSz = ets_size(Shadow),
1177    [{tab, TabSz}, {shadow, ShadowSz}].
1178
1179ets_size(T) ->
1180    case (catch ets:info(T, memory)) of
1181	Sz when is_integer(Sz) ->
1182	    Sz;
1183	_ ->
1184	    undefined
1185    end.
1186
1187%%------------------------------------------------------------------
1188
1189%% info_msg(F, A) ->
1190%%     ?snmpa_info("Local DB server: " ++ F, A).
1191
1192warning_msg(F, A) ->
1193    ?snmpa_warning("Local DB server: " ++ F, A).
1194
1195error_msg(F, A) ->
1196    ?snmpa_error("Local DB server: " ++ F, A).
1197
1198%% ---
1199
1200user_err(F, A) ->
1201    snmpa_error:user_err(F, A).
1202
1203config_err(F, A) ->
1204    snmpa_error:config_err(F, A).
1205
1206
1207%% ----------------------------------------------------------------
1208
1209call(Req) ->
1210    gen_server:call(?SERVER, Req, infinity).
1211
1212cast(Msg) ->
1213    gen_server:cast(?SERVER, Msg).
1214
1215
1216%% ----------------------------------------------------------------
1217%% DETS wrapper functions
1218%% The purpose of these fuctions is basically to hide the shadow
1219%% table.
1220%% Changes are made both in dets and in the shadow table.
1221%% Reads are made from the shadow table.
1222%% ----------------------------------------------------------------
1223
1224dets_sync(#dets{tab = Dets}) ->
1225    dets:sync(Dets).
1226
1227dets_insert(#dets{tab = Tab, shadow = Shadow}, Data) ->
1228    ets:insert(Shadow, Data),
1229    dets:insert(Tab, Data).
1230
1231dets_delete(#dets{tab = Tab, shadow = Shadow}, Key) ->
1232    ets:delete(Shadow, Key),
1233    dets:delete(Tab, Key).
1234
1235dets_match(#dets{shadow = Shadow}, Pat) ->
1236    ets:match(Shadow, Pat).
1237
1238dets_match_object(#dets{shadow = Shadow}, Pat) ->
1239    ets:match_object(Shadow, Pat).
1240
1241dets_lookup(#dets{shadow = Shadow}, Key) ->
1242    ets:lookup(Shadow, Key).
1243
1244dets_close(#dets{tab = Tab, shadow = Shadow}) ->
1245    ets:delete(Shadow),
1246    dets:close(Tab).
1247