%%
%% %CopyrightBegin%
%% 
  Copyright Ericsson AB 2005 - 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 /.
%% 
  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%
%%
%%

%%%-------------------------------------------------------------------
%%% File    : tft_file.erl
  Author   :   <  >
%%% Description : 
%%%
  Created : 24 May 2004 by   <  >
%%%-------------------------------------------------------------------

-module(tftp_file).

%%%-------------------------------------------------------------------
  Interface
%%%-------------------------------------------------------------------

-behaviour(tftp).

-export([prepare/6, open/6, read/1, write/2, abort/3]).

%%%-------------------------------------------------------------------
%%% Defines
%%%-------------------------------------------------------------------

-include_lib("kernel/include/file.hrl").

-record(initial,
	{filename,
	 is_native_ascii}).

-record(state,
	{access,
	 filename,
	 is_native_ascii,
	 is_network_ascii,
	 root_dir,
	 options,
	 blksize,
	 fd,
	 count,
	 buffer}).

%%-------------------------------------------------------------------
  prepare(Peer , Access , Filename , Mode , SuggestedOptions , InitialState ) - >
     { ok , AcceptedOptions , NewState } | { error , Code , Text }
%%
  Peer              = { PeerType , PeerHost , PeerPort }
  PeerType          = inet | 
  PeerHost          = ip_address ( )
%% PeerPort         = integer()
%% Acess            = read | write
%% Filename         = string()
%% Mode             = string()
  SuggestedOptions = [ { Key , Value } ]
  AcceptedOptions   = [ { Key , Value } ]
%% Key              = string()
%% Value            = string()
  InitialState      = [ ] | [ { root_dir , string ( ) } ]
  NewState          = term ( )
%% Code             = undef | enoent | eacces | enospc |
                     badop | eexist | baduser | badopt |
%%                    integer()
%% Text             = string()
%%
%% Prepares open of a file on the client side.
%% 
%% Will be followed by a call to open/4 before any read/write access
  is performed . The AcceptedOptions will be sent to the server which
%% will reply with those options that it accepts. The options that are
  accepted by the server will be forwarded to open/4 as SuggestedOptions .
%%
%% No new options may be added, but the ones that are present as
  SuggestedOptions may be omitted or replaced with new values
  in the AcceptedOptions .
%%-------------------------------------------------------------------

prepare(_Peer, Access, Filename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
    %% Client side
    case catch handle_options(Access, Filename, Mode, SuggestedOptions, Initial) of
	{ok, Filename2, IsNativeAscii, IsNetworkAscii, AcceptedOptions} ->
	    State = #state{access           = Access,
			   filename         = Filename2,
			   is_native_ascii  = IsNativeAscii,
			   is_network_ascii = IsNetworkAscii,
			   options  	    = AcceptedOptions,
			   blksize  	    = lookup_blksize(AcceptedOptions),
			   count    	    = 0,
			   buffer   	   =  []},
	    {ok, AcceptedOptions, State};
	{error, {Code, Text}} ->
	    {error, {Code, Text}}
    end.

%% ---------------------------------------------------------
  open(Peer , Access , Filename , Mode , SuggestedOptions , State ) - >
     { ok , AcceptedOptions , NewState } | { error , Code , Text }
%%
  Peer              = { PeerType , PeerHost , PeerPort }
  PeerType          = inet | 
  PeerHost          = ip_address ( )
%% PeerPort         = integer()
%% Acess            = read | write
%% Filename         = string()
%% Mode             = string()
  SuggestedOptions = [ { Key , Value } ]
  AcceptedOptions   = [ { Key , Value } ]
%% Key              = string()
%% Value            = string()
%% State            = InitialState | #state{}
  InitialState      = [ ] | [ { root_dir , string ( ) } ]
  NewState          = term ( )
%% Code             = undef | enoent | eacces  | enospc |
                     badop | eexist | baduser | badopt |
%%                    integer()
%% Text             = string()
%%
  Opens a file for read or write access .
%% 
%% On the client side where the open/4 call has been preceeded by a
%% call to prepare/4, all options must be accepted or rejected.
%% On the server side, where there are no preceeding prepare/4 call,
%% noo new options may be added, but the ones that are present as
  SuggestedOptions may be omitted or replaced with new values
  in the AcceptedOptions .
%%-------------------------------------------------------------------

open(Peer, Access, Filename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
    %% Server side
    case prepare(Peer, Access, Filename, Mode, SuggestedOptions, Initial) of
	{ok, AcceptedOptions, State} ->
	    open(Peer, Access, Filename, Mode, AcceptedOptions, State);
	{error, {Code, Text}} ->
	    {error, {Code, Text}}
    end;
open(_Peer, Access, Filename, Mode, NegotiatedOptions, State) when is_record(State, state) ->
    %% Both sides
    case catch handle_options(Access, Filename, Mode, NegotiatedOptions, State) of
	{ok, _Filename2, _IsNativeAscii, _IsNetworkAscii, Options} 
	   when Options =:= NegotiatedOptions ->
	    do_open(State);
	{error, {Code, Text}} ->
	    {error, {Code, Text}}
    end;
open(Peer, Access, Filename, Mode, NegotiatedOptions, State) ->
    %% Handle upgrade from old releases. Please, remove this clause in next release.
    State2 = upgrade_state(State),
    open(Peer, Access, Filename, Mode, NegotiatedOptions, State2).

do_open(State) when is_record(State, state) ->
    case file:open(State#state.filename, file_options(State)) of
	{ok, Fd} ->
	    {ok, State#state.options, State#state{fd = Fd}};
	{error, Reason} when is_atom(Reason) ->
	    {error, file_error(Reason)}
    end.
	
file_options(State) ->
    case State#state.access of
	read  -> [read, read_ahead, raw, binary];
	write -> [write, delayed_write, raw, binary]
    end.

file_error(Reason) when is_atom(Reason) ->
    Details = file:format_error(Reason),
    case Reason of
	eexist -> {Reason, Details};
	enoent -> {Reason, Details};
	eacces -> {Reason, Details};
	eperm  -> {eacces, Details};
	enospc -> {Reason, Details};
	_      -> {undef,  Details ++ " (" ++ atom_to_list(Reason) ++ ")"}
    end.

%%-------------------------------------------------------------------
%% read(State) ->
    { more ,  , NewState } | { last ,  , FileSize } | { error , { Code , Text } }
%%
%% State    = term()
  NewState = term ( )
    = binary ( )
%% FileSize = integer()
%% Code     = undef | enoent | eacces  | enospc |
             badop | eexist | baduser | badopt |
%%            integer()
%% Text     = string()
%%
%% Reads a chunk from the file
%% 
%% The file is automatically closed when the last chunk is read.
%%-------------------------------------------------------------------

read(#state{access = read} = State) ->
    BlkSize = State#state.blksize,
    case file:read(State#state.fd, BlkSize) of
	{ok, Bin} when is_binary(Bin), size(Bin) =:= BlkSize ->
	    Count = State#state.count + size(Bin),
	    {more, Bin, State#state{count = Count}};
	{ok, Bin} when is_binary(Bin), size(Bin) < BlkSize ->
	    file:close(State#state.fd),
	    Count = State#state.count + size(Bin),
	    {last, Bin, Count};
	eof ->
	    {last, <<>>, State#state.count};
	{error, Reason} ->
	    file:close(State#state.fd),
	    {error, file_error(Reason)}
    end;
read(State) ->
    %% Handle upgrade from old releases. Please, remove this clause in next release.
    State2 = upgrade_state(State),
    read(State2).

%%-------------------------------------------------------------------
  write(Bin , State ) - >
%%   {more, NewState} | {last, FileSize} | {error, {Code, Text}}
%%
%% State    = term()
  NewState = term ( )
    = binary ( )
%% FileSize = integer()
%% Code     = undef | enoent | eacces  | enospc |
             badop | eexist | baduser | badopt |
%%            integer()
%% Text     = string()
%%
%% Writes a chunk to the file
%%
%% The file is automatically closed when the last chunk is written
%%-------------------------------------------------------------------

write(Bin, #state{access = write} = State) when is_binary(Bin) ->
    Size = size(Bin),
    BlkSize = State#state.blksize,
    case file:write(State#state.fd, Bin) of
	ok when Size =:= BlkSize->
	    Count = State#state.count + Size,
	    {more, State#state{count = Count}};
	ok when Size < BlkSize->
	    file:close(State#state.fd),
	    Count = State#state.count + Size,
	    {last, Count};
	{error, Reason}  ->
	    file:close(State#state.fd),
	    file:delete(State#state.filename),
	    {error, file_error(Reason)}
    end;
write(Bin, State) ->
    %% Handle upgrade from old releases. Please, remove this clause in next release.
    State2 = upgrade_state(State),
    write(Bin, State2).

%%-------------------------------------------------------------------
%% abort(Code, Text, State) -> ok
%% 
%% State    = term()
%% Code     = undef  | enoent | eacces  | enospc |
                | eexist | baduser | badopt |
%%            badblk | integer()
%% Text     = string()
%%
%% Aborts the file transfer
%%-------------------------------------------------------------------

abort(_Code, _Text, #state{fd = Fd, access = Access} = State) ->
    file:close(Fd),
    case Access of
	write ->
	    ok = file:delete(State#state.filename);
	read ->
	    ok
    end.

%%-------------------------------------------------------------------
%% Process options
%%-------------------------------------------------------------------

handle_options(Access, Filename, Mode, Options, Initial) ->
    I = #initial{filename = Filename, is_native_ascii = is_native_ascii()},
    {Filename2, IsNativeAscii} = handle_initial(Initial, I),
    IsNetworkAscii = handle_mode(Mode, IsNativeAscii),
    Options2 = do_handle_options(Access, Filename2, Options),
    {ok, Filename2, IsNativeAscii, IsNetworkAscii, Options2}.

handle_mode(Mode, IsNativeAscii) ->
    case Mode of
	"netascii" when IsNativeAscii =:= true -> true;
	"octet" -> false;
	_ -> throw({error, {badop, "Illegal mode " ++ Mode}})
    end.

handle_initial([{root_dir, Dir} | Initial], I) ->
    case catch filename_join(Dir, I#initial.filename) of
	{'EXIT', _} ->
	    throw({error, {badop, "Internal error. root_dir is not a string"}});
	Filename2 ->
	    handle_initial(Initial, I#initial{filename = Filename2})
    end;
handle_initial([{native_ascii, Bool} | Initial], I) ->
    case Bool of
	true  -> handle_initial(Initial, I#initial{is_native_ascii = true});
	false -> handle_initial(Initial, I#initial{is_native_ascii = false})
    end;
handle_initial([], I) when is_record(I, initial) ->
    {I#initial.filename, I#initial.is_native_ascii};
handle_initial(State, _) when is_record(State, state) ->
    {State#state.filename, State#state.is_native_ascii}.

filename_join(Dir, Filename) ->
    case filename:pathtype(Filename) of
	absolute ->
	    [_ | RelFilename] = filename:split(Filename),
	    filename:join([Dir, RelFilename]);
	_ ->
	    filename:join([Dir, Filename])
    end.

do_handle_options(Access, Filename, [{Key, Val} | T]) ->
    case Key of
	"tsize" ->
	    case Access of
		read when Val =:= "0" ->
		    case file:read_file_info(Filename) of
			{ok, FI} ->
			    Tsize = integer_to_list(FI#file_info.size),
			    [{Key, Tsize} | do_handle_options(Access, Filename, T)];
			{error, _} ->
			    do_handle_options(Access, Filename, T)
		    end;
		_ ->
		    handle_integer(Access, Filename, Key, Val, T, 0, infinity)
	    end;
	"blksize" ->
	    handle_integer(Access, Filename, Key, Val, T, 8, 65464);
	"timeout" ->
	    handle_integer(Access, Filename, Key, Val, T, 1, 255);
	_ ->
	    do_handle_options(Access, Filename, T)
    end;
do_handle_options(_Access, _Filename, []) ->
    [].


handle_integer(Access, Filename, Key, Val, Options, Min, Max) ->
    case catch list_to_integer(Val) of
	{'EXIT', _} ->
	    do_handle_options(Access, Filename, Options);
	Int when Int >= Min, Int =< Max ->
	    [{Key, Val} | do_handle_options(Access, Filename, Options)];
	Int when Int >= Min, Max =:= infinity ->
	    [{Key, Val} | do_handle_options(Access, Filename, Options)];
	_Int ->
	    throw({error, {badopt, "Illegal " ++ Key ++ " value " ++ Val}})
    end.

lookup_blksize(Options) ->
    case lists:keysearch("blksize", 1, Options) of
	{value, {_, Val}} ->
	    list_to_integer(Val);
	false ->
	    512
    end.

is_native_ascii() ->
    case os:type() of
	{win32, _} -> true;
	_          -> false
    end.

%% Handle upgrade from old releases. Please, remove this function in next release.
upgrade_state({state, Access, Filename, RootDir, Options, BlkSize, Fd, Count, Buffer}) ->
    {state, Access, Filename, false, false, RootDir, Options, BlkSize, Fd, Count, Buffer}.


 %CopyrightBegin%
 
 
 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 /.
 
 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%


-------------------------------------------------------------------
 File    : tft_file.erl
 Description : 

-------------------------------------------------------------------
-------------------------------------------------------------------
-------------------------------------------------------------------
-------------------------------------------------------------------
 Defines
-------------------------------------------------------------------
-------------------------------------------------------------------

 PeerPort         = integer()
 Acess            = read | write
 Filename         = string()
 Mode             = string()
 Key              = string()
 Value            = string()
 Code             = undef | enoent | eacces | enospc |
                    integer()
 Text             = string()

 Prepares open of a file on the client side.
 
 Will be followed by a call to open/4 before any read/write access
 will reply with those options that it accepts. The options that are

 No new options may be added, but the ones that are present as
-------------------------------------------------------------------
 Client side
 ---------------------------------------------------------

 PeerPort         = integer()
 Acess            = read | write
 Filename         = string()
 Mode             = string()
 Key              = string()
 Value            = string()
 State            = InitialState | #state{}
 Code             = undef | enoent | eacces  | enospc |
                    integer()
 Text             = string()

 
 On the client side where the open/4 call has been preceeded by a
 call to prepare/4, all options must be accepted or rejected.
 On the server side, where there are no preceeding prepare/4 call,
 noo new options may be added, but the ones that are present as
-------------------------------------------------------------------
 Server side
 Both sides
 Handle upgrade from old releases. Please, remove this clause in next release.
-------------------------------------------------------------------
 read(State) ->

 State    = term()
 FileSize = integer()
 Code     = undef | enoent | eacces  | enospc |
            integer()
 Text     = string()

 Reads a chunk from the file
 
 The file is automatically closed when the last chunk is read.
-------------------------------------------------------------------
 Handle upgrade from old releases. Please, remove this clause in next release.
-------------------------------------------------------------------
   {more, NewState} | {last, FileSize} | {error, {Code, Text}}

 State    = term()
 FileSize = integer()
 Code     = undef | enoent | eacces  | enospc |
            integer()
 Text     = string()

 Writes a chunk to the file

 The file is automatically closed when the last chunk is written
-------------------------------------------------------------------
 Handle upgrade from old releases. Please, remove this clause in next release.
-------------------------------------------------------------------
 abort(Code, Text, State) -> ok
 
 State    = term()
 Code     = undef  | enoent | eacces  | enospc |
            badblk | integer()
 Text     = string()

 Aborts the file transfer
-------------------------------------------------------------------
-------------------------------------------------------------------
 Process options
-------------------------------------------------------------------
 Handle upgrade from old releases. Please, remove this function in next release.
  Copyright Ericsson AB 2005 - 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
  Software distributed under the License is distributed on an " AS IS "

  Author   :   <  >
  Created : 24 May 2004 by   <  >

-module(tftp_file).

  Interface

-behaviour(tftp).

-export([prepare/6, open/6, read/1, write/2, abort/3]).


-include_lib("kernel/include/file.hrl").

-record(initial,
	{filename,
	 is_native_ascii}).

-record(state,
	{access,
	 filename,
	 is_native_ascii,
	 is_network_ascii,
	 root_dir,
	 options,
	 blksize,
	 fd,
	 count,
	 buffer}).

  prepare(Peer , Access , Filename , Mode , SuggestedOptions , InitialState ) - >
     { ok , AcceptedOptions , NewState } | { error , Code , Text }
  Peer              = { PeerType , PeerHost , PeerPort }
  PeerType          = inet | 
  PeerHost          = ip_address ( )
  SuggestedOptions = [ { Key , Value } ]
  AcceptedOptions   = [ { Key , Value } ]
  InitialState      = [ ] | [ { root_dir , string ( ) } ]
  NewState          = term ( )
                     badop | eexist | baduser | badopt |
  is performed . The AcceptedOptions will be sent to the server which
  accepted by the server will be forwarded to open/4 as SuggestedOptions .
  SuggestedOptions may be omitted or replaced with new values
  in the AcceptedOptions .

prepare(_Peer, Access, Filename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
    case catch handle_options(Access, Filename, Mode, SuggestedOptions, Initial) of
	{ok, Filename2, IsNativeAscii, IsNetworkAscii, AcceptedOptions} ->
	    State = #state{access           = Access,
			   filename         = Filename2,
			   is_native_ascii  = IsNativeAscii,
			   is_network_ascii = IsNetworkAscii,
			   options  	    = AcceptedOptions,
			   blksize  	    = lookup_blksize(AcceptedOptions),
			   count    	    = 0,
			   buffer   	   =  []},
	    {ok, AcceptedOptions, State};
	{error, {Code, Text}} ->
	    {error, {Code, Text}}
    end.

  open(Peer , Access , Filename , Mode , SuggestedOptions , State ) - >
     { ok , AcceptedOptions , NewState } | { error , Code , Text }
  Peer              = { PeerType , PeerHost , PeerPort }
  PeerType          = inet | 
  PeerHost          = ip_address ( )
  SuggestedOptions = [ { Key , Value } ]
  AcceptedOptions   = [ { Key , Value } ]
  InitialState      = [ ] | [ { root_dir , string ( ) } ]
  NewState          = term ( )
                     badop | eexist | baduser | badopt |
  Opens a file for read or write access .
  SuggestedOptions may be omitted or replaced with new values
  in the AcceptedOptions .

open(Peer, Access, Filename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
    case prepare(Peer, Access, Filename, Mode, SuggestedOptions, Initial) of
	{ok, AcceptedOptions, State} ->
	    open(Peer, Access, Filename, Mode, AcceptedOptions, State);
	{error, {Code, Text}} ->
	    {error, {Code, Text}}
    end;
open(_Peer, Access, Filename, Mode, NegotiatedOptions, State) when is_record(State, state) ->
    case catch handle_options(Access, Filename, Mode, NegotiatedOptions, State) of
	{ok, _Filename2, _IsNativeAscii, _IsNetworkAscii, Options} 
	   when Options =:= NegotiatedOptions ->
	    do_open(State);
	{error, {Code, Text}} ->
	    {error, {Code, Text}}
    end;
open(Peer, Access, Filename, Mode, NegotiatedOptions, State) ->
    State2 = upgrade_state(State),
    open(Peer, Access, Filename, Mode, NegotiatedOptions, State2).

do_open(State) when is_record(State, state) ->
    case file:open(State#state.filename, file_options(State)) of
	{ok, Fd} ->
	    {ok, State#state.options, State#state{fd = Fd}};
	{error, Reason} when is_atom(Reason) ->
	    {error, file_error(Reason)}
    end.
	
file_options(State) ->
    case State#state.access of
	read  -> [read, read_ahead, raw, binary];
	write -> [write, delayed_write, raw, binary]
    end.

file_error(Reason) when is_atom(Reason) ->
    Details = file:format_error(Reason),
    case Reason of
	eexist -> {Reason, Details};
	enoent -> {Reason, Details};
	eacces -> {Reason, Details};
	eperm  -> {eacces, Details};
	enospc -> {Reason, Details};
	_      -> {undef,  Details ++ " (" ++ atom_to_list(Reason) ++ ")"}
    end.

    { more ,  , NewState } | { last ,  , FileSize } | { error , { Code , Text } }
  NewState = term ( )
    = binary ( )
             badop | eexist | baduser | badopt |

read(#state{access = read} = State) ->
    BlkSize = State#state.blksize,
    case file:read(State#state.fd, BlkSize) of
	{ok, Bin} when is_binary(Bin), size(Bin) =:= BlkSize ->
	    Count = State#state.count + size(Bin),
	    {more, Bin, State#state{count = Count}};
	{ok, Bin} when is_binary(Bin), size(Bin) < BlkSize ->
	    file:close(State#state.fd),
	    Count = State#state.count + size(Bin),
	    {last, Bin, Count};
	eof ->
	    {last, <<>>, State#state.count};
	{error, Reason} ->
	    file:close(State#state.fd),
	    {error, file_error(Reason)}
    end;
read(State) ->
    State2 = upgrade_state(State),
    read(State2).

  write(Bin , State ) - >
  NewState = term ( )
    = binary ( )
             badop | eexist | baduser | badopt |

write(Bin, #state{access = write} = State) when is_binary(Bin) ->
    Size = size(Bin),
    BlkSize = State#state.blksize,
    case file:write(State#state.fd, Bin) of
	ok when Size =:= BlkSize->
	    Count = State#state.count + Size,
	    {more, State#state{count = Count}};
	ok when Size < BlkSize->
	    file:close(State#state.fd),
	    Count = State#state.count + Size,
	    {last, Count};
	{error, Reason}  ->
	    file:close(State#state.fd),
	    file:delete(State#state.filename),
	    {error, file_error(Reason)}
    end;
write(Bin, State) ->
    State2 = upgrade_state(State),
    write(Bin, State2).

                | eexist | baduser | badopt |

abort(_Code, _Text, #state{fd = Fd, access = Access} = State) ->
    file:close(Fd),
    case Access of
	write ->
	    ok = file:delete(State#state.filename);
	read ->
	    ok
    end.


handle_options(Access, Filename, Mode, Options, Initial) ->
    I = #initial{filename = Filename, is_native_ascii = is_native_ascii()},
    {Filename2, IsNativeAscii} = handle_initial(Initial, I),
    IsNetworkAscii = handle_mode(Mode, IsNativeAscii),
    Options2 = do_handle_options(Access, Filename2, Options),
    {ok, Filename2, IsNativeAscii, IsNetworkAscii, Options2}.

handle_mode(Mode, IsNativeAscii) ->
    case Mode of
	"netascii" when IsNativeAscii =:= true -> true;
	"octet" -> false;
	_ -> throw({error, {badop, "Illegal mode " ++ Mode}})
    end.

handle_initial([{root_dir, Dir} | Initial], I) ->
    case catch filename_join(Dir, I#initial.filename) of
	{'EXIT', _} ->
	    throw({error, {badop, "Internal error. root_dir is not a string"}});
	Filename2 ->
	    handle_initial(Initial, I#initial{filename = Filename2})
    end;
handle_initial([{native_ascii, Bool} | Initial], I) ->
    case Bool of
	true  -> handle_initial(Initial, I#initial{is_native_ascii = true});
	false -> handle_initial(Initial, I#initial{is_native_ascii = false})
    end;
handle_initial([], I) when is_record(I, initial) ->
    {I#initial.filename, I#initial.is_native_ascii};
handle_initial(State, _) when is_record(State, state) ->
    {State#state.filename, State#state.is_native_ascii}.

filename_join(Dir, Filename) ->
    case filename:pathtype(Filename) of
	absolute ->
	    [_ | RelFilename] = filename:split(Filename),
	    filename:join([Dir, RelFilename]);
	_ ->
	    filename:join([Dir, Filename])
    end.

do_handle_options(Access, Filename, [{Key, Val} | T]) ->
    case Key of
	"tsize" ->
	    case Access of
		read when Val =:= "0" ->
		    case file:read_file_info(Filename) of
			{ok, FI} ->
			    Tsize = integer_to_list(FI#file_info.size),
			    [{Key, Tsize} | do_handle_options(Access, Filename, T)];
			{error, _} ->
			    do_handle_options(Access, Filename, T)
		    end;
		_ ->
		    handle_integer(Access, Filename, Key, Val, T, 0, infinity)
	    end;
	"blksize" ->
	    handle_integer(Access, Filename, Key, Val, T, 8, 65464);
	"timeout" ->
	    handle_integer(Access, Filename, Key, Val, T, 1, 255);
	_ ->
	    do_handle_options(Access, Filename, T)
    end;
do_handle_options(_Access, _Filename, []) ->
    [].


handle_integer(Access, Filename, Key, Val, Options, Min, Max) ->
    case catch list_to_integer(Val) of
	{'EXIT', _} ->
	    do_handle_options(Access, Filename, Options);
	Int when Int >= Min, Int =< Max ->
	    [{Key, Val} | do_handle_options(Access, Filename, Options)];
	Int when Int >= Min, Max =:= infinity ->
	    [{Key, Val} | do_handle_options(Access, Filename, Options)];
	_Int ->
	    throw({error, {badopt, "Illegal " ++ Key ++ " value " ++ Val}})
    end.

lookup_blksize(Options) ->
    case lists:keysearch("blksize", 1, Options) of
	{value, {_, Val}} ->
	    list_to_integer(Val);
	false ->
	    512
    end.

is_native_ascii() ->
    case os:type() of
	{win32, _} -> true;
	_          -> false
    end.

upgrade_state({state, Access, Filename, RootDir, Options, BlkSize, Fd, Count, Buffer}) ->
    {state, Access, Filename, false, false, RootDir, Options, BlkSize, Fd, Count, Buffer}.
