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