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