1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1997-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 21-module(snmp_log). 22 23 24-export([ 25 create/4, create/5, create/6, open/1, open/2, 26 change_size/2, close/1, sync/1, info/1, 27 log/3, log/4, 28 log_to_txt/6, log_to_txt/7, log_to_txt/8, 29 log_to_io/5, log_to_io/6, log_to_io/7 30 ]). 31-export([ 32 upgrade/1, upgrade/2, 33 downgrade/1 34 ]). 35-export([ 36 validate/1, validate/2 37 ]). 38%% <BACKWARD-COMPAT> 39-export([ 40 log_to_txt/5, 41 log_to_io/4 42 ]). 43%% </BACKWARD-COMPAT> 44 45-export_type([ 46 log/0, 47 log_time/0 48 ]). 49 50-define(SNMP_USE_V3, true). 51-include("snmp_types.hrl"). 52 53-define(VMODULE,"LOG"). 54-include("snmp_verbosity.hrl"). 55 56-define(LOG_FORMAT, internal). 57-define(LOG_TYPE, wrap). 58-define(BLOCK_DEFAULT, true). 59 60-record(snmp_log, {id, seqno}). 61 62 63%%----------------------------------------------------------------- 64%% Types 65%%----------------------------------------------------------------- 66 67-opaque log() :: #snmp_log{}. 68-type log_time() :: null | 69 calendar:datetime() | 70 {local_time, calendar:datetime()} | 71 {universal_time, calendar:datetime()}. 72 73 74%% -------------------------------------------------------------------- 75%% Exported functions 76%% -------------------------------------------------------------------- 77 78upgrade(Log) when is_record(Log, snmp_log) -> 79 Log; 80upgrade(Log) -> 81 upgrade(Log, disabled). 82 83upgrade(Log, _SeqNoGen) when is_record(Log, snmp_log) -> 84 Log; 85upgrade(Log, {M, F, A} = SeqNoGen) 86 when (is_atom(M) andalso is_atom(F) andalso is_list(A)) -> 87 #snmp_log{id = Log, seqno = SeqNoGen}; 88upgrade(Log, SeqNoGen) 89 when is_function(SeqNoGen, 0) -> 90 #snmp_log{id = Log, seqno = SeqNoGen}; 91upgrade(Log, disabled = SeqNoGen) -> 92 #snmp_log{id = Log, seqno = SeqNoGen}. 93 94downgrade(#snmp_log{id = Log}) -> 95 Log; 96downgrade(Log) -> 97 Log. 98 99 100%% -- create --- 101 102create(Name, File, Size, Repair) -> 103 create(Name, File, disabled, Size, Repair, false). 104 105create(Name, File, Size, Repair, Notify) 106 when (((Repair =:= true) orelse 107 (Repair =:= false) orelse 108 (Repair =:= truncate) orelse 109 (Repair =:= snmp_repair)) andalso 110 ((Notify =:= true) orelse 111 (Notify =:= false))) -> 112 create(Name, File, disabled, Size, Repair, Notify); 113create(Name, File, SeqNoGen, Size, Repair) -> 114 create(Name, File, SeqNoGen, Size, Repair, false). 115 116create(Name, File, SeqNoGen, Size, Repair, Notify) 117 when (((Repair =:= true) orelse 118 (Repair =:= false) orelse 119 (Repair =:= truncate) orelse 120 (Repair =:= snmp_repair)) andalso 121 ((Notify =:= true) orelse 122 (Notify =:= false))) -> 123 ?vtrace("create -> entry with" 124 "~n Name: ~p" 125 "~n File: ~p" 126 "~n SeqNoGen: ~p" 127 "~n Size: ~p" 128 "~n Repair: ~p" 129 "~n Notify: ~p", [Name, File, SeqNoGen, Size, Repair, Notify]), 130 log_open(Name, File, SeqNoGen, Size, Repair, Notify); 131create(Name, File, SeqNoGen, Size, Repair, Notify) -> 132 {error, {bad_args, Name, File, SeqNoGen, Size, Repair, Notify}}. 133 134 135%% -- open --- 136 137%% Open an already existing ( = open ) log 138 139open(Name) -> 140 open(Name, #snmp_log{seqno = disabled}). 141open(Name, #snmp_log{seqno = SeqNoGen} = _OldLog) -> 142 %% We include mode in the opts just to be on the safe side 143 case disk_log:open([{name, Name}, {mode, read_write}]) of 144 {ok, Log} -> 145 %% SeqNo must be proprly initiated also 146 {ok, #snmp_log{id = Log, seqno = SeqNoGen}}; 147 {repaired, Log, _RecBytes, _BadBytes} -> 148 {ok, #snmp_log{id = Log, seqno = SeqNoGen}}; 149 ERROR -> 150 ERROR 151 end. 152 153 154%% -- close --- 155 156close(#snmp_log{id = Log}) -> 157 ?vtrace("close -> entry with" 158 "~n Log: ~p", [Log]), 159 do_close(Log); 160close(Log) -> 161 do_close(Log). 162 163do_close(Log) -> 164 disk_log:close(Log). 165 166 167%% -- close --- 168 169sync(#snmp_log{id = Log}) -> 170 do_sync(Log); 171sync(Log) -> 172 do_sync(Log). 173 174do_sync(Log) -> 175 ?vtrace("sync -> entry with" 176 "~n Log: ~p", [Log]), 177 disk_log:sync(Log). 178 179 180%% -- info --- 181 182info(#snmp_log{id = Log}) -> 183 do_info(Log); 184info(Log) -> 185 do_info(Log). 186 187do_info(Log) -> 188 case disk_log:info(Log) of 189 Info when is_list(Info) -> 190 Items = [no_current_bytes, no_current_items, 191 current_file, no_overflows], 192 info_filter(Items, Info, []); 193 Else -> 194 Else 195 end. 196 197info_filter([], _Info, Acc) -> 198 {ok, Acc}; 199info_filter([Item|Items], Info, Acc) -> 200 case lists:keysearch(Item, 1, Info) of 201 {value, New} -> 202 info_filter(Items, Info, [New|Acc]); 203 false -> 204 info_filter(Items, Info, Acc) 205 end. 206 207 208%% -- validate -- 209 210%% This function is used to "validate" a log. 211%% At present this means making sure all entries 212%% are in the proper order, and if sequence numbering 213%% is used that no entries are missing. 214%% It is intended to be used for testing. 215 216validate(Log) -> 217 validate(Log, false). 218 219validate(#snmp_log{id = Log}, SeqNoReq) -> 220 validate(Log, SeqNoReq); 221validate(Log, SeqNoReq) 222 when ((SeqNoReq =:= true) orelse (SeqNoReq =:= false)) -> 223 Validator = 224 fun({Timestamp, SeqNo, _Packet, _Addr, _Port}, {PrevTS, PrevSN}) -> 225 ?vtrace("validating log entry when" 226 "~n Timestamp: ~p" 227 "~n SeqNo: ~p" 228 "~n PrevTS: ~p" 229 "~n PrevSN: ~p", 230 [Timestamp, SeqNo, PrevTS, PrevSN]), 231 validate_timestamp(PrevTS, Timestamp), 232 validate_seqno(PrevSN, SeqNo), 233 {Timestamp, SeqNo}; 234 235 ({Timestamp, SeqNo, _Packet, _AddrStr}, {PrevTS, PrevSN}) 236 when is_integer(SeqNo) -> 237 ?vtrace("validating log entry when" 238 "~n Timestamp: ~p" 239 "~n SeqNo: ~p" 240 "~n PrevTS: ~p" 241 "~n PrevSN: ~p", 242 [Timestamp, SeqNo, PrevTS, PrevSN]), 243 validate_timestamp(PrevTS, Timestamp), 244 validate_seqno(PrevSN, SeqNo), 245 {Timestamp, SeqNo}; 246 247 ({Timestamp, _Packet, _Addr, _Port}, {PrevTS, _PrevSN}) 248 when SeqNoReq =:= true -> 249 ?vtrace("validating log entry when" 250 "~n Timestamp: ~p" 251 "~n PrevTS: ~p", 252 [Timestamp, PrevTS]), 253 throw({error, {missing_seqno, Timestamp}}); 254 255 ({Timestamp, _Packet, _Addr, _Port}, {PrevTS, PrevSN}) -> 256 ?vtrace("validating log entry when" 257 "~n Timestamp: ~p" 258 "~n PrevTS: ~p", 259 [Timestamp, PrevTS]), 260 validate_timestamp(PrevTS, Timestamp), 261 {Timestamp, PrevSN}; 262 263 (E, Acc) -> 264 ?vtrace("validating bad log entry when" 265 "~n E: ~p" 266 "~n Acc: ~p", 267 [E, Acc]), 268 throw({error, {bad_entry, E, Acc}}) 269 end, 270 try 271 begin 272 validate_loop(disk_log:chunk(Log, start), 273 Log, Validator, first, first) 274 end 275 catch 276 throw:Error -> 277 Error 278 end. 279 280%% We shall check that TS2 >= TS1 281validate_timestamp(first, _TS2) -> 282 ok; 283validate_timestamp({LT1, UT1} = TS1, {LT2, UT2} = TS2) -> 284 LT1_Secs = calendar:datetime_to_gregorian_seconds(LT1), 285 UT1_Secs = calendar:datetime_to_gregorian_seconds(UT1), 286 LT2_Secs = calendar:datetime_to_gregorian_seconds(LT2), 287 UT2_Secs = calendar:datetime_to_gregorian_seconds(UT2), 288 case ((LT2_Secs >= LT1_Secs) andalso (UT2_Secs >= UT1_Secs)) of 289 true -> 290 ok; 291 false -> 292 throw({error, {invalid_timestamp, TS1, TS2}}) 293 end; 294validate_timestamp(TS1, TS2) -> 295 throw({error, {bad_timestamp, TS1, TS2}}). 296 297 298%% The usual case when SN2 = SN1 + 1 299validate_seqno(first, SN2) 300 when is_integer(SN2) >= 1 -> 301 ok; 302 303%% The usual case when SN2 = SN1 + 1 304validate_seqno(SN1, SN2) 305 when is_integer(SN1) andalso is_integer(SN2) andalso 306 (SN2 =:= (SN1 + 1)) andalso (SN1 >= 1) -> 307 ok; 308 309%% The case when we have a wrap 310validate_seqno(SN1, SN2) 311 when is_integer(SN1) andalso is_integer(SN2) andalso 312 (SN2 < SN1) andalso (SN2 >= 1) -> 313 ok; 314 315%% And everything else must be an error... 316validate_seqno(SN1, SN2) -> 317 throw({error, {bad_seqno, SN1, SN2}}). 318 319validate_loop(eof, _Log, _Validatior, _PrevTS, _PrevSN) -> 320 ok; 321validate_loop({error, _} = Error, _Log, _Validator, _PrevTS, _PrevSN) -> 322 Error; 323validate_loop({Cont, Terms}, Log, Validator, PrevTS, PrevSN) -> 324 ?vtrace("validate_loop -> entry with" 325 "~n Terms: ~p" 326 "~n PrevTS: ~p" 327 "~n PrevSN: ~p", [Terms, PrevTS, PrevSN]), 328 {NextTS, NextSN} = lists:foldl(Validator, {PrevTS, PrevSN}, Terms), 329 ?vtrace("validate_loop -> " 330 "~n NextTS: ~p" 331 "~n NextSN: ~p", [NextTS, NextSN]), 332 validate_loop(disk_log:chunk(Log, Cont), Log, Validator, NextTS, NextSN); 333validate_loop({Cont, Terms, BadBytes}, Log, Validator, PrevTS, PrevSN) -> 334 ?vtrace("validate_loop -> entry with" 335 "~n Terms: ~p" 336 "~n BadBytes: ~p" 337 "~n PrevTS: ~p" 338 "~n PrevSN: ~p", [Terms, BadBytes, PrevTS, PrevSN]), 339 error_logger:error_msg("Skipping ~w bytes while validating ~p~n~n", 340 [BadBytes, Log]), 341 {NextTS, NextSN} = lists:foldl(Validator, {PrevTS, PrevSN}, Terms), 342 ?vtrace("validate_loop -> " 343 "~n NextTS: ~p" 344 "~n NextSN: ~p", [NextTS, NextSN]), 345 validate_loop(disk_log:chunk(Log, Cont), Log, Validator, NextTS, NextSN). 346%% validate_loop(Error, _Log, _Write, _PrevTS, _PrevSN) -> 347%% Error. 348 349 350%% -- log --- 351 352%%----------------------------------------------------------------- 353%% For efficiency reasons, we want to log the packet as a binary. 354%% This is only possible for messages that are not encrypted. 355%% Therefore, Packet can be either a binary (encoded message), or 356%% a tuple {V3Hdr, ScopedPduBytes} 357%% 358%% log(Log, Packet, Addr, Port) 359%%----------------------------------------------------------------- 360 361log(#snmp_log{id = Log, seqno = SeqNo}, Packet, AddrStr) -> 362 ?vtrace( 363 "log -> entry with~n" 364 " Log: ~p~n" 365 " AddrStr: ~s", [Log, AddrStr]), 366 Entry = make_entry(SeqNo, Packet, AddrStr), 367 disk_log:alog(Log, Entry). 368 369log(#snmp_log{id = Log, seqno = SeqNo}, Packet, Ip, Port) -> 370 ?vtrace("log -> entry with" 371 "~n Log: ~p" 372 "~n Ip: ~p" 373 "~n Port: ~p", [Log, Ip, Port]), 374 Entry = make_entry(SeqNo, Packet, Ip, Port), 375%% io:format("log -> " 376%% "~n Entry: ~p" 377%% "~n Info: ~p" 378%% "~n", [Entry, disk_log:info(Log)]), 379 Res = disk_log:alog(Log, Entry), 380%% io:format("log -> " 381%% "~n Res: ~p" 382%% "~n Info: ~p" 383%% "~n", [Res, disk_log:info(Log)]), 384 %% disk_log:sync(Log), 385 Res. 386 387 388 389make_entry(SeqNoGen, Packet, AddrStr) 390 when is_integer(Packet); 391 is_tuple(AddrStr) -> 392 erlang:error(badarg, [SeqNoGen, Packet, AddrStr]); 393make_entry(SeqNoGen, Packet, AddrStr) -> 394 try next_seqno(SeqNoGen) of 395 disabled -> 396 {timestamp(), Packet, AddrStr}; 397 {ok, NextSeqNo} when is_integer(NextSeqNo) -> 398 {timestamp(), NextSeqNo, Packet, AddrStr} 399 catch 400 _:_ -> 401 {timestamp(), Packet, AddrStr} 402 end. 403 404make_entry(SeqNoGen, Packet, Ip, Port) when is_integer(Packet) -> 405 erlang:error(badarg, [SeqNoGen, Packet, Ip, Port]); 406make_entry(SeqNoGen, Packet, Ip, Port) -> 407 try next_seqno(SeqNoGen) of 408 disabled -> 409 {timestamp(), Packet, Ip, Port}; 410 {ok, NextSeqNo} when is_integer(NextSeqNo) -> 411 {timestamp(), NextSeqNo, Packet, Ip, Port} 412 catch 413 _:_ -> 414 {timestamp(), Packet, Ip, Port} 415 end. 416 417next_seqno({M, F, A}) -> 418 {ok, apply(M, F, A)}; 419next_seqno(F) when is_function(F) -> 420 {ok, F()}; 421next_seqno(_) -> 422 disabled. 423 424 425%% -- change_size --- 426 427change_size(#snmp_log{id = Log}, NewSize) -> 428 do_change_size(Log, NewSize); 429change_size(Log, NewSize) -> 430 do_change_size(Log, NewSize). 431 432do_change_size(Log, NewSize) -> 433 ?vtrace("change_size -> entry with" 434 "~n Log: ~p" 435 "~n NewSize: ~p", [Log, NewSize]), 436 disk_log:change_size(Log, NewSize). 437 438 439%% -- log_to_txt --- 440 441%% <BACKWARD-COMPAT> 442log_to_txt(Log, FileName, Dir, Mibs, TextFile) -> 443 log_to_txt(Log, ?BLOCK_DEFAULT, FileName, Dir, Mibs, TextFile). 444%% </BACKWARD-COMPAT> 445 446log_to_txt(Log, Block, FileName, Dir, Mibs, TextFile) 447 when ((Block =:= true) orelse (Block =:= false)) -> 448 log_to_txt(Log, Block, FileName, Dir, Mibs, TextFile, null, null); 449%% <BACKWARD-COMPAT> 450log_to_txt(Log, FileName, Dir, Mibs, TextFile, Start) -> 451 log_to_txt(Log, ?BLOCK_DEFAULT, FileName, Dir, Mibs, TextFile, Start, null). 452%% </BACKWARD-COMPAT> 453 454log_to_txt(Log, Block, FileName, Dir, Mibs, TextFile, Start) 455 when ((Block =:= true) orelse (Block =:= false)) -> 456 log_to_txt(Log, Block, FileName, Dir, Mibs, TextFile, Start, null); 457%% <BACKWARD-COMPAT> 458log_to_txt(Log, FileName, Dir, Mibs, TextFile, Start, Stop) -> 459 log_to_txt(Log, ?BLOCK_DEFAULT, FileName, Dir, Mibs, TextFile, Start, Stop). 460%% </BACKWARD-COMPAT> 461 462log_to_txt(Log, Block, FileName, Dir, Mibs, TextFile, Start, Stop) 463 when (((Block =:= true) orelse (Block =:= false)) andalso 464 is_list(Mibs) andalso is_list(TextFile)) -> 465 ?vtrace("log_to_txt -> entry with" 466 "~n Log: ~p" 467 "~n Block: ~p" 468 "~n FileName: ~p" 469 "~n Dir: ~p" 470 "~n Mibs: ~p" 471 "~n TextFile: ~p" 472 "~n Start: ~p" 473 "~n Stop: ~p", 474 [Log, Block, FileName, Dir, Mibs, TextFile, Start, Stop]), 475 File = filename:join(Dir, FileName), 476 Converter = fun(L) -> 477 do_log_to_file(L, TextFile, Mibs, Start, Stop) 478 end, 479 log_convert(Log, Block, File, Converter). 480 481 482%% -- log_to_io --- 483 484%% <BACKWARD-COMPAT> 485log_to_io(Log, FileName, Dir, Mibs) -> 486 log_to_io(Log, ?BLOCK_DEFAULT, FileName, Dir, Mibs, null, null). 487%% </BACKWARD-COMPAT> 488 489log_to_io(Log, Block, FileName, Dir, Mibs) 490 when ((Block =:= true) orelse (Block =:= false)) -> 491 log_to_io(Log, Block, FileName, Dir, Mibs, null, null); 492%% <BACKWARD-COMPAT> 493log_to_io(Log, FileName, Dir, Mibs, Start) -> 494 log_to_io(Log, ?BLOCK_DEFAULT, FileName, Dir, Mibs, Start, null). 495%% </BACKWARD-COMPAT> 496 497log_to_io(Log, Block, FileName, Dir, Mibs, Start) 498 when ((Block =:= true) orelse (Block =:= false)) -> 499 log_to_io(Log, Block, FileName, Dir, Mibs, Start, null); 500%% <BACKWARD-COMPAT> 501log_to_io(Log, FileName, Dir, Mibs, Start, Stop) -> 502 log_to_io(Log, ?BLOCK_DEFAULT, FileName, Dir, Mibs, Start, Stop). 503%% </BACKWARD-COMPAT> 504 505log_to_io(Log, Block, FileName, Dir, Mibs, Start, Stop) 506 when is_list(Mibs) -> 507 ?vtrace("log_to_io -> entry with" 508 "~n Log: ~p" 509 "~n Block: ~p" 510 "~n FileName: ~p" 511 "~n Dir: ~p" 512 "~n Mibs: ~p" 513 "~n Start: ~p" 514 "~n Stop: ~p", 515 [Log, Block, FileName, Dir, Mibs, Start, Stop]), 516 File = filename:join(Dir, FileName), 517 Converter = fun(L) -> 518 do_log_to_io(L, Mibs, Start, Stop) 519 end, 520 log_convert(Log, Block, File, Converter). 521 522 523%% -------------------------------------------------------------------- 524%% Internal functions 525%% -------------------------------------------------------------------- 526 527%% -- log_convert --- 528 529log_convert(#snmp_log{id = Log}, Block, File, Converter) -> 530 do_log_convert(Log, Block, File, Converter); 531log_convert(Log, Block, File, Converter) -> 532 do_log_convert(Log, Block, File, Converter). 533 534do_log_convert(Log, Block, File, Converter) -> 535 %% ?vtrace("do_log_converter -> entry with" 536 %% "~n Log: ~p" 537 %% "~n Block: ~p" 538 %% "~n File: ~p" 539 %% [Log, Block, File]), 540 Verbosity = get(verbosity), 541 {Pid, Ref} = 542 erlang:spawn_monitor( 543 fun() -> 544 put(sname, lc), 545 put(verbosity, Verbosity), 546 ?vlog("begin converting", []), 547 Result = do_log_convert2(Log, Block, File, Converter), 548 ?vlog("convert result: ~p", [Result]), 549 exit(Result) 550 end), 551 receive 552 {'DOWN', Ref, process, Pid, Result} -> 553 %% ?vtrace("do_log_converter -> received result" 554 %% "~n Result: ~p", [Result]), 555 Result 556 end. 557 558do_log_convert2(Log, Block, File, Converter) -> 559 560 %% ?vtrace("do_log_converter2 -> entry with" 561 %% "~n Log: ~p" 562 %% "~n Block: ~p" 563 %% "~n File: ~p" 564 %% "~n disk_log:info(Log): ~p", 565 %% [Log, Block, File, disk_log:info(Log)]), 566 567 %% First check if the caller process has already opened the 568 %% log, because if we close an already open log we will cause 569 %% a runtime error. 570 571 ?vtrace("do_log_convert2 -> entry - check if owner", []), 572 case is_owner(Log) of 573 true -> 574 ?vtrace("do_log_converter2 -> convert an already owned log", []), 575 maybe_block(Log, Block), 576 Res = Converter(Log), 577 maybe_unblock(Log, Block), 578 Res; 579 false -> 580 %% Not yet member of the ruling party, apply for membership... 581 ?vtrace("do_log_converter2 -> convert log", []), 582 case log_open(Log, File) of 583 {ok, _} -> 584 ?vdebug("do_log_convert2 -> opened - now convert", []), 585 maybe_block(Log, Block), 586 Res = Converter(Log), 587 maybe_unblock(Log, Block), 588 disk_log:close(Log), 589 ?vdebug("do_log_convert2 -> converted - done: " 590 "~n Result: ~p", [Res]), 591 Res; 592 {error, {name_already_open, _}} -> 593 ?vdebug("do_log_convert2 -> " 594 "already opened - now convert", []), 595 maybe_block(Log, Block), 596 Res = Converter(Log), 597 maybe_unblock(Log, Block), 598 ?vdebug("do_log_convert2 -> converted - done: " 599 "~n Result: ~p", [Res]), 600 Res; 601 {error, Reason} -> 602 ?vinfo("do_log_converter2 -> " 603 "failed converting log - open failed: " 604 "~n Reason: ~p", [Reason]), 605 {error, {Log, Reason}} 606 end 607 end. 608 609 610maybe_block(_Log, false = _Block) -> 611 %% ?vtrace("maybe_block(false) -> entry", []), 612 ok; 613maybe_block(Log, true = _Block) -> 614 %% ?vtrace("maybe_block(true) -> entry when" 615 %% "~n Log Status: ~p", [log_status(Log)]), 616 Res = disk_log:block(Log, true), 617 %% ?vtrace("maybe_block(true) -> " 618 %% "~n Log Status: ~p" 619 %% "~n Res: ~p", [log_status(Log), Res]), 620 Res. 621 622maybe_unblock(_Log, false = _Block) -> 623 %% ?vtrace("maybe_unblock(false) -> entry", []), 624 ok; 625maybe_unblock(Log, true = _Block) -> 626 %% ?vtrace("maybe_unblock(true) -> entry when" 627 %% "~n Log Status: ~p", [log_status(Log)]), 628 Res = disk_log:unblock(Log), 629 %% ?vtrace("maybe_unblock(true) -> " 630 %% "~n Log Status: ~p" 631 %% "~n Res: ~p", [log_status(Log), Res]), 632 Res. 633 634%% log_status(Log) -> 635%% Info = disk_log:info(Log), 636%% case lists:keysearch(status, 1, Info) of 637%% {value, {status, Status}} -> 638%% Status; 639%% false -> 640%% undefined 641%% end. 642 643 644%% -- do_log_to_text --- 645 646do_log_to_file(Log, TextFile, Mibs, Start, Stop) -> 647 case file:open(TextFile, [write]) of 648 {ok, Fd} -> 649 MiniMib = snmp_mini_mib:create(Mibs), 650 Write = fun(X) -> 651 case format_msg(X, MiniMib, Start, Stop) of 652 {Tag, S} when (Tag =:= ok) orelse (Tag =:= error) -> 653 io:format(Fd, "~s", [S]), 654 Tag; 655 Ignore -> 656 Ignore 657 end 658 end, 659 Res = (catch loop(Log, Write)), 660 snmp_mini_mib:delete(MiniMib), 661 file:close(Fd), 662 Res; 663 {error, Reason} -> 664 {error, {TextFile, Reason}} 665 end. 666 667 668do_log_to_io(Log, Mibs, Start, Stop) -> 669 MiniMib = snmp_mini_mib:create(Mibs), 670 Write = fun(X) -> 671 case format_msg(X, MiniMib, Start, Stop) of 672 {Tag, S} when (Tag =:= ok) orelse (Tag =:= error) -> 673 io:format("~s", [S]), 674 Tag; 675 X -> 676 X 677 end 678 end, 679 Res = (catch loop(Log, Write)), 680 snmp_mini_mib:delete(MiniMib), 681 Res. 682 683 684loop(Log, Write) -> 685 loop(disk_log:chunk(Log, start), Log, Write, 0, 0). 686 687loop(eof, _Log, _Write, _NumOK, 0 = _NumERR) -> 688 ok; 689loop(eof, _Log, _Write, NumOK, NumERR) -> 690 {ok, {NumOK, NumERR}}; 691loop({error, _} = Error, _Log, _Write, _NumOK, _NumERR) -> 692 Error; 693loop({Cont, Terms}, Log, Write, NumOK, NumERR) -> 694 try loop_terms(Terms, Write) of 695 {ok, {AddedOK, AddedERR}} -> 696 loop(disk_log:chunk(Log, Cont), Log, Write, 697 NumOK+AddedOK, NumERR+AddedERR) 698 catch 699 C:E:S -> 700 {error, {C, E, S}} 701 end; 702loop({Cont, Terms, BadBytes}, Log, Write, NumOK, NumERR) -> 703 error_logger:error_msg("Skipping ~w bytes while converting ~p~n~n", 704 [BadBytes, Log]), 705 try loop_terms(Terms, Write) of 706 {ok, {AddedOK, AddedERR}} -> 707 loop(disk_log:chunk(Log, Cont), Log, Write, 708 NumOK+AddedOK, NumERR+AddedERR) 709 catch 710 C:E:S -> 711 {error, {C, E, S}} 712 end. 713 714 715loop_terms(Terms, Write) -> 716 loop_terms(Terms, Write, 0, 0). 717 718loop_terms([], _Write, NumOK, NumERR) -> 719 {ok, {NumOK, NumERR}}; 720loop_terms([Term|Terms], Write, NumOK, NumERR) -> 721 case Write(Term) of 722 ok -> 723 loop_terms(Terms, Write, NumOK+1, NumERR); 724 error -> 725 loop_terms(Terms, Write, NumOK, NumERR+1); 726 _ -> 727 loop_terms(Terms, Write, NumOK, NumERR) 728 end. 729 730 731format_msg(Entry, Mib, Start, Stop) -> 732 TimeStamp = element(1, Entry), 733 case timestamp_filter(TimeStamp, Start, Stop) of 734 true -> 735 do_format_msg(Entry, Mib); 736 false -> 737 ignore 738 end. 739 740%% This is an old-style entry, that never had the sequence-number 741do_format_msg({Timestamp, Packet, {Ip, Port}}, Mib) -> 742 do_format_msg(Timestamp, Packet, ipPort2Str(Ip, Port), Mib); 743%% This is the format without sequence-number 744do_format_msg({Timestamp, Packet, AddrStr}, Mib) -> 745 do_format_msg(Timestamp, Packet, AddrStr, Mib); 746 747%% This is the format with sequence-number 748do_format_msg({Timestamp, SeqNo, Packet, AddrStr}, Mib) 749 when is_integer(SeqNo) -> 750 do_format_msg(Timestamp, Packet, AddrStr, Mib); 751%% This is the format without sequence-number 752do_format_msg({Timestamp, Packet, Ip, Port}, Mib) -> 753 do_format_msg(Timestamp, Packet, ipPort2Str(Ip, Port), Mib); 754 755%% This is the format with sequence-number 756do_format_msg({Timestamp, SeqNo, Packet, Ip, Port}, Mib) -> 757 do_format_msg(Timestamp, SeqNo, Packet, ipPort2Str(Ip, Port), Mib); 758 759%% This is crap... 760do_format_msg(_, _) -> 761 {error, format_tab("** unknown entry in log file\n\n", [])}. 762 763do_format_msg(TimeStamp, {V3Hdr, ScopedPdu}, AddrStr, Mib) -> 764 try snmp_pdus:dec_scoped_pdu(ScopedPdu) of 765 ScopedPDU when is_record(ScopedPDU, scopedPdu) -> 766 Msg = #message{version = 'version-3', 767 vsn_hdr = V3Hdr, 768 data = ScopedPDU}, 769 try f(ts2str(TimeStamp), "", Msg, AddrStr, Mib) of 770 {ok, _} = OK -> 771 OK 772 catch 773 FormatT:FormatE -> 774 format_error("format scoped pdu", 775 TimeStamp, AddrStr, FormatT, FormatE) 776 end 777 catch 778 DecT:DecE -> 779 format_error("decode scoped pdu", 780 TimeStamp, AddrStr, DecT, DecE) 781 end; 782do_format_msg(TimeStamp, Packet, AddrStr, Mib) -> 783 try snmp_pdus:dec_message(binary_to_list(Packet)) of 784 Msg when is_record(Msg, message) -> 785 try f(ts2str(TimeStamp), "", Msg, AddrStr, Mib) of 786 {ok, _} = OK -> 787 OK 788 catch 789 FormatT:FormatE -> 790 %% Provide info about the message 791 Extra = 792 case Msg#message.version of 793 'version-3' -> 794 #v3_hdr{msgID = ID, 795 msgFlags = Flags, 796 msgSecurityModel = SecModel} = 797 Msg#message.vsn_hdr, 798 SecLevel = snmp_misc:get_sec_level(Flags), 799 f("msg-id: ~w, sec-level: ~w, sec-model: ~w", 800 [ID, SecLevel, sm2atom(SecModel)]); 801 _ -> %% Community 802 f("community: ~s", [Msg#message.vsn_hdr]) 803 end, 804 format_error(f("format ~p message; ~s", 805 [Msg#message.version, Extra]), 806 TimeStamp, AddrStr, FormatT, FormatE) 807 end 808 catch 809 DecT:DecE -> 810 format_error("decode message", 811 TimeStamp, AddrStr, DecT, DecE) 812 813 end. 814 815sm2atom(?SEC_ANY) -> any; 816sm2atom(?SEC_V1) -> v1; 817sm2atom(?SEC_V2C) -> v2c; 818sm2atom(?SEC_USM) -> usm; 819sm2atom(_) -> unknown. 820 821do_format_msg(TimeStamp, SeqNo, {V3Hdr, ScopedPdu}, AddrStr, Mib) -> 822 try snmp_pdus:dec_scoped_pdu(ScopedPdu) of 823 ScopedPDU when is_record(ScopedPDU, scopedPdu) -> 824 Msg = #message{version = 'version-3', 825 vsn_hdr = V3Hdr, 826 data = ScopedPDU}, 827 try f(ts2str(TimeStamp), sn2str(SeqNo), Msg, AddrStr, Mib) of 828 {ok, _} = OK -> 829 OK 830 catch 831 FormatT:FormatE -> 832 format_error("format scoped pdu", 833 TimeStamp, SeqNo, AddrStr, FormatT, FormatE) 834 end 835 catch 836 DecT:DecE -> 837 format_error("decode scoped pdu", 838 TimeStamp, SeqNo, AddrStr, DecT, DecE) 839 end; 840do_format_msg(TimeStamp, SeqNo, Packet, AddrStr, Mib) -> 841 try snmp_pdus:dec_message(binary_to_list(Packet)) of 842 Msg when is_record(Msg, message) -> 843 try f(ts2str(TimeStamp), sn2str(SeqNo), Msg, AddrStr, Mib) of 844 {ok, _} = OK -> 845 OK 846 847 catch 848 FormatT:FormatE -> 849 %% Provide info about the message 850 Extra = 851 case Msg#message.version of 852 'version-3' -> 853 #v3_hdr{msgID = ID, 854 msgFlags = Flags, 855 msgSecurityModel = SecModel} = 856 Msg#message.vsn_hdr, 857 SecLevel = snmp_misc:get_sec_level(Flags), 858 f("msg-id: ~w, sec-level: ~w, sec-model: ~w", 859 [ID, SecLevel, sm2atom(SecModel)]); 860 _ -> %% Community 861 f("community: ~s", [Msg#message.vsn_hdr]) 862 end, 863 format_error(f("format ~p message; ~s", 864 [Msg#message.version, Extra]), 865 TimeStamp, SeqNo, AddrStr, FormatT, FormatE) 866 end 867 catch 868 DecT:DecE -> 869 format_error("decode message", 870 TimeStamp, SeqNo, AddrStr, DecT, DecE) 871 end. 872 873 874format_error(WhatStr, TimeStamp, AddrStr, throw, {error, Reason}) -> 875 {ok, Str} = 876 format_tab( 877 "** error (~s) in log file at ~s from ~s: " 878 "~n ~p\n\n", 879 [WhatStr, ts2str(TimeStamp), AddrStr, Reason]), 880 {error, Str}; 881format_error(WhatStr, TimeStamp, AddrStr, T, E) -> 882 {ok, Str} = 883 format_tab( 884 "** error (~s) in log file at ~s from ~s: " 885 "~n ~w: ~p\n\n", 886 [WhatStr, ts2str(TimeStamp), AddrStr, T, E]), 887 {error, Str}. 888 889format_error(WhatStr, TimeStamp, SeqNo, AddrStr, throw, {error, Reason}) -> 890 {ok, Str} = 891 format_tab( 892 "** error (~s) in log file at ~s~s from ~s: " 893 "~n ~p\n\n", 894 [WhatStr, ts2str(TimeStamp), sn2str(SeqNo), AddrStr, Reason]), 895 {error, Str}; 896format_error(WhatStr, TimeStamp, SeqNo, AddrStr, T, E) -> 897 {ok, Str} = 898 format_tab( 899 "** error (~s) in log file at ~s~s from ~s: " 900 "~n ~w, ~p\n\n", 901 [WhatStr, ts2str(TimeStamp), sn2str(SeqNo), AddrStr, T, E]), 902 {error, Str}. 903 904 905f(TimeStamp, SeqNo, 906 #message{version = Vsn, vsn_hdr = VsnHdr, data = Data}, 907 AddrStr, Mib) -> 908 Str = format_pdu(Data, Mib), 909 HdrStr = format_header(Vsn, VsnHdr), 910 Class = 911 case get_type(Data) of 912 trappdu -> 913 trap; 914 'snmpv2-trap' -> 915 trap; 916 'inform-request' -> 917 inform; 918 'get-response' -> 919 response; 920 report -> 921 report; 922 _ -> 923 request 924 end, 925 format_tab( 926 "~w ~s - ~s [~s]~s ~w\n~s", 927 [Class, AddrStr, HdrStr, TimeStamp, SeqNo, Vsn, Str]). 928 929f(F, A) -> 930 lists:flatten(io_lib:format(F, A)). 931 932 933ipPort2Str(Ip, Port) -> 934 snmp_conf:mk_addr_string({Ip, Port}). 935 936%% Convert a timestamp 2-tupple to a printable string 937%% 938ts2str({Local,Universal}) -> 939 dat2str(Local) ++ " , " ++ dat2str(Universal); 940ts2str(_) -> 941 "". 942 943%% Convert a sequence number integer to a printable string 944%% 945sn2str(SeqNo) when is_integer(SeqNo) -> 946 " [" ++ integer_to_list(SeqNo) ++ "]"; 947sn2str(_) -> 948 "". 949 950%% Convert a datetime 2-tupple to a printable string 951%% 952dat2str({{Y,M,D},{H,Min,S}}) -> 953 io_lib:format("~w-~w-~w,~w:~w:~w",[Y,M,D,H,Min,S]). 954 955 956timestamp_filter({Local,Universal},Start,Stop) -> 957 tsf_ge(Local,Universal,Start) and tsf_le(Local,Universal,Stop); 958timestamp_filter(_,_Start,_Stop) -> 959 true. 960 961tsf_ge(_Local,_Universal,null) -> 962 true; 963tsf_ge(Local,_Universal,{local_time,DateTime}) -> 964 tsf_ge(Local,DateTime); 965tsf_ge(_Local,Universal,{universal_time,DateTime}) -> 966 tsf_ge(Universal,DateTime); 967tsf_ge(Local,_Universal,DateTime) -> 968 tsf_ge(Local,DateTime). 969 970tsf_ge(TimeStamp, DateTime) -> 971 T1 = calendar:datetime_to_gregorian_seconds(TimeStamp), 972 T2 = calendar:datetime_to_gregorian_seconds(DateTime), 973 T1 >= T2. 974 975tsf_le(_Local, _Universal, null) -> 976 true; 977tsf_le(Local, _Universal, {local_time, DateTime}) -> 978 tsf_le(Local, DateTime); 979tsf_le(_Local, Universal, {universal_time, DateTime}) -> 980 tsf_le(Universal, DateTime); 981tsf_le(Local, _Universal, DateTime) -> 982 tsf_le(Local,DateTime). 983 984tsf_le(TimeStamp, DateTime) -> 985 T1 = calendar:datetime_to_gregorian_seconds(TimeStamp), 986 T2 = calendar:datetime_to_gregorian_seconds(DateTime), 987 T1 =< T2. 988 989 990%% In the output replace TAB by ESC TAB, and add a single trailing TAB. 991%% 992format_tab(Format, Args) -> 993 Str = lists:flatten(io_lib:format(Format, Args)), 994 DStr = lists:map(fun($\t) -> "\e\t"; (C) -> C end, Str), 995 {ok, io_lib:format("~s\t", [DStr])}. 996 997 998format_header('version-1', CommunityStr) -> 999 CommunityStr; 1000format_header('version-2', CommunityStr) -> 1001 CommunityStr; 1002format_header('version-3', #v3_hdr{msgFlags = MsgFlags, 1003 msgSecurityModel = SecModel, 1004 msgSecurityParameters = SecParams}) -> 1005 SecLevel = snmp_misc:get_sec_level(MsgFlags), 1006 case SecModel of 1007 ?SEC_USM -> 1008 case catch snmp_pdus:dec_usm_security_parameters(SecParams) of 1009 #usmSecurityParameters{msgAuthoritativeEngineID = AuthEngineID, 1010 msgUserName = UserName} -> 1011 io_lib:format("~w:\"~s\":\"~s\"", 1012 [SecLevel, AuthEngineID, UserName]); 1013 _ -> 1014 "-" 1015 end; 1016 _ -> 1017 "\"unknown security model\"" 1018 end. 1019 1020 1021format_pdu(#scopedPdu{contextName = Context, data = Pdu}, Mib) -> 1022 io_lib:format("Context: \"~s\"\n~s", 1023 [Context, snmp_misc:format_pdu(Pdu, Mib)]); 1024format_pdu(Pdu, Mib) -> 1025 try snmp_misc:format_pdu(Pdu, Mib) of 1026 Str -> 1027 Str 1028 catch 1029 _:_ -> 1030 throw({error, 'invalid-pdu'}) 1031 end. 1032 1033get_type(#scopedPdu{data = Pdu}) -> 1034 get_type(Pdu); 1035get_type(Pdu) when is_record(Pdu, trappdu) -> 1036 trappdu; 1037get_type(#pdu{type = Type}) -> 1038 Type. 1039 1040 1041 1042%% ------------------------------------------------------------------- 1043%% Various utility functions 1044%% ------------------------------------------------------------------- 1045 1046log_open(Name, File, {M, F, A} = SeqNoGen, Size, Repair, Notify) 1047 when (is_atom(M) andalso is_atom(F) andalso is_list(A)) -> 1048 log_open2(Name, File, SeqNoGen, Size, Repair, Notify); 1049log_open(Name, File, SeqNoGen, Size, Repair, Notify) 1050 when is_function(SeqNoGen, 0) -> 1051 log_open2(Name, File, SeqNoGen, Size, Repair, Notify); 1052log_open(Name, File, disabled = SeqNoGen, Size, Repair, Notify) -> 1053 log_open2(Name, File, SeqNoGen, Size, Repair, Notify); 1054log_open(_, _File, BadSeqNoGen, _Size, _Repair, _Notify) -> 1055 {error, {bad_seqno, BadSeqNoGen}}. 1056 1057log_open2(Name, File, SeqNoGen, Size, Repair, Notify) -> 1058 case do_log_open(Name, File, Size, Repair, Notify) of 1059 {ok, Log} -> 1060 {ok, #snmp_log{id = Log, seqno = SeqNoGen}}; 1061 {repaired, Log, Rec, Bad} -> 1062 ?vlog("log_open -> repaired: " 1063 "~n Rec: ~p" 1064 "~n Bad: ~p", [Rec, Bad]), 1065 {ok, #snmp_log{id = Log, seqno = SeqNoGen}}; 1066 Error -> 1067 Error 1068 end. 1069 1070 1071%% We need to make sure we do not end up in an infinit loop 1072%% Take the number of files of the wrap log and add 2 (for 1073%% the index and size files). 1074do_log_open(Name, File, {_, N} = Size, snmp_repair = _Repair, Notify) -> 1075 do_snmp_log_open(Name, File, Size, N+2, Notify); 1076 1077do_log_open(Name, File, Size, snmp_repair = _Repair, Notify) -> 1078 do_snmp_log_open(Name, File, Size, 1, Notify); 1079 1080do_log_open(Name, File, Size, Repair, Notify) -> 1081 do_std_log_open(Name, File, Size, Repair, Notify). 1082 1083 1084do_snmp_log_open(Name, File, Size, N, Notify) when N =< 0 -> 1085 do_std_log_open(Name, File, Size, true, Notify); 1086do_snmp_log_open(Name, File, Size, N, Notify) -> 1087 case do_std_log_open(Name, File, Size, true, Notify) of 1088 {error, {not_a_log_file, XFile}} -> 1089 case file:rename(XFile, lists:append([XFile, ".MOVED"])) of 1090 ok -> 1091 ?vinfo("Failed open log file (even with repair) - " 1092 "not a logfile:" 1093 "~n Attempting to move file aside (.MOVED)" 1094 "~n ~s", [XFile]), 1095 do_snmp_log_open(Name, File, Size, N-1, Notify); 1096 Error -> 1097 {error, {rename_failed, Error}} 1098 end; 1099 {error, Reason} -> 1100 ?vinfo("Failed open log file (even with repair) - " 1101 "~n Attempting to move old log file aside (.MOVED)" 1102 "~n~p", [Reason]), 1103 move_log(File), 1104 do_std_log_open(Name, File, Size, true, Notify); 1105 Else -> 1106 Else 1107 end. 1108 1109 1110%% First try to open the log without the size-spec. This will 1111%% succeed if the log has already been created. In that case, 1112%% we'll use whatever size the log had at the time it was closed. 1113do_std_log_open(Name, File, Size, Repair, Notify) -> 1114 Opts = [{name, Name}, 1115 {file, File}, 1116 {type, ?LOG_TYPE}, 1117 {format, ?LOG_FORMAT}, 1118 {mode, read_write}, 1119 {notify, Notify}, 1120 {repair, Repair}], 1121 case disk_log:open(Opts) of 1122 {error, {badarg, size}} -> 1123 %% The log didn't exist, try with the size-spec 1124 disk_log:open([{size, Size} | Opts]); 1125 Else -> 1126 Else 1127 end. 1128 1129 1130log_open(Name, File) -> 1131 Opts = [{name, Name}, 1132 {file, File}], 1133 case disk_log:open(Opts) of 1134 {error, {badarg, size}} -> 1135 {error, no_such_log}; 1136 Else -> 1137 Else 1138 end. 1139 1140 1141move_log(File) -> 1142 Dir = filename:dirname(File), 1143 FileName = filename:basename(File), 1144 case file:list_dir(Dir) of 1145 {ok, Files0} -> 1146 Files = [F || F <- Files0, lists:prefix(FileName, F)], 1147 F = fun(XFile) -> 1148 file:rename(XFile, lists:append([XFile, ".MOVED"])) 1149 end, 1150 lists:foreach(F, Files); 1151 _ -> 1152 ok 1153 end. 1154 1155 1156is_owner(Log) -> 1157 lists:member(self(), log_owners(Log)). 1158 1159log_owners(Log) -> 1160 Info = log_info(Log), 1161 case lists:keysearch(owners, 1, Info) of 1162 {value, {_, Pids}} -> 1163 [P || {P, _} <- Pids]; 1164 _ -> 1165 [] 1166 end. 1167 1168log_info(Log) -> 1169 case disk_log:info(Log) of 1170 Info when is_list(Info) -> 1171 Info; 1172 _ -> 1173 [] 1174 end. 1175 1176 1177timestamp() -> 1178 {calendar:local_time(), calendar:universal_time()}. 1179 1180