%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.0, (the "License"); you may not use this file except in
%% compliance with the License. You may obtain a copy of the License at
%% http://www.erlang.org/EPL1_0.txt
%% 
%% 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.
%% 
%% The Original Code is Erlang-4.7.3, December, 1998.
%% 
%% The Initial Developer of the Original Code is Ericsson Telecom
%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson
%% Telecom AB. All Rights Reserved.
%% 
%% Contributor(s): ______________________________________.''
%%
%%%----------------------------------------------------------------------
%%% File    : disk_log_1.erl
%%% Author  : Claes Wikstrom <klacke@erix.ericsson.se>
%%% Purpose : Efficient file based log - implementation part
%%% Created : 25 Sep 1996 by Claes Wikstrom <klacke@erix.ericsson.se>
%%% Modified: 4 April 1997 by Martin Björklund <mbj@erlang.ericsson.se>
%%%             Split into several modules.  This module implements the
%%%             ADT part (process independent).
%%%             Added wrap log.
%%% Modified: 4 Jan 1998 by Esko Vierumäki <esko@erix.ericsson.se>
%%%             Added option Mode = read_write | read_only
%%%----------------------------------------------------------------------
-module(disk_log_1).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/3 $ ').
-author('klacke@erix.ericsson.se').

-export([open/3, log/2, close/1, truncate/2, chunk/4, sync/1]).
-export([mf_int_open/6, mf_int_log/3, mf_int_close/1,
	 mf_int_chunk/4, mf_int_chunk_step/4, mf_int_sync/1]).
-export([mf_ext_open/5, mf_ext_log/3, mf_ext_close/1, mf_ext_sync/1]).

-export([print_index_file/1]).
-export([read_index_file/3]).
-export([chunk_read_only/4]).
-export([mf_int_chunk_read_only/4]).

-include("disk_log.hrl").

%% Header defines
-define(HEADSZ, 8).
-define(MAGICINT, 203500599).  %% i32(?MAGICHEAD).

%% Object defines
-define(LOGMAGIC, [1,2,3,4]). 
-define(OPENED, [6,7,8,9]).
-define(CLOSED, [99,88,77,11]).
%%-define(DEBUG, true).

-record(handle, {dir, name, maxB, maxF, curB, curF, cur_fd, cur_cnt}).

-define(i32(Int), (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255.

%% at the head of a LOG file we have
%% [?LOGMAGIC, ?OPENED | ?CLOSED]
%% Otherwise it's not a LOG file

%% Following that, (the head), comes the logged items/terms
%% each logged item looks like [4_byte_size, MAGICHEAD, term_as_binary ...]

%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
log(File, X) when binary(X) ->
    Sz = size(X),
    case file:write(File, [?i32(Sz), ?MAGICHEAD, X]) of
	ok -> 1;
	{error, Error} -> throw({error, {file_error, Error}})
    end;
log(File, X) -> log(File, X, [], 0).
log(File, [X|T], List, Res) ->
    Sz = size(X),
    log(File, T, [List, ?i32(Sz), ?MAGICHEAD, X], Res+1);
log(File, [], List, Res) -> 
    fwrite(File, List),
    Res.

sync(Fd) -> file:sync(Fd).
  
truncate(Fd, Head) ->
    file:position(Fd, ?HEADSZ),
    file:truncate(Fd),
    case Head of
	{head, B} -> log(Fd, B);
	none -> ok
    end.

chunk(Fd, Pos, {B, true}, N) ->
    case handle_chunk(B, Pos, N) of
	{Cont, []} ->
	    chunk(Fd, Pos, B, N);
	Else ->
	    Else
    end;

chunk(Fd, Pos, B, N) ->
    case read_chunk(Fd, Pos) of
	eof ->
	    eof;
	{ok, Bin}  ->
	    handle_chunk(list_to_binary([B, Bin]), Pos + size(Bin), N);
	Other -> 
	    Other
    end.

read_chunk(Fd, Pos) ->
    file:position(Fd, Pos + ?HEADSZ),
    R = file:read(Fd, 8192),
    file:position(Fd, eof),
    R.

%% Format of log items is
%% [Size, Magic, binary_term]

handle_chunk(B, Pos, infinity) ->
    handle_chunk(B, Pos, 8192);  % There cannot be more than 8192 terms
				 % in each chunk.
handle_chunk(B, Pos, N) ->
    {Cont, Ack} = handle_chunk2(B, Pos, N, []),
    {Cont, lists:reverse(Ack)}.

handle_chunk2(B, Pos, 0, Ack) ->
    {{more, Pos, {B, true}}, Ack};
handle_chunk2(B, Pos, N, Ack) when size(B) > 7 ->
    {Head, Tail} =  split_binary(B, 8),
    {Sz, Mg} = split_binary(Head, 4),
    Size = i32(Sz),
    Magic = i32(Mg),
    if
	Magic /= ?MAGICINT ->
	    {error(chunk, corrupt_log_file), Ack};
	size(Tail) >= Size  ->
	    {BinTerm, Tail2} = split_binary(Tail, Size),
	    case catch binary_to_term(BinTerm) of
		{'EXIT', _} -> %% truncated binary !!!
		    {error(chunk, corrupt_log_file), Ack};
		Term ->
		    handle_chunk2(Tail2, Pos, N-1, [Term | Ack])
	    end;
	true ->
	    {{more, Pos, B}, Ack}
    end;
handle_chunk2(B, Pos, _N, Ack) ->
    {{more, Pos, B}, Ack}.





chunk_read_only(Fd, Pos, {B, true}, N) ->
    case handle_chunk_ro(B, Pos, N) of
	{Cont, []} ->
	    chunk_read_only(Fd, Pos, B, N);
	Else ->
	    Else
    end;
chunk_read_only(Fd, Pos, B, N) ->
    case read_chunk(Fd, Pos) of
	eof ->
	    eof;
	{ok, Bin}  ->
	    handle_chunk_ro(list_to_binary([B, Bin]), Pos + size(Bin), N);
	Other -> 
	    Other
    end.

%% Format of log items is
%% [Size, Magic, binary_term]

handle_chunk_ro(B, Pos, infinity) ->
    handle_chunk_ro(B, Pos, 8192);  %% There cannot be more than 8192 terms
                                    %% in each chunk.
handle_chunk_ro(B, Pos, N) ->
    case handle_chunk_ro2(B, Pos, N, [], 0) of
	{Cont, Ack, 0} -> %% No bad bytes
	    {Cont, lists:reverse(Ack)};
	{Cont, Ack, Bad} ->
	    {Cont, lists:reverse(Ack), Bad}
    end.

handle_chunk_ro2(B, Pos, 0, Ack, Bad) ->
    {{more, Pos, {B, true}}, Ack, Bad};
handle_chunk_ro2(B, Pos, N, Ack, Bad) when size(B) > 7 ->
    {Head, Tail} =  split_binary(B, 8),
    {Sz, Mg} = split_binary(Head, 4),
    Size = i32(Sz),
    Magic = i32(Mg),
    if
	Magic /= ?MAGICINT ->
	    {_, B2} = split_binary(B,1),
	    handle_chunk_ro2(B2, Pos, N-1, Ack, Bad+1);
	size(Tail) >= Size  ->
	    {BinTerm, Tail2} = split_binary(Tail, Size),
	    case catch binary_to_term(BinTerm) of
		{'EXIT', _} -> %% truncated binary !!!
		    {_, B2} = split_binary(B,1),
		    handle_chunk_ro2(B2, Pos, N-1, Ack, Bad+1);
		Term ->
		    handle_chunk_ro2(Tail2, Pos, N-1, [Term | Ack], Bad)
	    end;
	true ->
	    {{more, Pos, B}, Ack, Bad}
    end;
handle_chunk_ro2(B, Pos, _N, Ack, Bad) ->
    {{more, Pos, B}, Ack, Bad}.




close(Fd) ->
    mark(Fd, closed),
    file:close(Fd).

%% Ret: {ok, Fd} |
%%      {repaired, Fd, Terms, BadBytes} |
%%      {error, Reason}

open(FName, truncate, read_write) ->
    new_file(FName);
open(FName, Repair, read_write) ->
    case file:open(FName, [raw, binary, read]) of
	{ok, Fd} ->  %% File exists
	    case file:read(Fd, ?HEADSZ) of
		{ok, Head} when size(Head) == ?HEADSZ ->
		    case is_head(Head) of
			yes ->
			    file:close(Fd),
			    case file:open(FName, [raw, binary, read, write]) of
				{ok, Fd2} ->
				    mark(Fd2, opened),
				    file:position(Fd2, eof),
				    {ok, Fd2};
				Other ->
				    Other
			    end;
			yes_not_closed when Repair == true ->
			    repair(Fd, FName);
			yes_not_closed  ->
			    file:close(Fd),
			    error(open, need_repair);
			no ->
			    error(open, not_a_logfile)
		    end;
		{ok, _} ->
		    file:close(Fd),
		    new_file(FName);
		eof ->
		    file:close(Fd),
		    new_file(FName);
		Other ->
		    Other
	    end;
	Other ->
	    new_file(FName)
    end;
open(FName, Repair, read_only) ->
    case file:open(FName, [raw, binary, read]) of
	{ok, Fd} ->  %% File exists
	    case file:read(Fd, ?HEADSZ) of
		{ok, Head} when size(Head) == ?HEADSZ ->
		    case is_head(Head) of
			yes ->
			    file:position(Fd, eof),
			    {ok, Fd};
			yes_not_closed  ->
			    file:position(Fd, eof),
			    {ok, Fd};
			no ->
			    error(open, not_a_logfile)
		    end;
		{ok, _} ->
		    file:close(Fd),
		    new_file(FName);
		eof ->
		    file:close(Fd),
		    new_file(FName);
		Other ->
		    Other
	    end;
	Other ->
	    error(open, not_a_logfile)
    end.

mark(File, opened) ->
    file:position(File, 4),
    file:write(File, ?OPENED);
mark(File, closed) ->
    file:position(File, 4),
    file:write(File, ?CLOSED).

new_file(FName) ->
    case file:open(FName, [raw, binary, read, write]) of
	{ok, Fd} ->
	    file:truncate(Fd),
	    case file:write(Fd, [?LOGMAGIC, ?OPENED]) of
		ok -> 
		    {ok, Fd};
		Error ->
		    file:close(Fd),
		    Error
	    end;
	Other ->
	    Other
    end.

file_size(Fname) ->
    {ok, Info} = file:file_info(Fname),
    Fz = element(1, Info).

%% Ret: {repaired, Fd, {recovered, Terms}, {badbytes, BadBytes}}
repair(In, FName) ->
    error_logger:info_msg("disk_log: repairing ~p ...\n", [FName]),
    Newname = [FName, ".TMP"],
    case file:open(Newname, [raw, binary, write]) of %% will truncate
	{ok, Out} ->
	    case file:write(Out, [?LOGMAGIC, ?OPENED]) of
		ok ->
		    {ok, Pos} = file:position(In, ?HEADSZ),
		    Start = list_to_binary([]),
		    scan_f(Start, 8, FName, Out, In, 0, 0,
			   list_to_binary(?MAGICHEAD));
		{error, Error} ->
		    file:close(In),
		    {error, {file_error, Error}}
	    end;
	Other ->
	    Other
    end.

scan_f(B, Sz, FName, Out, In, Terms, Bad, Magic) when size(B) >= Sz ->
    case get_8(B) of
	{TermSize, Magic, RestB} when TermSize =< size(RestB) ->
	    {BinTerm, Tail} = split_binary(RestB, TermSize),
	    case catch binary_to_term(BinTerm) of
		{'EXIT', _} ->
		    {_, B2} = split_binary(B, 1),
		    scan_f(B2, 8, FName, Out, In, Terms, Bad+1, Magic);
		Term ->
		    log(Out, BinTerm),
		    scan_f(Tail, 8, FName, Out, In, Terms+1, Bad, Magic)
	    end;
	{TermSize, Magic, RestB} ->
	    %% size(RestB) < TermSize =>
	    %% size(B) = 8 + size(RestB) < 8 + TermSize =>
	    %% we'll get into 2:nd clause and read more bytes
	    scan_f(B, 8 + TermSize, FName, Out, In, Terms, Bad, Magic);
	{TermSize, BadMagic, RestB} ->
	    {_, B2} = split_binary(B, 1),
	    scan_f(B2, 8, FName, Out, In, Terms, Bad+1, Magic);
	Other ->
	    Other
    end;
scan_f(B, Sz, FName, Out, In, Terms, Bad, Magic) ->
    case file:read(In, 8192) of
	eof ->
	    done_scan(Out, In, FName, Terms, Bad);
	{ok, B2} ->
	    scan_f(list_to_binary([B, B2]), Sz, FName, Out, In,
		   Terms, Bad, Magic);
	Other ->
	    Other
    end.
	 
done_scan(Out, In, FName, RecoveredTerms, BadChars) ->
    file:close(Out),
    file:close(In),
    file:rename([FName,".TMP"], FName),
    {ok, New} = file:open(FName, [raw, binary, read, write]),
    file:position(New, eof),
    {repaired, New, RecoveredTerms, BadChars}.
   
get_8(Bin) ->
    {BTermSize, T1} = split_binary(Bin, 4),
    {BMagic, T2} = split_binary(T1, 4),
    {i32(BTermSize), BMagic, T2}.

is_head(Bin) ->
    L = binary_to_list(Bin),
    case catch {lists:sublist(L, 1,4), lists:sublist(L, 5, 4)} of
	{?LOGMAGIC, ?CLOSED} ->
	    yes;
	{?LOGMAGIC, ?OPENED} ->
	    yes_not_closed;
	_ ->
	    no
    end.

i32(Int) when binary(Int) ->
    i32(binary_to_list(Int));

i32(Int)  when integer(Int) -> [(Int bsr 24) band 255,
				(Int bsr 16) band 255,
				(Int bsr 8) band 255,
				Int band 255];
i32([X1,X2,X3,X4]) ->
    (X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4.

error(Op, {error, Reason}) ->
    error(Op, Reason);
error(Op, Reason) ->
    {error, Reason}.

%%-----------------------------------------------------------------
%% Func: mf_int_open/4, mf_ext_open/4
%% Args: Dir  = string()
%%       Name = string()
%%       MaxB = integer() 
%%       MaxF = byte()
%% Purpose: An ADT for wrapping logs.  mf_int_ writes binaries (mf_ext_
%%          writes bytes)
%%          to files in the directory Dir.  Each file is called
%%          Name.1, Name.2, ..., Name.MaxF.  Writes MaxB bytes on each
%%          file.  Creates a file called Name.idx in the Dir.  This
%%          file contains the last written FileName as one byte, and
%%          follwing that, the sizes of each file (size 0 no of items).
%%          On startup, this file is read, and the next available
%%          filename is used as first logfile.
%%          Reports can be browsed with Report Browser Tool (rb), or
%%          read with disk_log.
%% Returns: {ok, handle()} | {error, Reason}
%%-----------------------------------------------------------------
mf_int_open(Dir, Name, MaxB, MaxF, Repair, Mode) when MaxF > 0, MaxF < 256 -> 
    {First, Sz, TotSz, NFiles} = read_index_file(Dir, Name, Repair),
    NFirst = adjust_size(Dir, Name, MaxF, First, NFiles),
    case catch int_file_open(Dir, Name, NFirst, 0, 0, Repair, Mode) of
	{ok, Fd, _} ->
	    {ok, CurB} = file:position(Fd, cur),
	    {ok, #handle{dir = Dir, name = Name, maxB = MaxB,
			 maxF = MaxF, curF = NFirst, cur_fd = Fd,
			 cur_cnt = Sz, curB = CurB}, TotSz + Sz};
	{repaired, Fd, Rec, Bad, FSz} ->
	    {ok, #handle{dir = Dir, name = Name, maxB = MaxB,
			 maxF = MaxF, curF = NFirst, cur_fd = Fd,
			 cur_cnt = Rec, curB = FSz}, TotSz + Rec};
	Error ->
	    {error, Error}
    end.

mf_int_log(Handle, Bin, Wf) when binary(Bin) ->
    #handle{dir = Dir, name = Name, curB = CurB, maxB = MaxB, cur_cnt = CurCnt,
	    curF = CurF, maxF = MaxF, cur_fd = CurFd} = Handle,
    BSize = size(Bin) + ?HEADERSZ,
    if
	CurB + BSize < MaxB ->
	    log(Handle#handle.cur_fd, Bin),
	    {ok, Handle#handle{curB = Handle#handle.curB + BSize +?HEADERSZ,
			       cur_cnt = CurCnt+1}, 1};
	true ->
	    close(CurFd),
	    NewF = inc(CurF, MaxF),
	    {ok, NewFd, Lost} = int_file_open(Dir, Name, NewF, CurF, CurCnt),
	    {M,F,A} = Wf,
	    Nh = case apply(M, F, A) of
		     {ok, BinHead} -> log(NewFd, BinHead);
		     _ -> 0
		 end,
	    log(NewFd, Bin),
	    {ok, Handle#handle{cur_fd = NewFd, curF = NewF, cur_cnt = Nh + 1,
			       curB = BSize + ?HEADERSZ}, Nh + 1, Lost}
    end;
mf_int_log(Handle, Bins, Wf) ->
    #handle{dir = Dir, name = Name, curB = CurB, maxB = MaxB,
	    curF = CurF, maxF = MaxF, cur_fd = CurFd, cur_cnt = CurCnt} =Handle,
    {FirstBins, LastBins} = split_bins(CurB, MaxB, [], Bins, ?HEADERSZ), 
    Nf = case FirstBins of
	     [] -> 0;
	     _ -> log(CurFd, FirstBins)
	 end,
    case LastBins of
	[] -> {ok, Handle, Nf};
	_  ->
	    close(CurFd),
	    NewF = inc(CurF, MaxF),
	    {ok, NewFd, Lost1} = int_file_open(Dir, Name, NewF, CurF, CurCnt),
	    {M,F,A} = Wf,
	    Nh = case apply(M, F, A) of
		     {ok, BinHead} -> log(NewFd, BinHead);
		     _ -> 0
		 end,
	    case mf_int_log(Handle#handle{cur_fd = NewFd, curF =NewF,cur_cnt=Nh,
					  curB=?HEADERSZ}, LastBins, Wf) of
		{ok, H2, Nf2, Lost2} -> {ok, H2, Nf2 + Nf + Nh, Lost1 + Lost2};
		{ok, H2, Nf2} -> {ok, H2, Nf2 + Nf + Nh, Lost1}
	    end;
	Error -> Error
    end.

mf_int_chunk(Handle, 0, Bin, N) ->
    FirstF = find_first_file(Handle),
    mf_int_chunk(Handle, {FirstF, 0}, Bin, N);

mf_int_chunk(Handle, {FileNo, Pos}, Bin, N) when FileNo == Handle#handle.curF ->
    conv(chunk(Handle#handle.cur_fd, Pos, Bin, N), FileNo);

mf_int_chunk(Handle, {FileNo, Pos}, Bin, N) ->
    FName = mk_name(Handle#handle.dir, Handle#handle.name, FileNo),
    case open(FName, true, read_only) of
	{error, Reason} ->
	    {error, Reason};
	Ok ->
	    Fd = element(2, Ok),
	    case chunk(Fd, Pos, Bin, N) of
		eof ->
		    close(Fd),
		    mf_int_chunk(Handle, {inc(FileNo, Handle#handle.maxF), 0},
				 [], N);
		Other ->
		    close(Fd),
		    conv(Other, FileNo)
	    end
    end.

mf_int_chunk_read_only(Handle, 0, Bin, N) ->
    FirstF = find_first_file(Handle),
    mf_int_chunk_read_only(Handle, {FirstF, 0}, Bin, N);

mf_int_chunk_read_only(Handle, {FileNo, Pos}, Bin, N) when FileNo == Handle#handle.curF ->
    conv_ro(chunk_read_only(Handle#handle.cur_fd, Pos, Bin, N), FileNo);

mf_int_chunk_read_only(Handle, {FileNo, Pos}, Bin, N) ->
    FName = mk_name(Handle#handle.dir, Handle#handle.name, FileNo),
    case open(FName, true, read_only) of
	{error, Reason} ->
	    {error, Reason};
	Ok ->
	    Fd = element(2, Ok),
	    case chunk_read_only(Fd, Pos, Bin, N) of
		eof ->
		    close(Fd),
		    mf_int_chunk_read_only(Handle, {inc(FileNo, Handle#handle.maxF), 0},
				 [], N);
		Other ->
		    close(Fd),
		    conv_ro(Other, FileNo)
	    end
    end.



mf_int_chunk_step(Handle, 0, Bin, Step) ->
    FirstF = find_first_file(Handle),
    mf_int_chunk_step(Handle, {FirstF, 0}, Bin, Step);
mf_int_chunk_step(Handle, {FileNo, Pos}, Bin, Step) ->
    NFileNo = inc(FileNo, Handle#handle.maxF, Step),
    Dir = Handle#handle.dir,
    FName = mk_name(Handle#handle.name, NFileNo),
    case file:list_dir(Dir) of
	{ok, Files} ->
	    case lists:member(FName, Files) of
		true -> {ok,{more, {NFileNo, 0}, Bin}};
		false -> {error, end_of_log}
	    end;
	Error ->
	    Error
    end.

mf_int_sync(#handle{cur_fd = Fd}) ->
    sync(Fd).

mf_int_close(#handle{dir = Dir, name = Name, curF = CurF,
		  cur_fd = CurFd, cur_cnt = CurCnt}) ->
    close(CurFd),
    write_index_file(Dir, Name, CurF, CurF, CurCnt),
    ok.

mf_ext_open(Dir, Name, MaxB, MaxF, Repair) when MaxF > 0, MaxF < 256 -> 
    {First, Sz, TotSz, NFiles} = read_index_file(Dir, Name, Repair),
    NFirst = adjust_size(Dir, Name, MaxF, First, NFiles),
    case catch ext_file_open(Dir, Name, NFirst, 0, 0, Repair) of
	{ok, Fd, Lost} ->
	    {ok, CurB} = file:position(Fd, eof),
	    {ok, #handle{dir = Dir, name = Name, maxB = MaxB,
			 maxF = MaxF, cur_cnt = TotSz + Sz, 
			 curF = NFirst, cur_fd = Fd,
			 curB = CurB},
	     TotSz + Sz};
	Error -> {error, Error}
    end.

mf_ext_log(Handle, Bin, Wf) when binary(Bin) ->
    #handle{dir = Dir, name = Name, curB = CurB, maxB = MaxB, cur_cnt = CurCnt,
	    curF = CurF, maxF = MaxF, cur_fd = CurFd} = Handle,
    BSize = size(Bin),
    if
	CurB + BSize =< MaxB ->
	    fwrite(Handle#handle.cur_fd, Bin),
	    {ok, Handle#handle{curB = CurB + BSize, cur_cnt = CurCnt + 1}, 1};
	true ->
	    file:close(CurFd),
	    NewF = inc(CurF, MaxF),
	    {ok, NewFd, Lost} = ext_file_open(Dir, Name, NewF, CurF, CurCnt),
	    {M,F,A} = Wf,
	    Nh = case apply(M, F, A) of
		     {ok, BinHead} -> fwrite(NewFd, BinHead);
		     _ -> 0
		 end,
	    fwrite(NewFd, Bin),
	    {ok, Handle#handle{cur_fd = NewFd, curF = NewF, curB = BSize,
			       cur_cnt = Nh + 1}, Nh + 1, Lost}
    end;
mf_ext_log(Handle, Bins, Wf) ->
    #handle{dir = Dir, name = Name, curB = CurB, maxB = MaxB, cur_cnt = CurCnt,
	    curF = CurF, maxF = MaxF, cur_fd = CurFd} = Handle,
    {FirstBins, LastBins} = split_bins(CurB, MaxB, [], Bins, 0), 
    Nf = case FirstBins of
	     [] -> 0;
	     _ -> fwrite(CurFd, FirstBins)
	 end,
    case LastBins of
	[] when integer(Nf) -> {ok, Handle, Nf};
	_ when integer(Nf) ->
	    file:close(CurFd),
	    NewF = inc(CurF, MaxF),
	    {ok, NewFd, Lost1} = ext_file_open(Dir, Name, NewF, CurF, CurCnt),
	    {M,F,A} = Wf,
	    Nh = case apply(M, F, A) of
		     {ok, BinHead} -> fwrite(NewFd, BinHead);
		     _ -> 0
		 end,
	    case mf_ext_log(Handle#handle{cur_fd = NewFd, curF = NewF,
					  curB =0, cur_cnt = Nh},
			    LastBins, Wf) of
		{ok, H2, Nf2, Lost2} -> {ok, H2, Nf2 + Nf + Nh, Lost1 + Lost2};
		{ok, H2, Nf2} -> {ok, H2, Nf2 + Nf + Nh, Lost1}
	    end;
	Error -> Error
    end.

mf_ext_sync(#handle{cur_fd = Fd}) ->
    file:sync(Fd).

mf_ext_close(#handle{dir = Dir, name = Name, curF = CurF,
		  cur_fd = CurFd, cur_cnt = CurCnt}) ->
    file:close(CurFd),
    write_index_file(Dir, Name, CurF, CurF, CurCnt),
    ok.

%%-----------------------------------------------------------------
%% Misc functions
%%-----------------------------------------------------------------
int_file_open(Dir, Name, NewFile, OldFile, OldCnt) ->
    int_file_open(Dir, Name, NewFile, OldFile, OldCnt, truncate, read_write).
int_file_open(Dir, Name, NewFile, OldFile, OldCnt, Repair, Mode) ->
    case open(N=mk_name(Dir, Name, NewFile), Repair, Mode) of
	{ok, Fd} ->
	    Lost = write_index_file(Dir, Name, NewFile, OldFile, OldCnt),
	    {ok, Fd, Lost};
	{repaired, Fd, Recovered, BadBytes} ->
	    write_index_file(Dir, Name, NewFile, OldFile, OldCnt),
	    {repaired, Fd, Recovered, BadBytes, file_size(N)};
	_ -> 
	    exit(file_open)
    end.

ext_file_open(Dir, Name, NewFile, OldFile, OldCnt) ->
    ext_file_open(Dir, Name, NewFile, OldFile, OldCnt, truncate).
ext_file_open(Dir, Name, NewFile, OldFile, OldCnt, Repair) ->
    case file:open(mk_name(Dir, Name, NewFile),
		   [raw, binary, write | mk_extra(Repair)]) of
	{ok, Fd} ->
	    Lost = write_index_file(Dir, Name, NewFile, OldFile, OldCnt),
	    {ok, Fd, Lost};
	_ -> 
	    exit(file_open)
    end.

mk_extra(truncate) -> [];
mk_extra(_) -> [read].

%% Ret: {CurFileNo, CurFileSz, TotSz, NoOfFiles}
%%              (TotSz does not include CurFileSz)
read_index_file(Dir, Name, truncate) ->
    file:delete(filename:join(Dir, Name ++ ".idx")),
    {1, 0, 0, 0};
read_index_file(Dir, Name, _) ->
    case file:read_file(filename:join(Dir, Name ++ ".idx")) of
	{ok, Bin} when size(Bin) >= 1 ->
	    case split_binary(Bin, 1) of
		{BIdx, Tail} when (size(Tail) rem 4) == 0 ->
		    case hd(binary_to_list(BIdx)) of
			CurF when 0 < CurF, CurF < 256 ->
			    parse_index(CurF, 1, Tail, 0, 0, 0);
			_ ->
			    {1, 0, 0, 0}
		    end;
		_ -> {1, 0, 0, 0}
	    end;
	_ -> {1, 0, 0, 0}
    end.

parse_index(CurF, CurF, Bin, CurSz, TotSz, NFiles) when size(Bin) >= 4 ->
    {Sz, Tail} = split_binary(Bin, 4),
    parse_index(CurF, CurF+1, Tail, i32(Sz), TotSz, NFiles+1);
parse_index(CurF, N, Bin, CurSz, TotSz, NFiles) when size(Bin) >= 4->
    {Sz, Tail} = split_binary(Bin, 4),
    parse_index(CurF, N+1, Tail, CurSz, TotSz + i32(Sz), NFiles+1);
parse_index(CurF, _, _, CurSz, TotSz, NFiles) ->
    {CurF, CurSz, TotSz, NFiles}.
    
%%-----------------------------------------------------------------
%% Fileformat for index file
%%
%% CurFileNo SizeFile1 SizeFile2  ... SizeFileN
%%   1 byte   4 bytes    4 bytes       4 bytes
%%-----------------------------------------------------------------

print_index_file(File) ->
    io:format("-- Index begin --~n"),
    case file:read_file(File) of
	{ok, Bin} when size(Bin) >= 1 ->
	    case split_binary(Bin, 1) of
		{BIdx, Tail} when (size(Tail) rem 4) == 0 ->
		    case hd(binary_to_list(BIdx)) of
			CurF when 0 < CurF, CurF < 256 ->
			    io:format("cur file: ~w~n", [CurF]),
			    loop_index(Tail);
			_ ->
			    ok
		    end;
		_ -> ok
	    end;
	_ -> ok
    end,
    io:format("-- end --~n").

loop_index(Bin) when size(Bin) >= 4 ->
    {Sz, Tail} = split_binary(Bin, 4),
    io:format("   items: ~w~n", [i32(Sz)]),
    loop_index(Tail);
loop_index(_) ->
    done.

%%-----------------------------------------------------------------
%% Returns: No of lost items (if an old file was truncated)
%%-----------------------------------------------------------------
write_index_file(Dir, Name, NewFile, OldFile, OldCnt) ->
    case file:open(filename:join([Dir, Name ++ ".idx"]),
		   [raw, binary, read, write]) of
	{ok, Fd} ->
	    ex_fwrite(Fd, [NewFile]),
	    if
		OldFile > 0 ->
		    file:position(Fd, 1 + (NewFile - 1)*4),
		    R = file:read(Fd, 4),
		    file:position(Fd, 1 + (OldFile - 1)*4),
		    ex_fwrite(Fd, i32(OldCnt)),
		    file:close(Fd),
		    case R of
			{ok, Lost} when size(Lost) == 4 -> i32(Lost);
			_ -> 0
		    end;
		true -> 	
		    file:position(Fd, 1 + (NewFile - 1)*4),
		    ex_fwrite(Fd, i32(OldCnt)),
		    file:close(Fd),
		    0
	    end;
	_ -> exit(open_index_file)
    end.

ex_fwrite(Fd, B) ->
    case file:write(Fd, B) of
	ok -> ok;
	{error, Error} -> exit({error, {file_error, Error}})
    end.

conv({{more, Pos, Bin}, Terms}, FileNo) ->
    {{more, {FileNo, Pos}, Bin}, Terms};
conv(Other, _) ->
    Other.

conv_ro({{more, Pos, Bin}, Terms, 0}, FileNo) ->
    {{more, {FileNo, Pos}, Bin}, Terms};
conv_ro({{more, Pos, Bin}, Terms, Bad}, FileNo) ->
    {{more, {FileNo, Pos}, Bin}, Terms, Bad};
conv_ro(Other, _) ->
    Other.

%%-----------------------------------------------------------------
%% Used to delete and rename unnecessary files when a wrap log
%% is opened with new size which is less than prev size.
%% For example, old:  {1000, 4} new: {800, 2}
%% Old: 1 2 3 4 - 3 current (= First)
%% New: 1 2 - 1 new current, 3 is renamed to 1, 4 is renamed to 2.
%%-----------------------------------------------------------------
adjust_size(Dir, Name, MaxF, First, NFiles) when MaxF >= NFiles ->
    First;
adjust_size(Dir, Name, MaxF, First, NFiles) when First =< MaxF ->
    rename(Dir, Name, (NFiles-(MaxF-First))+1, First+1, MaxF),
    delete(Dir, Name, MaxF+1, NFiles),
    First;
adjust_size(Dir, Name, MaxF, First, NFiles) ->
    rename(Dir, Name, (First-MaxF)+1, 1, MaxF),
    delete(Dir, Name, MaxF+1, NFiles),
    MaxF.
    
    
rename(Dir, Name, From, To, MaxF) when MaxF < To ->
    ok;
rename(Dir, Name, From, To, Max) ->
    file:rename(mk_name(Dir, Name, From), mk_name(Dir, Name, To)),
    rename(Dir, Name, From+1, To+1, Max).
    
delete(Dir, Name, File, Max) when File > Max ->
    ok;
delete(Dir, Name, File, Max) ->
    file:delete(mk_name(Dir, Name, File)),
    delete(Dir, Name, File+1, Max).
		

find_first_file(#handle{dir = Dir, name = Name, curF = CurF, maxF = MaxF}) ->
    fff(Dir, Name, inc(CurF, MaxF), CurF, MaxF).

fff(Dir, Name, CurF, CurF, MaxF) -> CurF;
fff(Dir, Name, MaybeFirstF, CurF, MaxF) ->
    N = mk_name(Dir, Name, MaybeFirstF),
    case file:file_info(N) of
	{ok, _} -> MaybeFirstF;
	_ -> fff(Dir, Name, inc(MaybeFirstF, MaxF), CurF, MaxF)
    end.
	    
fwrite(Fd, B) ->
    case file:write(Fd, B) of
	ok -> 1;
	{error, Error} -> throw({error, {file_error, Error}})
    end.

split_bins(CurB, MaxB, First, [X | Last], HeaderSz) ->
    NextB = CurB + size(X) + HeaderSz,
    if
	NextB =< MaxB, CurB == 0, First == [] ->
	    {X, Last}; % To avoid infinite loop - we allow the file to be
	               % larger than MaxB if it's just one item on the file.
	NextB =< MaxB ->
	    split_bins(NextB, MaxB, [X | First], Last, HeaderSz);
	true ->
	    {lists:reverse(First), [X | Last]}
    end;
split_bins(_, _, First, [], _) ->
    {lists:reverse(First), []}.
	    
mk_name(Dir, Name, FileNo) ->
    filename:join([Dir, Name ++ "." ++ integer_to_list(FileNo)]).
mk_name(Name, FileNo) ->
    Name ++ "." ++ integer_to_list(FileNo).

inc(N, Max) -> inc(N, Max, 1).
inc(N, Max, Step) ->
    Nx = (N + Step) rem Max,
    if
	Nx > 0 -> Nx;
	true -> Nx + Max
    end.

