diff options
Diffstat (limited to 'lib/sasl/src/rb.erl')
-rw-r--r-- | lib/sasl/src/rb.erl | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/lib/sasl/src/rb.erl b/lib/sasl/src/rb.erl new file mode 100644 index 0000000000..00d86285e5 --- /dev/null +++ b/lib/sasl/src/rb.erl @@ -0,0 +1,697 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(rb). + +-behaviour(gen_server). + +%% External exports +-export([start/0, start/1, stop/0, rescan/0, rescan/1]). +-export([list/0, list/1, show/0, show/1, grep/1, start_log/1, stop_log/0]). +-export([h/0, help/0]). + +%% Internal exports +-export([start_link/1]). + +%% gen_server callbacks +-export([init/1, terminate/2, handle_call/3, + handle_cast/2, handle_info/2, code_change/3]). + +%%%----------------------------------------------------------------- +%%% Report Browser Tool. +%%% Formats Error reports written by log_mf_h +%%%----------------------------------------------------------------- + +-record(state, {dir, data, device, max, type, abort, log}). + +%%----------------------------------------------------------------- +%% Interface functions. +%% For available options; see print_options(). +%%----------------------------------------------------------------- +start() -> start([]). +start(Options) -> + supervisor:start_child(sasl_sup, + {rb_server, {rb, start_link, [Options]}, + temporary, brutal_kill, worker, [rb]}). + +start_link(Options) -> + gen_server:start_link({local, rb_server}, rb, Options, []). + +stop() -> + gen_server:call(rb_server, stop), + supervisor:delete_child(sasl_sup, rb_server). + +rescan() -> rescan([]). +rescan(Options) -> + gen_server:call(rb_server, {rescan, Options}, infinity). + +list() -> list(all). +list(Type) -> gen_server:call(rb_server, {list, Type}, infinity). + +show() -> + gen_server:call(rb_server, show, infinity). + +show(Number) when is_integer(Number) -> + gen_server:call(rb_server, {show_number, Number}, infinity); +show(Type) when is_atom(Type) -> + gen_server:call(rb_server, {show_type, Type}, infinity). + +grep(RegExp) -> gen_server:call(rb_server, {grep, RegExp}, infinity). + +start_log(FileName) -> gen_server:call(rb_server, {start_log, FileName}). + +stop_log() -> gen_server:call(rb_server, stop_log). + +h() -> help(). +help() -> + io:format("~nReport Browser Tool - usage~n"), + io:format("===========================~n"), + io:format("rb:start() - start the rb_server with default options~n"), + io:format("rb:start(Options) - where Options is a list of:~n"), + print_options(), + io:format("rb:h() - print this help~n"), + io:format("rb:help() - print this help~n"), + io:format("rb:list() - list all reports~n"), + io:format("rb:list(Type) - list all reports of type Type~n"), + io:format(" currently supported types are:~n"), + print_types(), + io:format("rb:grep(RegExp) - print reports containing RegExp~n"), + io:format("rb:rescan() - rescans the report directory with same~n"), + io:format(" options.~n"), + io:format("rb:rescan(Options) - rescans the report directory with new~n"), + io:format(" options. Options is same as in start/1.~n"), + io:format("rb:show(Number) - print report no Number~n"), + io:format("rb:show(Type) - print all reports of type Type~n"), + io:format("rb:show() - print all reports~n"), + io:format("rb:start_log(File) - redirect all reports to file~n"), + io:format("rb:stop_log() - close the log file and redirect to~n"), + io:format(" standard_io~n"), + io:format("rb:stop - stop the rb_server~n"). + +%%----------------------------------------------------------------- +%% Internal functions. +%%----------------------------------------------------------------- +%%----------------------------------------------------------------- +%% MAKE SURE THESE TWO FUNCTIONS ARE UPDATED! +%%----------------------------------------------------------------- +print_options() -> + io:format(" {start_log, FileName}~n"), + io:format(" - default: standard_io~n"), + io:format(" {max, MaxNoOfReports}~n"), + io:format(" - MaxNoOfReports should be an integer or 'all'~n"), + io:format(" - default: all~n"), + io:format(" {report_dir, DirString}~n"), + io:format(" - DirString should be a string without trailing~n"), + io:format(" - directory delimiter.~n"), + io:format(" - default: {sasl, error_logger_mf_dir}~n"), + io:format(" {type, ReportType}~n"), + io:format(" - ReportType should be a supported type, 'all'~n"), + io:format(" - or a list of supported types~n"), + io:format(" - default: all~n"), + io:format(" {abort_on_error, Bool}~n"), + io:format(" - Bool: true | false~n"), + io:format(" - default: false~n"). + +print_types() -> + io:format(" - crash_report~n"), + io:format(" - supervisor_report~n"), + io:format(" - progress~n"), + io:format(" - error~n"). + + +init(Options) -> + process_flag(priority, low), + process_flag(trap_exit, true), + Log = get_option(Options, start_log, standard_io), + Device = open_log_file(Log), + Dir = get_report_dir(Options), + Max = get_option(Options, max, all), + Type = get_option(Options, type, all), + Abort = get_option(Options, abort_on_error, false), + Data = scan_files(Dir ++ "/", Max, Type), + {ok, #state{dir = Dir ++ "/", data = Data, device = Device, + max = Max, type = Type, abort = Abort, log = Log}}. + +handle_call({rescan, Options}, _From, State) -> + {Device,Log1} = + case get_option(Options, start_log, {undefined}) of + {undefined} -> + {State#state.device,State#state.log}; + Log -> + close_device(State#state.device), + {open_log_file(Log),Log} + end, + Max = get_option(Options, max, State#state.max), + Type = get_option(Options, type, State#state.type), + Abort = get_option(Options, abort_on_error, false), + Data = scan_files(State#state.dir, Max, Type), + NewState = State#state{data = Data, max = Max, type = Type, + device = Device, abort = Abort, log = Log1}, + {reply, ok, NewState}; +handle_call(stop, _From, State) -> + {stop, normal, stopped, State}; +handle_call(_, _From, #state{data = undefined}) -> + {reply, {error, no_data}, #state{}}; +handle_call({list, Type}, _From, State) -> + print_list(State#state.data, Type), + {reply, ok, State}; +handle_call({start_log, FileName}, _From, State) -> + NewDevice = open_log_file(FileName), + {reply, ok, State#state{device = NewDevice}}; +handle_call(stop_log, _From, State) -> + close_device(State#state.device), + {reply, ok, State#state{device = standard_io}}; +handle_call({show_number, Number}, _From, State) -> + #state{dir = Dir, data = Data, device = Device, abort = Abort, log = Log} = State, + NewDevice = print_report_by_num(Dir, Data, Number, Device, Abort, Log), + {reply, ok, State#state{device = NewDevice}}; +handle_call({show_type, Type}, _From, State) -> + #state{dir = Dir, data = Data, device = Device, abort = Abort, log = Log} = State, + NewDevice = print_typed_reports(Dir, Data, Type, Device, Abort, Log), + {reply, ok, State#state{device = NewDevice}}; +handle_call(show, _From, State) -> + #state{dir = Dir, data = Data, device = Device, abort = Abort, log = Log} = State, + NewDevice = print_all_reports(Dir, Data, Device, Abort, Log), + {reply, ok, State#state{device = NewDevice}}; +handle_call({grep, RegExp}, _From, State) -> + #state{dir = Dir, data = Data, device = Device, abort = Abort, log = Log} = State, + NewDevice = print_grep_reports(Dir, Data, RegExp, Device, Abort, Log), + {reply, ok, State#state{device = NewDevice}}. + +terminate(_Reason, #state{device = Device}) -> + close_device(Device). + +handle_cast(_Msg, State) -> + {noreply, State}. +handle_info(_Info, State) -> + {noreply, State}. +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%----------------------------------------------------------------- +%% Func: open_log_file/1 +%% Args: FileName | standard_io +%% Returns: A Device for later use in call to io:format +%%----------------------------------------------------------------- +open_log_file(standard_io) -> standard_io; +open_log_file(FileName) -> + case file:open(FileName, [write,append]) of + {ok, Fd} -> Fd; + Error -> + io:format("rb: Cannot open file '~s' (~w).~n", + [FileName, Error]), + io:format("rb: Using standard_io~n"), + standard_io + end. + +close_device(Fd) when is_pid(Fd) -> + catch file:close(Fd); +close_device(_) -> ok. + +get_option(Options, Key, Default) -> + case lists:keysearch(Key, 1, Options) of + {value, {_Key, Value}} -> Value; + _ -> Default + end. + +get_report_dir(Options) -> + case lists:keysearch(report_dir, 1, Options) of + {value, {_Key, RptDir}} -> RptDir; + _ -> + case catch application:get_env(sasl, error_logger_mf_dir) of + {ok, Dir} -> Dir; + _ -> + exit("cannot locate report directory") + end + end. + +%%----------------------------------------------------------------- +%% Func: scan_files(RptDir, Max, Type) +%% Args: RptDir ::= string(). +%% Max ::= integer() | all, describing how many reports +%5 to read. +%% Type ::= atom(), describing which reports to read. +%% Purpose: Scan all report files one time, and build a list of +%% small elements +%% Returns: Data, where Data is a list of +%% {Number, Type, ShortDescr, Date, Fname, FilePosition}. +%%----------------------------------------------------------------- +scan_files(RptDir, Max, Type) -> + case file:open(RptDir ++ "/index", [raw, read]) of + {ok, Fd} -> + case catch file:read(Fd, 1) of + {ok, [LastWritten]} -> + Files = make_file_list(RptDir, LastWritten), + scan_files(RptDir, Files, Max, Type); + _ -> exit("cannot read the index file") + end; + _ -> exit("cannot read the index file") + end. + +make_file_list(Dir, FirstFileNo) -> + case file:list_dir(Dir) of + {ok, FileNames} -> + FileNumbers = lists:zf(fun(Name) -> + case catch list_to_integer(Name) of + Int when is_integer(Int) -> + {true, Int}; + _ -> + false + end + end, + FileNames), + shift(lists:sort(FileNumbers), FirstFileNo); + _ -> exit({bad_directory, Dir}) + end. + +shift(List, First) -> + shift(List, First, []). + +shift([H | T], H, Res) -> + [H | Res] ++ lists:reverse(T); +shift([H | T], First, Res) -> + shift(T, First, [H | Res]); +shift([], _, Res) -> + Res. + +%%----------------------------------------------------------------- +%% Func: scan_files(Dir, Files, Max, Type) +%% Args: Files is a list of FileName. +%% Purpose: Scan the report files in the index variable. +%% Returns: {Number, Type, ShortDescr, Date, FileName, FilePosition} +%%----------------------------------------------------------------- +scan_files(Dir, Files, Max, Type) -> + scan_files(Dir, 1, Files, [], Max, Type). +scan_files(_Dir, _, [], Res, _Max, _Type) -> Res; +scan_files(_Dir, _, _Files, Res, Max, _Type) when Max =< 0 -> Res; +scan_files(Dir, No, [H|T], Res, Max, Type) -> + Data = get_report_data_from_file(Dir, No, H, Max, Type), + Len = length(Data), + NewMax = dec_max(Max, Len), + NewNo = No + Len, + NewData = Data ++ Res, + scan_files(Dir, NewNo, T, NewData, NewMax, Type). + +dec_max(all, _) -> all; +dec_max(X,Y) -> X-Y. + +get_report_data_from_file(Dir, No, FileNr, Max, Type) -> + Fname = integer_to_list(FileNr), + FileName = lists:concat([Dir, Fname]), + case file:open(FileName, [read]) of + {ok, Fd} when is_pid(Fd) -> read_reports(No, Fd, Fname, Max, Type); + _ -> [{No, unknown, "Can't open file " ++ Fname, "???", Fname, 0}] + end. + +%%----------------------------------------------------------------- +%% Func: read_reports(No, Fd, Fname, Max, Type) +%% Purpose: Read reports from one report file. +%% Returns: A list of {No, Type, ShortDescr, Date, FileName, FilePosition} +%% Note: We have to read all reports, and then check the max- +%% variable, because the reports are reversed on the file, and +%% we may need the last ones. +%%----------------------------------------------------------------- +read_reports(No, Fd, Fname, Max, Type) -> + io:format("rb: reading report..."), + case catch read_reports(Fd, [], Type) of + {ok, Res} -> + file:close(Fd), + io:format("done.~n"), + NewRes = + if + length(Res) > Max -> + lists:sublist(Res, 1, Max); + true -> + Res + end, + add_report_data(NewRes, No, Fname); + {error, [Problem | Res]} -> + file:close(Fd), + io:format("Error: ~p~n",[Problem]), + io:format("Salvaged ~p entries from corrupt report file ~s...~n", + [length(Res),Fname]), + NewRes = + if + length([Problem|Res]) > Max -> + lists:sublist([Problem|Res], 1, Max); + true -> + [Problem|Res] + end, + add_report_data(NewRes, No, Fname); + Else -> + io:format("err ~p~n", [Else]), + [{No, unknown, "Can't read reports from file " ++ Fname, + "???", Fname, 0}] + end. + +%%----------------------------------------------------------------- +%% Func: add_report_data(Res, No, FName) +%% Args: Res is a list of {Type, ShortDescr, Date, FilePos} +%% Purpose: Convert a list of {Type, ShortDescr, Date, FilePos} to +%% a list of {No, Type, ShortDescr, Date, FileName, FilePos} +%% Returns: A list of {No, Type, ShortDescr, Date, FileName, FilePos} +%%----------------------------------------------------------------- +add_report_data(Res, No, FName) -> + add_report_data(Res, No, FName, []). +add_report_data([{Type, ShortDescr, Date, FilePos}|T], No, FName, Res) -> + add_report_data(T, No+1, FName, + [{No, Type, ShortDescr, Date, FName, FilePos}|Res]); +add_report_data([], _No, _FName, Res) -> Res. + +read_reports(Fd, Res, Type) -> + {ok, FilePos} = file:position(Fd, cur), + case catch read_report(Fd) of + {ok, Report} -> + RealType = get_type(Report), + {ShortDescr, Date} = get_short_descr(Report), + Rep = {RealType, ShortDescr, Date, FilePos}, + if + Type == all-> + read_reports(Fd, [Rep | Res], Type); + RealType == Type -> + read_reports(Fd, [Rep | Res], Type); + is_list(Type) -> + case lists:member(RealType, Type) of + true -> + read_reports(Fd, [Rep | Res], Type); + _ -> + read_reports(Fd, Res, Type) + end; + true -> + read_reports(Fd, Res, Type) + end; + {error, Error} -> + {error, [{unknown, Error, [], FilePos} | Res]}; + eof -> + {ok, Res}; + {'EXIT', Reason} -> + [{unknown, Reason, [], FilePos} | Res] + end. + +read_report(Fd) -> + case io:get_chars(Fd,'',2) of + [Hi,Lo] -> + Size = get_int16(Hi,Lo), + case io:get_chars(Fd,'',Size) of + eof -> + {error,"Premature end of file"}; + List -> + Bin = list_to_binary(List), + Ref = make_ref(), + case (catch {Ref,binary_to_term(Bin)}) of + {'EXIT',_} -> + {error, "Inclomplete erlang term in log"}; + {Ref,Term} -> + {ok, Term} + end + end; + eof -> + eof + end. + +get_int16(Hi,Lo) -> + ((Hi bsl 8) band 16#ff00) bor (Lo band 16#ff). + + +%%----------------------------------------------------------------- +%% Update these functions with the reports that should be possible +%% to browse with rb. +%%----------------------------------------------------------------- +get_type({_Time, {error_report, _Pid, {_, crash_report, _}}}) -> + crash_report; +get_type({_Time, {error_report, _Pid, {_, supervisor_report, _}}}) -> + supervisor_report; +get_type({_Time, {info_report, _Pid, {_, progress, _}}}) -> + progress; +get_type({_Time, {Type, _, _}}) -> Type; +get_type(_) -> unknown. + +get_short_descr({{Date, Time}, {error_report, Pid, {_, crash_report, Rep}}}) -> + [OwnRep | _] = Rep, + Name = + case lists:keysearch(registered_name, 1, OwnRep) of + {value, {_Key, []}} -> + case lists:keysearch(initial_call, 1, OwnRep) of + {value, {_K, {M,_F,_A}}} -> M; + _ -> Pid + end; + {value, {_Key, N}} -> N; + _ -> Pid + end, + NameStr = lists:flatten(io_lib:format("~w", [Name])), + {NameStr, date_str(Date, Time)}; +get_short_descr({{Date, Time}, {error_report, Pid, {_, supervisor_report,Rep}}}) -> + Name = + case lists:keysearch(supervisor, 1, Rep) of + {value, {_Key, N}} when is_atom(N) -> N; + _ -> Pid + end, + NameStr = lists:flatten(io_lib:format("~w", [Name])), + {NameStr, date_str(Date,Time)}; +get_short_descr({{Date, Time}, {_Type, Pid, _}}) -> + NameStr = lists:flatten(io_lib:format("~w", [Pid])), + {NameStr, date_str(Date,Time)}; +get_short_descr(_) -> + {"???", "???"}. + +date_str({Y,Mo,D}=Date,{H,Mi,S}=Time) -> + case application:get_env(sasl,utc_log) of + {ok,true} -> + {{YY,MoMo,DD},{HH,MiMi,SS}} = + local_time_to_universal_time({Date,Time}), + lists:flatten(io_lib:format("~w-~2.2.0w-~2.2.0w ~2.2.0w:" + "~2.2.0w:~2.2.0w UTC", + [YY,MoMo,DD,HH,MiMi,SS])); + _ -> + lists:flatten(io_lib:format("~w-~2.2.0w-~2.2.0w ~2.2.0w:" + "~2.2.0w:~2.2.0w", + [Y,Mo,D,H,Mi,S])) + end. + +local_time_to_universal_time({Date,Time}) -> + case calendar:local_time_to_universal_time_dst({Date,Time}) of + [UCT] -> + UCT; + [UCT1,_UCT2] -> + UCT1; + [] -> % should not happen + {Date,Time} + end. + + +print_list(Data, Type) -> + Header = {"No", "Type", "Process", "Date Time"}, + Width = find_width([Header | Data], 0)+1, + DateWidth = find_date_width([Header | Data], 0) +1, + Format = lists:concat(["~4s~20s ~", Width, "s~20s~n"]), + io:format(Format, tuple_to_list(Header)), + io:format(Format, ["==", "====", "=======", "==== ===="]), + print_list(Data, Type, Width, DateWidth). +print_list([], _, _, _) -> true; +print_list([H|T], Type, Width, DateWidth) -> + print_one_report(H, Type, Width, DateWidth), + print_list(T, Type, Width, DateWidth). + +find_width([], Width) -> Width; +find_width([H|T], Width) -> + Try = length(element(3, H)), + if + Try > Width -> find_width(T, Try); + true -> find_width(T, Width) + end. +find_date_width([], Width) -> Width; +find_date_width([H|T], Width) -> + Try = length(element(4, H)), + if + Try > Width -> find_date_width(T, Try); + true -> find_date_width(T, Width) + end. + +print_one_report({No, RealType, ShortDescr, Date, _Fname, _FilePos}, + WantedType, + Width, DateWidth) -> + if + WantedType == all -> + print_short_descr(No, RealType, ShortDescr, Date, Width, + DateWidth); + WantedType == RealType -> + print_short_descr(No, RealType, ShortDescr, Date, Width, + DateWidth); + true -> ok + end. + +print_short_descr(No, Type, ShortDescr, Date, Width, DateWidth) -> + Format = lists:concat(["~4w~20w ~", Width, "s~", DateWidth,"s~n"]), + io:format(Format, [No, + Type, + io_lib:format("~s", [ShortDescr]), + Date]). + +print_report_by_num(Dir, Data, Number, Device, Abort, Log) -> + {_,Device1} = print_report(Dir, Data, Number, Device, Abort, Log), + Device1. + +print_typed_reports(_Dir, [], _Type, Device, _Abort, _Log) -> + Device; +print_typed_reports(Dir, Data, Type, Device, Abort, Log) -> + {Next,Device1} = + case element(2, hd(Data)) of + Type -> + print_report(Dir, Data, element(1, hd(Data)), Device, Abort, Log); + _ -> + {proceed,Device} + end, + if Next == abort -> + Device1; + true -> + print_typed_reports(Dir, tl(Data), Type, Device1, Abort, Log) + end. + +print_all_reports(_Dir, [], Device, _Abort, _Log) -> + Device; +print_all_reports(Dir, Data, Device, Abort, Log) -> + {Next,Device1} = print_report(Dir, Data, element(1, hd(Data)), + Device, Abort, Log), + if Next == abort -> + Device1; + true -> + print_all_reports(Dir, tl(Data), Device1, Abort, Log) + end. + +print_report(Dir, Data, Number, Device, Abort, Log) -> + case find_report(Data, Number) of + {Fname, FilePosition} -> + FileName = lists:concat([Dir, Fname]), + case file:open(FileName, [read]) of + {ok, Fd} -> + read_rep(Fd, FilePosition, Device, Abort, Log); + _ -> + io:format("rb: can't open file ~p~n", [Fname]), + {proceed,Device} + end; + no_report -> + {proceed,Device} + end. + +find_report([{No, _Type, _Descr, _Date, Fname, FilePosition}|_T], No) -> + {Fname, FilePosition}; +find_report([_H|T], No) -> + find_report(T, No); +find_report([], No) -> + io:format("There is no report with number ~p.~n", [No]), + no_report. + +print_grep_reports(_Dir, [], _RegExp, Device, _Abort, _Log) -> + Device; +print_grep_reports(Dir, Data, RegExp, Device, Abort, Log) -> + {Next,Device1} = print_grep_report(Dir, Data, element(1, hd(Data)), + Device, RegExp, Abort, Log), + if Next == abort -> + Device1; + true -> + print_grep_reports(Dir, tl(Data), RegExp, Device1, Abort, Log) + end. + +print_grep_report(Dir, Data, Number, Device, RegExp, Abort, Log) -> + {Fname, FilePosition} = find_report(Data, Number), + FileName = lists:concat([Dir, Fname]), + case file:open(FileName, [read]) of + {ok, Fd} when is_pid(Fd) -> + check_rep(Fd, FilePosition, Device, RegExp, Number, Abort, Log); + _ -> + io:format("rb: can't open file ~p~n", [Fname]), + {proceed,Device} + end. + +check_rep(Fd, FilePosition, Device, RegExp, Number, Abort, Log) -> + case read_rep_msg(Fd, FilePosition) of + {Date, Msg} -> + MsgStr = lists:flatten(io_lib:format("~p",[Msg])), + case regexp:match(MsgStr, RegExp) of + {match, _, _} -> + io:format("Found match in report number ~w~n", [Number]), + case catch rb_format_supp:print(Date, Msg, Device) of + {'EXIT', _} -> + handle_bad_form(Date, Msg, Device, Abort, Log); + _ -> + {proceed,Device} + end; + _ -> + {proceed,Device} + end; + _ -> + io:format("rb: Cannot read from file~n"), + {proceed,Device} + end. + +read_rep(Fd, FilePosition, Device, Abort, Log) -> + case read_rep_msg(Fd, FilePosition) of + {Date, Msg} -> + case catch rb_format_supp:print(Date, Msg, Device) of + {'EXIT', _} -> + handle_bad_form(Date, Msg, Device, Abort, Log); + _ -> + {proceed,Device} + end; + _ -> + io:format("rb: Cannot read from file~n"), + {proceed,Device} + end. + +handle_bad_form(Date, Msg, Device, Abort, Log) -> + io:format("rb: ERROR! A report on bad form was encountered. " ++ + "It can not be printed to the log.~n~n"), + io:format("Details:~n~p ~p~n~n", [Date,Msg]), + case {Abort,Device,open_log_file(Log)} of + {true,standard_io,standard_io} -> + io:format("rb: Logging aborted.~n"), + {abort,Device}; + {false,standard_io,standard_io} -> + io:format("rb: Logging resumed...~n~n"), + {proceed,Device}; + {_,_,standard_io} -> + io:format("rb: Can not reopen ~p. Logging aborted.~n", [Log]), + {abort,Device}; + {true,_,NewDevice} -> + io:format(NewDevice, + "~n~n************************* RB ERROR ************************~n" ++ + "A report on bad form was encountered here and the logging~n" ++ + "process was aborted. Note that there may well be remaining~n" ++ + "reports that haven't yet been logged. Please see the rb~n" ++ + "manual for more info.~n" ++ + "***********************************************************~n", []), + io:format("rb: Logging aborted.~n"), + {abort,NewDevice}; + {false,_,NewDevice} -> + io:format(NewDevice, + "~n ********* RB: UNPRINTABLE REPORT ********~n~n", []), + io:format("rb: Logging resumed...~n~n"), + {proceed,NewDevice} + end. + +read_rep_msg(Fd, FilePosition) -> + file:position(Fd, {bof, FilePosition}), + Res = + case catch read_report(Fd) of + {ok, Report} -> + {_ShortDescr, Date} = get_short_descr(Report), + {Date, Report}; + _ -> error + end, + file:close(Fd), + Res. |