%% ``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.erl
%%% Author  : Claes Wikstrom <klacke@erix.ericsson.se>
%%% Purpose : Efficient file based log - process 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
%%%             Each log is implemented in one process
%%%             Added wrap logs, halt logs etc.
%%% Modified: 4 Jan 1998 by Esko Vierumäki <esko@erix.ericsson.se>
%%%             Added option Mode = read_write | read_only
%%%----------------------------------------------------------------------

-module(disk_log).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/3 $ ').
-author('klacke@erix.ericsson.se').

-export([start/0, istart_link/0, 
	 log/2, log_terms/2, blog/2, blog_terms/2,
	 alog/2, alog_terms/2, balog/2, balog_terms/2,
	 close/1, sync/1, open/1, truncate/1, truncate/2, btruncate/2,
	 reopen/2, reopen/3, breopen/3,
	 chunk/2, chunk/3, chunk_step/3, block/1, block/2, unblock/1,
	 info/0, info/1]).

%% Internal exports
-export([init/1, internal_open/2, get_log_pids/1, lh/2,
	 system_continue/3, system_terminate/4, system_code_change/4]).

-record(log,
	{status = ok,         %%  ok | {blocked, QueueLogRecords}
	 name,                %%  the key leading to this structure
	 blocked_by = none,   %%  pid of blocker | none
	 users = 1,           %%  number of concurrent users
	 filename,            %%  real name of the file
	 owners,              %%  [{pid, notify}]
	 type,                %%  halt_int | wrap_int | halt_ext | wrap_ext
	 format = internal,   %%  internal | external
	 head = none,         %%  none | {head, H} | {M,F,A}
	                      %%  called when wraplog wraps
	 mode,                %%  read_write | read_only
	 size,
	 extra}).             %%  e.g. the fd, max_size etc.

-record(state, {queue = [], links = [], parent, cnt = 0, args, notify,
		error_status = ok   %%  ok | {error, Reason}
	       }).

-include("disk_log.hrl").

%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------

%%-----------------------------------------------------------------
%% This module implements the API, and the processes for each log.
%% There is one process/log.
%%-----------------------------------------------------------------

open(A) ->
    disk_log_server:open(check_arg(A, #arg{})).
log(Log, Term) -> 
    req(Log, {log, term_to_binary(Term)}).
blog(Log, Bytes) ->
    req(Log, {blog, check_bytes(Bytes)}).
log_terms(Log, Terms) ->
    Bs = lists:map({erlang, term_to_binary}, [], Terms),
    req(Log, {log, Bs}).
blog_terms(Log, Bytess) ->
    Bs = lists:map(fun check_bytes/1, Bytess),
    req(Log, {blog, Bytess}).
alog(Log, Term) -> 
    notify(Log, {alog, term_to_binary(Term)}).
alog_terms(Log, Terms) ->
    Bs = lists:map({erlang, term_to_binary}, [], Terms),
    notify(Log, {alog, Bs}).
balog(Log, Bytes) -> 
    notify(Log, {balog, check_bytes(Bytes)}).
balog_terms(Log, Bytess) ->
    Bs = lists:map(fun check_bytes/1, Bytess),
    notify(Log, {balog, Bs}).
close(Log) -> 
    req(Log, close).

check_bytes(Binary) when binary(Binary) -> Binary;
check_bytes(Bytes) -> list_to_binary(Bytes).

truncate(Log) -> 
    req(Log, {truncate, none}).
truncate(Log, Head) ->
    req(Log, {truncate, {head, term_to_binary(Head)}}).
btruncate(Log, Head) ->
    req(Log, {truncate, {head, check_bytes(Head)}}).

reopen(Log, NewFile) ->
    req(Log, {reopen, NewFile, none}).
reopen(Log, NewFile, NewHead) ->
    req(Log, {reopen, NewFile, {ok, term_to_binary(NewHead)}}).
breopen(Log, NewFile, NewHead) ->
    req(Log, {reopen, NewFile, {ok, check_bytes(NewHead)}}).

    
sync(Log) -> req(Log, sync).
block(Log) -> block(Log, true).
block(Log, QueueLogRecords) -> req(Log, {block, QueueLogRecords}).
unblock(Log) -> req(Log, unblock).
info(Log) -> sreq(Log, info).
info() -> % debug
    lists:map(fun({Log, _}) -> sreq(Log, info) end,
	      ets:tab2list(disk_log_names)).
	      

%% This function Takes 3 args, a Log, a Continuation and N.
%% It retuns a {Cont2, ObjList} | eof | {error, Reason}
%% The initial continuation is the atom 'start'

chunk(Log, Cont) ->
    chunk(Log, Cont, infinity).
chunk(Log, Cont, infinity) ->
    ichunk(Log, Cont, infinity);
chunk(Log, Cont, N) when integer(N), N > 0 ->
    ichunk(Log, Cont, N).

ichunk(Log, start, N) ->
    sreq(Log, {chunk, 0, [], N});
ichunk(Log, {more, Pos, B}, N) ->
    sreq(Log, {chunk, Pos, B, N});
ichunk(Log, {error, Reason}, _) ->
    {error, Reason}.

chunk_step(Log, Cont, N) when integer(N) ->
    ichunk_step(Log, Cont, N).

ichunk_step(Log, start, N) ->
    sreq(Log, {chunk_step, 0, [], N});
ichunk_step(Log, {more, Pos, B}, N) ->
    sreq(Log, {chunk_step, Pos, B, N});
ichunk_step(Log, {error, Reason}, _) ->
    {error, Reason}.



istart_link() ->  
    {ok, proc_lib:spawn_link(disk_log, init, [self()])}.

%% Only for backwards compatibility, could probably be removed.
start() ->
    disk_log_server:start().

internal_open(Pid, A) ->
    req2(Pid, {internal_open, A}).

check_arg([], Res) -> 
    if  %% check result
	Res#arg.name == 0 -> 
	    {error, {badarg, name}};
	Res#arg.file == none ->
	    case catch lists:concat([Res#arg.name, ".LOG"]) of
		{'EXIT',_} -> {error, {badarg, file}};
		FName ->  check_arg([], Res#arg{file = FName})
	    end;
	Res#arg.type == halt, tuple(Res#arg.size) ->
	    {error, {badarg, size}};
	Res#arg.type == wrap, Res#arg.size == infinity ->
	    case file:read_file_info(Res#arg.file++".idx") of
		{error, _} ->
		    {error,{badarg,size}};
		{ok, _} ->
		    Dir = filename:dirname(Res#arg.file),
		    Name = filename:basename(Res#arg.file),
		    disk_log_1:print_index_file(Res#arg.file++".idx"),
		    {A, FileSz, B, NoOf} = disk_log_1:read_index_file(Dir, Name, false),
		    check_arg([], Res#arg{size = {FileSz, NoOf}})
	    end;
	Res#arg.type == wrap, tuple(Res#arg.size) ->
	    {ok, Res};
	Res#arg.type == wrap ->
	    {error, {badarg, size}};
	Res#arg.repair == truncate, Res#arg.mode == read_only ->
	    {error, {badarg, 'not possible to truncate file in read only mode'}};
	true ->
	    {ok, Res}
    end;
check_arg([{file, F} | Tail], Res) ->
    check_arg(Tail, Res#arg{file = F});
check_arg([{linkto, Pid} |Tail], Res) when pid(Pid) ->
    check_arg(Tail, Res#arg{linkto = Pid});
check_arg([{linkto, none} |Tail], Res) ->
    check_arg(Tail, Res#arg{linkto = none});
check_arg([{name, Name}|Tail], Res) ->
    check_arg(Tail, Res#arg{name =Name});
check_arg([{repair, Repair}|Tail], Res) ->
    check_arg(Tail, Res#arg{repair = Repair});
check_arg([{size, Int}|Tail], Res) when integer(Int), Int > 0 ->
    check_arg(Tail, Res#arg{size = Int});
check_arg([{size, infinity}|Tail], Res) ->
    check_arg(Tail, Res#arg{size = infinity});
check_arg([{size, {MaxB,MaxF}}|Tail], Res) when integer(MaxB), integer(MaxF),
						MaxB > 0, MaxF > 0, MaxF < 256 ->
    check_arg(Tail, Res#arg{size = {MaxB, MaxF}});
check_arg([{type, wrap}|Tail], Res) ->
    check_arg(Tail, Res#arg{type = wrap});
check_arg([{type, halt}|Tail], Res) ->
    check_arg(Tail, Res#arg{type = halt});
check_arg([{format, internal}|Tail], Res) ->
    check_arg(Tail, Res#arg{format = internal});
check_arg([{format, external}|Tail], Res) ->
    check_arg(Tail, Res#arg{format = external});
check_arg([{distributed, Nodes}|Tail], Res) when list(Nodes) ->
    check_arg(Tail, Res#arg{distributed = {true, Nodes}});
check_arg([{notify, Bool}|Tail], Res) ->
    check_arg(Tail, Res#arg{notify = Bool});
check_arg([{head_func, {M,F,A}}|Tail], Res) ->
    check_arg(Tail, Res#arg{head = {M,F,A}});
check_arg([{head, Term}|Tail], Res) ->
    check_arg(Tail, Res#arg{head = {head, Term}});
check_arg([{mode, read_only}|Tail], Res) ->
    check_arg(Tail, Res#arg{mode = read_only});
check_arg([{mode, read_write}|Tail], Res) ->
    check_arg(Tail, Res#arg{mode = read_write});
check_arg(Arg, _) ->
    {error, {badarg, Arg}}.

%%%-----------------------------------------------------------------
%%% Server functions
%%%-----------------------------------------------------------------
init(Parent) ->
    process_flag(trap_exit, true),
    loop(#state{parent = Parent}).

loop(State) ->
    receive
	Message ->
	    handle(Message, State)
    end.

handle({From, {log, B}}, S) ->
    case get(log) of
	L when L#log.mode == read_only ->
	    reply(From, {error, {read_only_mode, L#log.name}}, S);
	L when L#log.status == ok, L#log.format == internal ->
	    case do_log(L, B) of
		N when integer(N) ->
		    reply(From, ok, (state_ok(S))#state{cnt = S#state.cnt + N});
		Else ->
		    reply(From, Else, state_err(S, Else))
	    end;
	L when L#log.status == ok, L#log.format == external ->
	    reply(From, {error, {format_external, L#log.name}}, S);
	L when L#log.status == {blocked, false} ->
	    reply(From, {error, {blocked_log, L#log.name}}, S);
	_ ->
	    loop(S#state{queue = [{From, {log, B}} | S#state.queue]})
    end;    

handle({From, {blog, B}}, S) ->
    case get(log) of
	L when L#log.mode == read_only ->
	    reply(From, {error, {read_only_mode, L#log.name}}, S);
	L when L#log.status == ok ->
	    case do_log(L, B) of
		N when integer(N) ->
		    reply(From, ok, (state_ok(S))#state{cnt = S#state.cnt + N});
		Else ->
		    reply(From, Else, state_err(S, Else))
	    end;
	L when L#log.status == {blocked, false} ->
	    reply(From, {error, {blocked_log, L#log.name}}, S);
	_ ->
	    loop(S#state{queue = [{From, {blog, B}} | S#state.queue]})
    end;

handle({alog, B}, S) ->
    case get(log) of
	L when L#log.mode == read_only ->
	    notify_owners({read_only,B}),
	    loop(S);
	L when L#log.status == ok, L#log.format == internal ->
	    case do_log(L, B) of
		N when integer(N) ->
		    loop((state_ok(S))#state{cnt = S#state.cnt + N});
		{error, full} ->
		    loop(state_ok(S));
		{error, Reason} ->
		    loop(state_err(S, {error, Reason}))
	    end;
	L when L#log.status == ok ->
	    loop(S);
	L when L#log.status == {blocked, false} ->
	    loop(S);
	_ ->
	    loop(S#state{queue = [{alog, B} | S#state.queue]})
    end;

handle({balog, B}, S) ->
    case get(log) of
	L when L#log.mode == read_only ->
	    notify_owners({read_only,B}),
	    loop(S);
	L when L#log.status == ok ->
	    case do_log(L, B) of
		N when integer(N) ->
		    loop((state_ok(S))#state{cnt = S#state.cnt + N});
		{error, full} ->
		    loop(state_ok(S));
		{error, Reason} ->
		    loop(state_err(S, {error, Reason}))
	    end;
	L when L#log.status == ok ->
	    loop(S);
	L when L#log.status == {blocked, false} ->
	    loop(S);
	_ ->
	    loop(S#state{queue = [{alog, B} | S#state.queue]})
    end;

handle({From, {block, QueueLogRecs}}, S) ->
    case get(log) of
	L when L#log.status == ok ->
	    L2 = L#log{status = {blocked, QueueLogRecs},
		       blocked_by = From},
	    put(log, L2),
	    Links = do_link(From, S#state.links),
	    reply(From, ok, S#state{links = Links});
	_ ->
	    loop(S#state{queue = [{From, {block, QueueLogRecs}} |
				  S#state.queue]})
    end;
    
handle({From, unblock}, S) ->
    case get(log) of
	L when L#log.status == ok ->
	    reply(From, {error, {not_blocked, L#log.name}}, S);
	L ->
	    Links2 = do_unlink(L#log.blocked_by, S#state.links),
	    L2 = L#log{blocked_by = none,
		       status = ok},
	    put(log, L2),
	    send_self(S#state.queue),
	    reply(From, ok, S#state{queue = [], links = Links2})
    end;

handle({From, sync}, S) ->
    case get(log) of
	L when L#log.mode == read_only ->
	    reply(From, {error, {read_only_mode, L#log.name}}, S);
	L when L#log.status == ok ->
	    Res = do_sync(L),
	    reply(From, Res, state_err(S, Res));
	_ ->
	    loop(S#state{queue = [{From, sync} | S#state.queue]})
    end;

handle({From, {truncate, Head}}, S) ->
    case get(log) of
	L when L#log.mode == read_only ->
	    reply(From, {error, {read_only_mode, L#log.name}}, S);
	L when L#log.status == ok ->
	    case do_trunc(L, Head) of
		ok ->
		    erase(is_full),
		    notify_owners({truncated, S#state.cnt}),
		    N = if Head == none -> 0; true -> 1 end,
		    reply(From, ok, (state_ok(S))#state{cnt = N});
		Else ->
		    reply(From, Else, state_err(S, Else))
	    end;
	_ ->
	    loop(S#state{queue = [{From, {truncate, Head}} | S#state.queue]})
    end;

handle({From, {chunk, Pos, B, N}},  S) ->
    case get(log) of
	L when L#log.status == ok ->	
	    R = do_chunk(L, Pos, B, N),
	    reply(From, R, S);
	L when L#log.blocked_by == From ->	
	    R = do_chunk(L, Pos, B, N),
	    reply(From, R, S);
	_ ->
	    loop(S#state{queue = [{From, {chunk, Pos, B}} | S#state.queue]})
    end;

handle({From, {chunk_step, Pos, B, N}},  S) ->
    case get(log) of
	L when L#log.status == ok ->	
	    R = do_chunk_step(L, Pos, B, N),
	    reply(From, R, S);
	L when L#log.blocked_by == From ->	
	    R = do_chunk_step(L, Pos, B, N),
	    reply(From, R, S);
	_ ->
	    loop(S#state{queue = [{From, {chunk_step, Pos, B}} |S#state.queue]})
    end;

handle({From, {reopen, NewFile, Head}}, S) ->
    case get(log) of
	L when L#log.mode == read_only ->
	    reply(From, {error, {read_only_mode, L#log.name}}, S);
	L when L#log.type == wrap ->
	    reply(From, {error, wrap}, S);
	L when L#log.status == ok, L#log.filename /= NewFile  ->
	    do_close2(L),
	    case file:rename(File = L#log.filename, NewFile) of
		ok ->
		    H = merge_head(Head, L#log.head),
		    case do_open((S#state.args)#arg{name = L#log.name,
						    linkto = none,
						    repair = truncate,
						    head = H,
						    file = File},
				 []) of
			{ok, Res, [], Cnt} ->
			    L2 = get(log),
			    put(log, L2#log{owners = L#log.owners,
					    head = L#log.head,
					    users = L#log.users}),
			    notify_owners({truncated, S#state.cnt}),
			    erase(is_full),
			    case Res of
				{error, _} ->
				    do_stop(S),
				    From ! {disk_log, self(), Res},
				    exit({failed, reopen});
				_ ->
				    reply(From, ok, S#state{cnt = Cnt})
			    end;
			{Res, _, _} ->
			    do_stop(S),
			    From ! {disk_log, self(), Res},
			    exit({failed, reopen})
		    end;
		Error ->
		    do_stop(S),
		    From ! {disk_log, self(), Error},
		    exit({failed, reopen})
	    end;
	L when L#log.status == ok ->
	    reply(From, {error, {same_file_name, L#log.name}}, S);
	L ->
	    reply(From, {error, {blocked_log, L#log.name}}, S)
    end;

handle({From, {internal_open, A}}, S) ->
    case get(log) of
	undefined ->
	    case do_open(A, S#state.links) of % does the put
		{ok, Res, Links2, Cnt} ->
		    reply(From, Res, S#state{links=Links2, args=A, cnt=Cnt});
		{Res, Links2, Cnt} ->
		    do_stop(S#state{links=Links2}),
		    From ! {disk_log, self(), Res},
		    exit(shutdown)
	    end;
	L when L#log.mode == read_only, A#arg.mode == read_write ->
	    reply(From, {error, {'already opened in read only mode', L#log.name}}, S);
	L when L#log.mode == read_write, A#arg.mode == read_only ->
	    reply(From, {error, {'already opened in read write mode', L#log.name}}, S);
	L when A#arg.file == L#log.filename  ->
	    put(log, add_owner(A, L#log{mode = A#arg.mode})),
	    Links2 = maybe_do_link(A#arg.linkto, S#state.links),
	    reply(From, {ok, L#log.name}, S#state{links = Links2});
	L ->
	    reply(From, {error, {name_already_open, L#log.name}}, S)
    end;

handle({From, close}, S) ->
    case get(log) of
	L when L#log.status == ok, L#log.users == 1  ->
	    do_stop(S),
	    From ! {disk_log, self(), ok},
	    exit(normal);
	L when L#log.status == ok, L#log.users > 1  ->
	    Links2 = do_unlink(From, S#state.links),
	    NewOwners = lists:keydelete(From, 1, L#log.owners),
	    put(log, L#log{users = L#log.users - 1, owners = NewOwners}),
	    reply(From, ok, S#state{links = Links2});
	L when L#log.blocked_by == From, L#log.users == 1 ->
	    do_stop(S),
	    From ! {disk_log, self(), ok},
	    exit(normal);
	_ ->
	    loop(S#state{queue = [{From, close} | S#state.queue]})
    end;

handle({From, info}, S) ->
    #log{name = Name, type = Type, filename = File, size = Size,
	 status = Status, owners = Owners,  format = Format} = get(log),
    IsFull = case get(is_full) of undefined -> false; _ -> true end,
    reply(From, [{name, Name},
		 {items, S#state.cnt}, 
		 {size, Size},
		 {full, IsFull},
		 {status, Status},
		 {error_status, S#state.error_status},
		 {type, Type}, 
		 {file, File},
		 {owners, Owners},
		 {format, Format}], S);

handle({'EXIT', From, Reason}, S) when From == S#state.parent ->
    %% Parent orders shutdown
    do_stop(S),
    exit(Reason);
      
handle({'EXIT', From, Reason}, S) ->
    case get(log) of
	L when L#log.blocked_by == From ->
	    do_stop(S),
	    exit(normal);
	L when L#log.users == 1, L#log.owners == [{From, true}]  ->
	    do_stop(S),
	    exit(normal);
	L when L#log.users == 1, L#log.owners == [{From, false}]  ->
	    do_stop(S),
	    exit(normal);
	L ->
	    Links2 = do_unlink(From, S#state.links),
	    NewOwners = lists:keydelete(From, 1, L#log.owners),
	    put(log, L#log{users = L#log.users - 1, owners = NewOwners}),
	    loop(S#state{links = Links2});
	_ ->
	    loop(S)
    end;

handle({system, From, Req}, S) ->
    sys:handle_system_msg(Req, From, S#state.parent, ?MODULE, [], S);

handle(_, S) ->
    loop(S).

%%-----------------------------------------------------------------
%% Callback functions for system messages handling.
%%-----------------------------------------------------------------
system_continue(Parent, _, State) ->
    loop(State).

system_terminate(Reason, Parent, _, State) ->
    do_stop(State),
    exit(Reason).

%%-----------------------------------------------------------------
%% Temporay code for upgrade.
%%-----------------------------------------------------------------
system_code_change(State, _Module, OldVsn, Extra) ->
    {ok, State}.


%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
do_stop(#state{queue = Q, links = Links}) ->
    proc_q(Q),
    do_close(get(log), Links).

proc_q([{From, R}|Tail]) ->
    From ! {disk_log, self(), {error, disk_log_stopped}},
    proc_q(Tail);
proc_q([_|T]) -> %% async stuff 
    proc_q(T);
proc_q([]) ->
    ok.

maybe_do_link(P, Links) when pid(P) -> 
    case lists:keysearch(P, 1, Links) of
	{value, _} -> Links;
	_ -> incr_link(P, Links)
    end;
maybe_do_link(_, Links) -> 
    Links.

do_link(P, Links) when pid(P) -> 
    incr_link(P, Links);
do_link(_, Links) -> 
    Links.

incr_link(P, [{P, I} | Tail]) ->
    [{P, I+1} | Tail];
incr_link(P, [{P2, I} | Tail]) ->
    [{P2, I} | incr_link(P, Tail)];
incr_link(P, []) ->
    link(P),
    [{P, 1}].


do_unlink(P, Links) when pid(P) -> 
    incr_unlink(P, Links);
do_unlink(_, Links) -> 
    Links.

incr_unlink(P, [{P, 1} | Tail]) ->
    unlink(P),
    Tail;
incr_unlink(P, [{P, I} | Tail]) ->
    [{P, I-1} | Tail];
incr_unlink(P, [{P2, I} | Tail]) ->
    [{P2, I} | incr_unlink(P, Tail)];
incr_unlink(P, []) ->
    [].

add_owner(#arg{linkto = none}, L) ->
    L#log{users = 1 + L#log.users};
add_owner(#arg{linkto = Pid, notify = Notify}, L) ->
    case lists:keysearch(Pid, 1, L#log.owners) of
	{value, _} -> L;
	_ when Notify == true ->
	    L#log{owners = [{Pid, true}] ++ L#log.owners,
		  users = 1 + L#log.users};
	_ ->
	    L#log{owners = [{Pid, false}] ++ L#log.owners,
		  users = 1 + L#log.users}
    end.

send_self(L) ->
    lists:foreach(fun(M) -> self() ! M end, lists:reverse(L)).

%% Ret: {ok, Res, Links, Cnt} | {Other, Links, Cnt}
do_open(A, Links) ->
    L = #log{name = A#arg.name,
	     filename = A#arg.file,
	     size = A#arg.size,
	     head = mk_head(A#arg.head, A#arg.format),
	     mode = A#arg.mode,
	     owners = link_to(A)},
    case do_open2(L, A) of
	{ok, Reply, L2, Cnt} ->
	    Links2 = do_link(A#arg.linkto, Links),
	    put(log, L2),
	    if
		Cnt == 0 ->
		    case catch log_head(L#log.head, L2) of
			N when integer(N) ->
			    {ok, Reply, Links2, N};
			Else ->
			    {Else, Links2, 0}
		    end;
		true ->
		    {ok, Reply, Links2, Cnt}
	    end;
	Other ->
	    {Other, Links, 0}
    end.
	    
log_head(Head, L) ->
    case lh(Head, L#log.format) of
	{ok, Bin} -> do_log(L, Bin);
	_ -> 0
    end.

mk_head({head, Term}, internal) -> 
    {ok, term_to_binary(Term)};
mk_head({head, Bytes}, external) ->
    {ok, check_bytes(Bytes)};
mk_head(H, _) ->
    H.

lh({ok, Bin}, _) ->
    {ok, Bin};
lh({M, F, A}, Format) ->
    case catch apply(M, F, A) of
	{ok, Head} when Format == internal ->
	    {ok, term_to_binary(Head)};
	{ok, Head} ->
	    case catch check_bytes(Head) of
		{'EXIT', _} ->
		    error_logger:error_msg("disk_log: ~w:~w(~w) returned ~p~n",
					   [M,F,A,{ok, Head}]),
		    none;
		Bin ->
		    {ok, Bin}
	    end;
	Error ->
	    error_logger:error_msg("disk_log: ~w:~w(~w) returned ~p~n",
				   [M,F,A,Error]),
	    none
    end;
lh(_, _) ->
    none.

do_open2(L, #arg{type = halt, format = internal,
		 file = N, repair = Repair, size = Size, mode = Mode}) ->
    case disk_log_1:open(N, Repair, Mode) of
	{ok, Fd} ->
	    {ok, {ok, L#log.name}, L#log{type = halt_int, extra = {Fd, Size}},
	     0};
	{repaired, Fd, Rec, Bad} ->
	    {ok, {repaired, L#log.name, {recovered, Rec}, {badbytes, Bad}},
	     L#log{type = halt_int, extra = {Fd, Size}}, Rec};
	Other ->
	    Other
    end;
do_open2(L, #arg{type = wrap, format = internal,
		 size = {MaxB, MaxF}, repair = Repair, file = N, mode = Mode}) ->
    Dir = filename:dirname(N),
    case disk_log_1:mf_int_open(Dir, filename:basename(N), MaxB,MaxF, Repair, Mode) of
	{ok, Handle, Cnt} ->
	    {ok, {ok, L#log.name}, L#log{type = wrap_int, extra = Handle}, Cnt};
	Other ->
	    Other
    end;
do_open2(L, #arg{type = halt, format = external, file = N,
		 size = Size, repair = Repair, mode = Mode}) ->
    Type = 
	case Repair of
	    truncate -> [write];
	    _ -> [read, write]
	end,
    case file:open(N, [raw, binary] ++ Type) of
	{ok, Fd} ->
	    file:position(Fd, eof),
	    {ok, {ok, L#log.name}, L#log{type = halt_ext, extra = {Fd, Size},
					 format = external}, 0};
	Other ->
	    Other
    end;
do_open2(L, #arg{type = wrap, format = external, size = {MaxB, MaxF},
		 file = N, repair = Repair, mode = Mode}) ->
    Dir = filename:dirname(N),
    case disk_log_1:mf_ext_open(Dir, filename:basename(N), MaxB, MaxF, Repair) of
	{ok, Handle, Cnt} ->
	    {ok, {ok, L#log.name}, L#log{type = wrap_ext, extra = Handle,
					 format = external}, Cnt};
	Other ->
	    Other
    end.

link_to(#arg{linkto = Pid, notify = true}) when pid(Pid) -> [{Pid, true}];
link_to(#arg{linkto = Pid}) when pid(Pid) -> [{Pid, false}];
link_to(_) -> [].
    
do_close(undefined, Links) ->
    closed;
do_close(L, Links) ->
    L2 = do_unlink(L#log.blocked_by, Links),
    lists:foldl(fun({Pid, _}, Links2) -> do_unlink(Pid, Links2) end,
		L2, L#log.owners),
    do_close2(L).

do_close2(L) ->
    case L of
	#log{type = halt_int, extra = {Fd, _Size}} ->
	    disk_log_1:close(Fd);
	#log{type = wrap_int, extra = Handle} ->
	    disk_log_1:mf_int_close(Handle);
	#log{type = halt_ext, extra = {Fd, _Size}} ->
	    file:close(Fd);
	#log{type = wrap_ext, extra = Handle} ->
	    disk_log_1:mf_ext_close(Handle)
    end,
    erase(log),
    closed.

%% Ret: integer() | {error, Reason}
do_log(#log{type = halt_int, extra = {Fd, infinity}}, B) ->
    catch disk_log_1:log(Fd, B);
do_log(#log{type = halt_int, extra = {Fd, Sz}}, B) ->
    {ok, CurSize} = file:position(Fd, cur),
    BSize = sz(B),
    IsFull = get(is_full),
    if
	IsFull == true ->
	    {error, full};
	CurSize + BSize =< Sz ->
	    catch disk_log_1:log(Fd, B);
	true ->
	    put(is_full, true),
	    notify_owners(full),
	    {error, full}
    end;
do_log(L, B) when L#log.type == wrap_int ->
    Wf = {?MODULE, lh, [L#log.head, L#log.format]},
    case catch disk_log_1:mf_int_log(L#log.extra, B, Wf) of
	{ok, Handle, Logged, Lost} ->
	    notify_owners({wrap, Lost}),
	    put(log, L#log{extra = Handle}),
	    Logged - Lost;
	{ok, Handle, Logged} ->
	    put(log, L#log{extra = Handle}),
	    Logged;
	Error ->
	      Error
    end;
do_log(#log{type = halt_ext, extra = {Fd, Sz}}, B) ->
    {ok, CurSize} = file:position(Fd, cur),
    BSize = xsz(B),
    IsFull = get(is_full),
    if
	IsFull == true ->
	    {error, full};
	CurSize + BSize =< Sz ->
	    if
		binary(B) ->
		    case file:write(Fd, B) of
			ok -> 1;
			{error, Error} -> {error, {file_error, Error}}
		    end;
		true ->
		    lists:foldl(fun(Bin, N) ->
					case file:write(Fd, Bin) of
					    ok -> N + 1;
					    {error, Error} ->
					       throw({error,{file_error,Error}})
					end
				end, 0, B)
	    end;
	true ->
	    put(is_full, true),
	    notify_owners(full),
	    {error, full}
    end;
do_log(L, B) when L#log.type == wrap_ext ->
    Wf = {?MODULE, lh, [L#log.head, L#log.format]},
    case catch disk_log_1:mf_ext_log(L#log.extra, B, Wf) of
	{ok, Handle, Logged, Lost} ->
	    notify_owners({wrap, Lost}),
	    put(log, L#log{extra = Handle}),
	    Logged - Lost;
	{ok, Handle, Logged} ->
	    put(log, L#log{extra = Handle}),
	    Logged;
	Error ->
	    Error
    end.

sz(B) when binary(B) -> size(B) + ?HEADERSZ;
sz([B|T]) when binary(B) -> size(B) + ?HEADERSZ + sz(T);
sz([]) -> 0.
	
xsz(B) when binary(B) -> size(B);
xsz([B|T]) when binary(B) -> size(B) + xsz(T);
xsz([]) -> 0.
	
do_sync(#log{type = halt_int, extra = {Fd, _Size}}) ->
    disk_log_1:sync(Fd);
do_sync(#log{type = wrap_int, extra = Handle}) ->
    disk_log_1:mf_int_sync(Handle);
do_sync(#log{type = halt_ext, extra = {Fd, _Size}}) ->
    file:sync(Fd);
do_sync(#log{type = wrap_ext, extra = Handle}) ->
    disk_log_1:mf_int_sync(Handle).

do_trunc(#log{type = halt_int, extra = {Fd, _Size}}, Head) ->
    disk_log_1:truncate(Fd, Head);
do_trunc(#log{type = halt_ext, extra = {Fd, _Size}}, Head) ->
    file:position(Fd, bof),
    file:truncate(Fd),
    case Head of
	{head, H} ->
	    case file:write(Fd, H) of
		ok -> ok;
		{error, Error} -> {error, {file_error, Error}}
	    end;
	none -> ok
    end;
do_trunc(#log{type = wrap_int, extra = Handle}, Head) ->
    {error, wrap}.



do_chunk(#log{type = halt_int, mode = read_only, extra = {Fd, _Size}}, Pos, B, N) ->
    disk_log_1:chunk_read_only(Fd, Pos, B, N);
do_chunk(#log{type = halt_int, extra = {Fd, _Size}}, Pos, B, N) ->
    disk_log_1:chunk(Fd, Pos, B, N);
do_chunk(#log{type = wrap_int, mode = read_only, extra = Handle}, Pos, B, N) ->
    disk_log_1:mf_int_chunk_read_only(Handle, Pos, B, N);
do_chunk(#log{type = wrap_int, extra = Handle}, Pos, B, N) ->
    disk_log_1:mf_int_chunk(Handle, Pos, B, N);
do_chunk(_Log, _Pos, _B, _) ->
    {error, format_external}.

do_chunk_step(#log{type = wrap_int, extra = Handle}, Pos, B, N) ->
    disk_log_1:mf_int_chunk_step(Handle, Pos, B, N);
do_chunk_step(#log{type = halt_int, extra = {Fd, _Size}}, Pos, B, N) ->
    {error, type_halt};
do_chunk_step(_Log, _Pos, _B, _) ->
    {error, format_external}.


reply(To, Rep, S) ->
    To ! {disk_log, self(), Rep},
    loop(S).

req(Log, R) ->
    case get_log_pids(Log) of
	[Pid] ->
	    Pid ! {self(), R},
	    receive {disk_log, Pid, Reply} -> Reply end;
	undefined ->
	    {error, no_such_log};
	Pids ->
	    multi_req(Log, {self(), R}, Pids)
    end.

sreq(Log, R) ->
    case get_log_pids(Log) of
	undefined ->
	    {error, {no_such_log, Log}};
	Pids ->
	    (Pid = get_near_pid(Pids, node())) ! {self(), R},
	    receive {disk_log, Pid, Reply} -> Reply end
    end.

multi_req(Log, Msg, Pids) ->
    lists:foreach(fun(Pid) ->
			  erlang:monitor_node(node(Pid), true), Pid ! Msg
		  end, Pids),
    lists:foldl(fun(Pid, Reply) ->
			receive
			    {disk_log, Pid, _Reply} ->
				erlang:monitor_node(node(Pid), false),
				ok;
			    {nodedown, Node} when Node == node(Pid) ->
				Reply
			end
		end, {error, nonode}, Pids).

req2(Pid, R) ->
    Pid ! {self(), R},
    receive {disk_log, Pid, Reply} -> Reply end.

get_near_pid([Pid | _], Node) when node(Pid) == Node -> Pid;
get_near_pid([Pid], _ ) -> Pid;
get_near_pid([_ | T], Node) -> get_near_pid(T, Node).

get_log_pids(Log) ->
    case ets:lookup(disk_log_names, Log) of
	[{_, Pid}] ->
	    [Pid];
	[] -> 
	    case catch pg2:get_members(Log) of
		[] -> undefined;
		Pids when list(Pids) -> Pids;
		_Error -> undefined
	    end
    end.

merge_head(none, Head) ->
    Head;
merge_head(Head, _) ->
    Head.

notify(Log, R) ->
    case get_log_pids(Log) of
	undefined ->
	    {error, {no_such_log, Log}};
	Pids ->
	    lists:foreach(fun(Pid) -> Pid ! R end, Pids),
	    ok
    end.

notify_owners(Note) ->
    L = get(log),
    Msg = {disk_log, node(), L#log.name, Note},
    lists:foreach(fun({Pid, true}) -> Pid ! Msg;
		     (_) -> ok
		  end, L#log.owners).

state_ok(S) when S#state.error_status == ok -> S;
state_ok(S) ->
    notify_owners({error_status, ok}),
    S#state{error_status = ok}.

%% Note: Err = ok | {error, Reason}
state_err(S, Err) when S#state.error_status == Err -> S;
state_err(S, Err) ->
    notify_owners({error_status, Err}),
    S#state{error_status = Err}.
