1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2000-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_general_db).
21
22
23%%%-----------------------------------------------------------------
24%%% This module implements a very simple "generic" MIB database
25%%% interface. It contains the most common functions performed.
26%%% It is generic in the sense that it implements both an interface
27%%% to mnesia and ets.
28%%%
29%%% Note that this module has nothing to do with the snmp_generic
30%%% and snmp_generic_mnesia modules.
31%%%-----------------------------------------------------------------
32
33-export([open/5, close/1, read/2, write/2, delete/1, delete/2]).
34-export([sync/1, backup/2]).
35-export([match_object/2, match_delete/2]).
36-export([tab2list/1, info/1, info/2]).
37
38
39-define(VMODULE,"GDB").
40-include("snmp_verbosity.hrl").
41
42
43%% ---------------------------------------------------------------
44%% open(Info,Name,RecName,Attr,Type) -> term()
45%% Info    -> ets | {ets, Dir} |
46%%            {dets, Dir} | {dets, Dir, Action} |
47%%            {mnesia, Nodes} | {mnesia, Nodes, Action}
48%% Name    -> atom()
49%% RecName -> Name of the record to store
50%% Attr    -> Attributes of the record stored in the table
51%% Type    -> set | bag
52%% Dir     -> string()
53%% Nodes   -> [node()]
54%% Action  -> keep | clear
55%%
56%% Open or create a database table. In the mnesia/dets case,
57%% where the table is stored on disc, it could be of interest
58%% to clear the table. This is controlled by the Action parameter.
59%% An empty node list ([]), is traslated into a list containing
60%% only the own node.
61%% ---------------------------------------------------------------
62open({mnesia,Nodes,clear}, Name, RecName, Attr, Type) when is_list(Nodes) ->
63    ?vtrace("[mnesia] open ~p database ~p for ~p on ~p; clear",
64	    [Type, Name, RecName, Nodes]),
65    mnesia_open(mnesia_table_check(Name), Nodes, RecName, Attr, Type, clear);
66open({mnesia,Nodes,_}, Name, RecName, Attr, Type) ->
67    ?vtrace("[mnesia] open ~p database ~p for ~p on ~p; keep",
68	    [Type, Name, RecName, Nodes]),
69    open({mnesia,Nodes}, Name, RecName, Attr, Type);
70open({mnesia,Nodes}, Name, RecName, Attr, Type) when is_list(Nodes) ->
71    ?vtrace("[mnesia] open ~p database ~p for ~p on ~p",
72	    [Type, Name, RecName, Nodes]),
73    mnesia_open(mnesia_table_check(Name), Nodes, RecName, Attr, Type, keep);
74
75open({dets, Dir, Action}, Name, _RecName, _Attr, Type) ->
76    dets_open(Name, dets_filename(Name, Dir), Type, Action);
77open({dets, Dir}, Name, _RecName, _Attr, Type) ->
78    dets_open(Name, dets_filename(Name, Dir), Type, keep);
79
80%% This function creates the ets table
81open(ets, Name, _RecName, _Attr, Type) ->
82    ?vtrace("[ets] open ~p database ~p", [Type, Name]),
83    Ets = ets:new(Name, [Type, protected, {keypos, 2}]),
84    {ets, Ets, undefined};
85open({ets, Dir}, Name, _RecName, _Attr, Type) ->
86    ets_open(Name, Dir, keep, Type);
87open({ets, Dir, Action}, Name, _RecName, _Attr, Type) ->
88    ets_open(Name, Dir, Action, Type).
89
90ets_open(Name, Dir, keep, Type) ->
91    ?vtrace("[ets] open ~p database ~p", [Type, Name]),
92    File = filename:join(Dir, atom_to_list(Name) ++ ".db"),
93    case file:read_file_info(File) of
94	{ok, _} ->
95	    case ets:file2tab(File) of
96		{ok, Tab} ->
97		    {ets, Tab, File};
98		{error, Reason} ->
99		    user_err("failed converting file to (ets-) tab: "
100			     "File: ~p"
101			     "~n~p", [File, Reason]),
102		    Ets = ets:new(Name, [Type, protected, {keypos, 2}]),
103		    {ets, Ets, File}
104	    end;
105	{error, _} ->
106	    Ets = ets:new(Name, [Type, protected, {keypos, 2}]),
107	    {ets, Ets, File}
108    end;
109ets_open(Name, Dir, clear, Type) ->
110    File = filename:join(Dir, atom_to_list(Name) ++ ".db"),
111    Ets  = ets:new(Name, [Type, protected, {keypos, 2}]),
112    {ets, Ets, File}.
113
114
115
116mnesia_open({table_exist,Name},_Nodes,_RecName,_Attr,_Type,clear) ->
117    ?vtrace("[mnesia] database ~p already exists; clear content",[Name]),
118    F = fun() -> mnesia:clear_table(Name) end,
119    case mnesia:transaction(F) of
120	{aborted,Reason} ->
121	    exit({aborted,Reason});
122	{atomic,_} ->
123	    {mnesia,Name}
124    end;
125mnesia_open({table_exist,Name},_Nodes,_RecName,_Attr,_Type,keep) ->
126    ?vtrace("[mnesia] database ~p already exists; keep content",[Name]),
127    {mnesia,Name};
128mnesia_open({no_table,Name},[],Type,RecName,Attr,Action) ->
129    mnesia_open({no_table,Name},[node()],Type,RecName,Attr,Action);
130mnesia_open({no_table,Name},Nodes,RecName,Attr,Type,_) ->
131    ?vtrace("[mnesia] no database ~p: create for ~p of type ~p",
132	    [Name,RecName,Type]),
133    %% Ok, we assume that this means that the table does not exist
134    Args = [{record_name,RecName}, {attributes,Attr},
135	    {type,Type}, {disc_copies,Nodes}],
136    case mnesia:create_table(Name,Args) of
137	{atomic,ok} ->
138	    {mnesia,Name};
139	{aborted,Reason} ->
140	    %% ?vinfo("[mnesia] aborted: ~p", [Reason]),
141	    exit({failed_create_mnesia_table,Reason})
142    end.
143
144
145mnesia_table_check(Name) ->
146    ?vtrace("[mnesia] check existens of database ~p",[Name]),
147    case (catch mnesia:table_info(Name,type)) of
148	{'EXIT', _Reason} ->
149	    {no_table, Name};
150	_ ->
151	    {table_exist, Name}
152    end.
153
154
155dets_open(Name, File, Type, Action) ->
156    ?vtrace("[dets] open database ~p (~p)", [Name, Action]),
157    N = dets_open1(Name, File, Type),
158    dets_open2(N, Action).
159
160dets_open1(Name, File, Type) ->
161    ?vtrace("[dets] open database ~p of type ~p",[Name, Type]),
162    {ok, N} = dets:open_file(Name, [{file, File}, {type, Type}, {keypos, 2}]),
163    N.
164
165dets_open2(N, clear) ->
166    dets:match_delete(N,'_'),
167    {dets, N};
168dets_open2(N, _) ->
169    {dets, N}.
170
171%% dets_table_check(Name, Dir) ->
172%%     Filename = dets_filename(Name, Dir),
173%%     ?vtrace("[dets] check existens of database: "
174%% 	"~n   ~p -> ~s"
175%% 	"~n   ~p"
176%% 	"~n   ~p"
177%% 	,
178%% 	[Name, Filename, file:list_dir(Dir), file:read_file_info(Filename)]),
179%%     case (catch dets:info(Filename, size)) of
180%% 	{'EXIT', Reason} ->
181%% 	    {no_table, Name, Filename};
182%% 	undefined -> %% Introduced in R8
183%% 	    {no_table, Name, Filename};
184%% 	_ ->
185%% 	    {table_exist, Name, Filename}
186%%     end.
187
188
189dets_filename(Name, Dir) ->
190    Dir1 = dets_filename1(Dir),
191    Dir2 = string:strip(Dir1, right, $/),
192    io_lib:format("~s/~p.dat", [Dir2, Name]).
193
194dets_filename1([])  -> ".";
195dets_filename1(Dir) -> Dir.
196
197
198%% ---------------------------------------------------------------
199%% close(DbRef) ->
200%% DbRef -> term()
201%%
202%% Close the database. This does nothing in the mnesia case, but
203%% deletes the table in the ets case.
204%% ---------------------------------------------------------------
205close({mnesia,_}) ->
206    ?vtrace("[mnesia] close database: NO ACTION",[]),
207    ok;
208close({dets, Name}) ->
209    ?vtrace("[dets] close database ~p",[Name]),
210    dets:close(Name);
211close({ets, Name, undefined}) ->
212    ?vtrace("[ets] close (delete) table ~p",[Name]),
213    ets:delete(Name);
214close({ets, Name, File}) ->
215    ?vtrace("[ets] close (delete) table ~p",[Name]),
216    write_ets_file(Name, File),
217    ets:delete(Name).
218
219
220%% ---------------------------------------------------------------
221%% read(DbRef,Key) -> false | {value,Rec}
222%% DbRef -> term()
223%% Rec   -> tuple()
224%%
225%% Retrieve a record from the database.
226%% ---------------------------------------------------------------
227read({mnesia, Name}, Key) ->
228    ?vtrace("[mnesia] read (dirty) from database ~p: ~p",[Name,Key]),
229    case (catch mnesia:dirty_read(Name,Key)) of
230	[Rec|_] -> {value,Rec};
231	_ -> false
232    end;
233read({dets, Name}, Key) ->
234    ?vtrace("[dets] read from table ~p: ~p",[Name,Key]),
235    case dets:lookup(Name, Key) of
236	[Rec|_] -> {value, Rec};
237	_ -> false
238    end;
239read({ets, Name, _}, Key) ->
240    ?vtrace("[ets] read from table ~p: ~p",[Name,Key]),
241    case ets:lookup(Name, Key) of
242	[Rec|_] -> {value, Rec};
243	_ -> false
244    end.
245
246
247%% ---------------------------------------------------------------
248%% write(DbRef,Rec) -> ok
249%% DbRef -> term()
250%% Rec   -> tuple()
251%%
252%% Write a record to the database.
253%% ---------------------------------------------------------------
254write({mnesia, Name}, Rec) ->
255    ?vtrace("[mnesia] write to database ~p",[Name]),
256    F = fun() -> mnesia:write(Name, Rec, write) end,
257    case mnesia:transaction(F) of
258	{aborted, Reason} ->
259	    exit({aborted, Reason});
260	{atomic,_} ->
261	    ok
262    end;
263write({dets, Name}, Rec) ->
264    ?vtrace("[dets] write to table ~p",[Name]),
265    dets:insert(Name, Rec);
266write({ets, Name, _}, Rec) ->
267    ?vtrace("[ets] write to table ~p",[Name]),
268    ets:insert(Name, Rec).
269
270
271%% ---------------------------------------------------------------
272%% delete(DbRef) ->
273%% DbRef -> term()
274%%
275%% Delete the database.
276%% ---------------------------------------------------------------
277delete({mnesia, Name}) ->
278    ?vtrace("[mnesia] delete database: ~p",[Name]),
279    mnesia:delete_table(Name);
280delete({dets, Name}) ->
281    ?vtrace("[dets] delete database ~p",[Name]),
282    File = dets:info(Name, filename),
283    case dets:close(Name) of
284	ok ->
285	    file:delete(File);
286	Error ->
287	    Error
288    end;
289delete({ets, Name, undefined}) ->
290    ?vtrace("[dets] delete table ~p",[Name]),
291    ets:delete(Name);
292delete({ets, Name, File}) ->
293    ?vtrace("[dets] delete table ~p",[Name]),
294    file:delete(File),
295    ets:delete(Name).
296
297
298%% ---------------------------------------------------------------
299%% delete(DbRef, Key) -> ok
300%% DbRef -> term()
301%% Key   -> term()
302%%
303%% Delete a record from the database.
304%% ---------------------------------------------------------------
305delete({mnesia, Name}, Key) ->
306    ?vtrace("[mnesia] delete from database ~p: ~p", [Name, Key]),
307    F = fun() -> mnesia:delete(Name, Key, write) end,
308    case mnesia:transaction(F) of
309	{aborted,Reason} ->
310	    exit({aborted,Reason});
311	{atomic,_} ->
312	    ok
313    end;
314delete({dets, Name}, Key) ->
315    ?vtrace("[dets] delete from table ~p: ~p", [Name, Key]),
316    dets:delete(Name, Key);
317delete({ets, Name, _}, Key) ->
318    ?vtrace("[ets] delete from table ~p: ~p", [Name, Key]),
319    ets:delete(Name, Key).
320
321
322%% ---------------------------------------------------------------
323%% match_object(DbRef,Pattern) -> [tuple()]
324%% DbRef -> term()
325%% Pattern -> tuple()
326%%
327%% Search the database for records witch matches the pattern.
328%% ---------------------------------------------------------------
329match_object({mnesia, Name}, Pattern) ->
330    ?vtrace("[mnesia] match_object in ~p of ~p",[Name, Pattern]),
331    F = fun() -> mnesia:match_object(Name, Pattern, read) end,
332    case mnesia:transaction(F) of
333	{aborted, Reason} ->
334	    exit({aborted, Reason});
335	 {atomic, Recs} ->
336	    Recs
337    end;
338match_object({dets, Name}, Pattern) ->
339    ?vtrace("[dets] match_object in ~p of ~p",[Name, Pattern]),
340    dets:match_object(Name, Pattern);
341match_object({ets, Name, _}, Pattern) ->
342    ?vtrace("[ets] match_object in ~p of ~p",[Name, Pattern]),
343    ets:match_object(Name, Pattern).
344
345
346%% ---------------------------------------------------------------
347%% match_delete(DbRef,Pattern) ->
348%% DbRef -> term()
349%% Pattern -> tuple()
350%%
351%% Search the database for records witch matches the pattern and
352%% deletes them from the database.
353%% ---------------------------------------------------------------
354match_delete({mnesia, Name}, Pattern) ->
355    ?vtrace("[mnesia] match_delete in ~p with pattern ~p",[Name,Pattern]),
356    F = fun() ->
357		Recs = mnesia:match_object(Name, Pattern, read),
358		lists:foreach(fun(Rec) ->
359				      mnesia:delete_object(Name, Rec, write)
360			      end, Recs),
361		Recs
362	end,
363    case mnesia:transaction(F) of
364	{aborted, Reason} ->
365	    exit({aborted, Reason});
366	{atomic,R} ->
367	    R
368    end;
369match_delete({dets, Name}, Pattern) ->
370    ?vtrace("[dets] match_delete in ~p with pattern ~p",[Name,Pattern]),
371    Recs = dets:match_object(Name, Pattern),
372    dets:match_delete(Name, Pattern),
373    Recs;
374match_delete({ets, Name, _}, Pattern) ->
375    ?vtrace("[ets] match_delete in ~p with pattern ~p",[Name,Pattern]),
376    Recs = ets:match_object(Name, Pattern),
377    ets:match_delete(Name, Pattern),
378    Recs.
379
380
381%% ---------------------------------------------------------------
382%% tab2list(DbRef) -> [tuple()]
383%% DbRef -> term()
384%%
385%% Return all records in the table in the form of a list.
386%% ---------------------------------------------------------------
387tab2list({mnesia, Name}) ->
388    ?vtrace("[mnesia] tab2list -> list of ~p", [Name]),
389    match_object({mnesia, Name}, mnesia:table_info(Name, wild_pattern));
390tab2list({dets, Name}) ->
391    ?vtrace("[dets] tab2list -> list of ~p", [Name]),
392    match_object({dets, Name}, '_');
393tab2list({ets, Name, _}) ->
394    ?vtrace("[ets] tab2list -> list of ~p", [Name]),
395    ets:tab2list(Name).
396
397
398
399%% ---------------------------------------------------------------
400%% info(Db) -> taglist()
401%% info(Db, Item) -> Info
402%% Db   -> term()
403%% Item -> atom()
404%% tablist() -> [{key(),value()}]
405%% key() -> atom()
406%% value() -> term()
407%%
408%% Retrieve table information.
409%% ---------------------------------------------------------------
410info({mnesia, Name}) ->
411    case (catch mnesia:table_info(Name, all)) of
412	Info when is_list(Info) ->
413	    Info;
414	{'EXIT', {aborted, Reason}} ->
415	    {error, Reason};
416	Else ->
417	    {error, Else}
418    end;
419info({dets, Name}) ->
420    dets:info(Name);
421info({ets, Name, _}) ->
422    case ets:info(Name) of
423	undefined ->
424	    [];
425	L ->
426	    L
427    end.
428
429
430info({mnesia, Name}, Item) ->
431    case (catch mnesia:table_info(Name, Item)) of
432	{'EXIT', {aborted, Reason}} ->
433	    {error, Reason};
434	Info ->
435	    Info
436    end;
437info({dets, Name}, memory) ->
438    dets:info(Name, file_size);
439info({dets, Name}, Item) ->
440    dets:info(Name, Item);
441info({ets, Name, _}, Item) ->
442    ets:info(Name, Item).
443
444
445%% ---------------------------------------------------------------
446%% sync(Db) -> ok | {error, Reason}
447%% Db     -> term()
448%% Reason -> term()
449%%
450%% Dump table to disc (if possible)
451%% ---------------------------------------------------------------
452
453sync({mnesia, _}) ->
454    ok;
455sync({dets, Name}) ->
456    dets:sync(Name);
457sync({ets, _Name, undefined}) ->
458    ok;
459sync({ets, Name, File}) ->
460    write_ets_file(Name, File).
461
462
463%% ---------------------------------------------------------------
464%% backup(Db, BackupDir) -> ok | {error, Reason}
465%% Db     -> term()
466%% Reason -> term()
467%%
468%% Make a backup copy of the DB (only valid for det and ets)
469%% ---------------------------------------------------------------
470
471backup({mnesia, _}, _) ->
472    ok;
473backup({dets, Name}, BackupDir) ->
474    case dets:info(Name, filename) of
475	undefined ->
476	    {error, no_file};
477	Filename ->
478	    case filename:dirname(Filename) of
479		BackupDir ->
480		    {error, db_dir};
481		_ ->
482		    Type = dets:info(Name, type),
483		    KP   = dets:info(Name, keypos),
484		    dets_backup(Name,
485				filename:basename(Filename),
486				BackupDir, Type, KP)
487	    end
488    end;
489backup({ets, _Name, undefined}, _BackupDir) ->
490    ok;
491backup({ets, Name, File}, BackupDir) ->
492    Filename = filename:basename(File),
493    case filename:join(BackupDir, Filename) of
494	File ->
495	    %% Oups: backup-dir and db-dir the same
496	    {error, db_dir};
497	BackupFile ->
498	    write_ets_file(Name, BackupFile)
499    end.
500
501
502dets_backup(Name, Filename, BackupDir, Type, KP) ->
503    ?vtrace("dets_backup -> entry with"
504	    "~n   Name:      ~p"
505	    "~n   Filename:  ~p"
506	    "~n   BackupDir: ~p"
507	    "~n   Type:      ~p"
508	    "~n   KP:        ~p", [Name, Filename, BackupDir, Type, KP]),
509    BackupFile = filename:join(BackupDir, Filename),
510    ?vtrace("dets_backup -> "
511	    "~n   BackupFile: ~p", [BackupFile]),
512    Backup = list_to_atom(atom_to_list(Name) ++ "_backup"),
513    Opts   = [{file, BackupFile}, {type, Type}, {keypos, KP}],
514    case dets:open_file(Backup, Opts) of
515	{ok, B} ->
516	    ?vtrace("dets_backup -> create fun", []),
517	    F = fun(Arg) ->
518			dets_backup(Arg, start, Name, B)
519		end,
520	    dets:safe_fixtable(Name, true),
521	    Res = dets:init_table(Backup, F, [{format, bchunk}]),
522	    dets:safe_fixtable(Name, false),
523	    ?vtrace("dets_backup -> Res: ~p", [Res]),
524	    Res;
525	Error ->
526	    ?vinfo("dets_backup -> open_file failed: "
527		   "~n   ~p", [Error]),
528	    Error
529    end.
530
531
532dets_backup(close, _Cont, _Name, B) ->
533    dets:close(B),
534    ok;
535dets_backup(read, Cont1, Name, B) ->
536    case dets:bchunk(Name, Cont1) of
537	{error, _} = ERROR ->
538	    ERROR;
539	'$end_of_table' ->
540	    dets:close(B),
541	    end_of_input;
542	{Cont2, Data} ->
543	    F = fun(Arg) ->
544			dets_backup(Arg, Cont2, Name, B)
545		end,
546	    {Data, F}
547    end.
548
549
550%%----------------------------------------------------------------------
551
552write_ets_file(Name, File) ->
553    TmpFile = File ++ ".tmp",
554    case ets:tab2file(Name, TmpFile) of
555	ok ->
556	    case file:rename(TmpFile, File) of
557		ok ->
558		    ok;
559		Else ->
560		    user_err("Warning: could not move file ~p"
561			     " (~p)", [File, Else])
562	    end;
563	{error, Reason} ->
564	    user_err("Warning: could not save file ~p (~p)",
565		     [File, Reason])
566    end.
567
568
569%%----------------------------------------------------------------------
570
571user_err(F, A) ->
572    snmpa_error:user_err(F, A).
573