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