<commit_before>%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2013. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%% A primary loader, provides two different methods to fetch a file:
%% efile and inet. The efile method is simple communication with a
%% port program.
%%
%% The distribution loading was removed and replaced with
%% inet loading
%%
%% The start_it/4 function initializes a record with callback 
%% functions used to handle the interface functions.
%%

-module(erl_prim_loader).

%% If the macro DEBUG is defined during compilation, 
%% debug printouts are done through erlang:display/1.
%% Activate this feature by starting the compiler 
%% with> erlc -DDEBUG ... 
%% or by> setenv ERL_COMPILER_FLAGS DEBUG 
%% before running make (in the OTP make system)
%% (the example is for tcsh)

-include("inet_boot.hrl").

%% Public
-export([start/3, set_path/1, get_path/0, get_file/1, get_files/2,
         list_dir/1, read_file_info/1, read_link_info/1, get_cwd/0, get_cwd/1]).

%% Used by erl_boot_server
-export([prim_init/0, prim_get_file/2, prim_list_dir/2,
         prim_read_file_info/3, prim_get_cwd/2]).

%% Used by escript and code
-export([set_primary_archive/4, release_archives/0]).

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

-type host() :: atom().

-record(prim_state, {debug :: boolean(),
		     cache,
		     primary_archive}).
-type prim_state() :: #prim_state{}.

-record(state, 
        {loader            :: 'efile' | 'inet',
         hosts = []        :: [host()], % hosts list (to boot from)
         id,                      % not used any more?
         data              :: 'noport' | port(), % data port etc
         timeout           :: timeout(),         % idle timeout
	 %% Number of timeouts before archives are released
	 n_timeouts        :: non_neg_integer(),
         multi_get = false :: boolean(),
         prim_state        :: prim_state()}).    % state for efile code loader

-define(IDLE_TIMEOUT, 60000).  %% tear inet connection after 1 minutes
-define(N_TIMEOUTS, 6).        %% release efile archive after 6 minutes

%% Defines for inet as prim_loader
-define(INET_FAMILY, inet).
-define(INET_ADDRESS, {0,0,0,0}).

-ifdef(DEBUG).
-define(dbg(Tag, Data), erlang:display({Tag,Data})).
-else.
-define(dbg(Tag, Data), true).
-endif.

-define(SAFE2(Expr, State), 
        fun() ->
                case catch Expr of
                    {'EXIT',XXXReason} -> {{error,XXXReason}, State};
                    XXXRes -> XXXRes
                end
        end()).

debug(#prim_state{debug = Deb}, Term) ->
    case Deb of
        false -> ok;
        true  -> erlang:display(Term)
    end.

%%% --------------------------------------------------------
%%% Interface Functions. 
%%% --------------------------------------------------------

-spec start(Id, Loader, Hosts) ->
	    {'ok', Pid} | {'error', What} when
      Id :: term(),
      Loader :: atom() | string(),
      Hosts :: Host | [Host],
      Host :: host(),
      Pid :: pid(),
      What :: term().
start(Id, Pgm, Hosts) when is_atom(Hosts) ->
    start(Id, Pgm, [Hosts]);
start(Id, Pgm0, Hosts) ->
    Pgm = if
              is_atom(Pgm0) ->
                  atom_to_list(Pgm0);
              true ->
                  Pgm0
          end,
    Self = self(),
    Pid = spawn_link(fun() -> start_it(Pgm, Id, Self, Hosts) end),
    register(erl_prim_loader, Pid),
    receive
        {Pid,ok} ->
            {ok,Pid};
        {'EXIT',Pid,Reason} ->
            {error,Reason}
    end.

%% Hosts must be a list of form ['1.2.3.4' ...]
start_it("inet", Id, Pid, Hosts) ->
    process_flag(trap_exit, true),
    ?dbg(inet, {Id,Pid,Hosts}),
    AL = ipv4_list(Hosts),
    ?dbg(addresses, AL),
    {ok,Tcp} = find_master(AL),
    init_ack(Pid),
    PS = prim_init(),
    State = #state {loader = inet,
                    hosts = AL,
                    id = Id,
                    data = Tcp,
                    timeout = ?IDLE_TIMEOUT,
		    n_timeouts = ?N_TIMEOUTS,
                    prim_state = PS},
    loop(State, Pid, []);

start_it("efile", Id, Pid, _Hosts) ->
    process_flag(trap_exit, true),
    {ok, Port} = prim_file:start(),
    %% Check that we started in a valid directory.
    case prim_file:get_cwd(Port) of
	{error, _} ->
	    %% At this point in the startup, we have no error_logger at all.
	    Report = "Invalid current directory or invalid filename "
		"mode: loader cannot read current directory\n",
	    erlang:display(Report),
	    exit({error, invalid_current_directory});
	_ ->
	    init_ack(Pid)
    end,
    MultiGet = case erlang:system_info(thread_pool_size) of
                   0 -> false;
                   _ -> true
               end,
    PS = prim_init(),
    State = #state {loader = efile,
                    id = Id,
                    data = Port,
                    timeout = infinity,
                    multi_get = MultiGet,
                    prim_state = PS},
    loop(State, Pid, []).

init_ack(Pid) ->
    Pid ! {self(),ok},
    ok.

-spec set_path(Path) -> 'ok' when
      Path :: [Dir :: string()].
set_path(Paths) when is_list(Paths) ->
    request({set_path,Paths}).

-spec get_path() -> {'ok', Path} when
      Path :: [Dir :: string()].
get_path() ->
    request({get_path,[]}).

-spec get_file(Filename) -> {'ok', Bin, FullName} | 'error' when
      Filename :: atom() | string(),
      Bin :: binary(),
      FullName :: string().
get_file(File) when is_atom(File) ->
    get_file(atom_to_list(File));
get_file(File) ->
    check_file_result(get_file, File, request({get_file,File})).

-spec get_files([{atom(), string()}],
		fun((atom(),binary(),string()) -> 'ok' | {'error', atom()})) ->
			'ok' | {'error', atom()}.
get_files(ModFiles, Fun) ->
    case request({get_files,{ModFiles,Fun}}) of
        E = {error,_M} ->
            E;
        {error,Reason,M} ->
            check_file_result(get_files, M, {error,Reason}),
            {error,M};
        ok ->
            ok
    end.

-spec list_dir(Dir) -> {'ok', Filenames} | 'error' when
      Dir :: string(),
      Filenames :: [Filename :: string()].
list_dir(Dir) ->
    check_file_result(list_dir, Dir, request({list_dir,Dir})).

-spec read_file_info(Filename) -> {'ok', FileInfo} | 'error' when
      Filename :: string(),
      FileInfo :: file:file_info().
read_file_info(File) ->
    check_file_result(read_file_info, File, request({read_file_info,File})).

-spec read_link_info(Filename) -> {'ok', FileInfo} | 'error' when
      Filename :: string(),
      FileInfo :: file:file_info().
read_link_info(File) ->
    check_file_result(read_link_info, File, request({read_link_info,File})).

-spec get_cwd() -> {'ok', string()} | 'error'.
get_cwd() ->
    check_file_result(get_cwd, [], request({get_cwd,[]})).

-spec get_cwd(string()) -> {'ok', string()} | 'error'.
get_cwd(Drive) ->
    check_file_result(get_cwd, Drive, request({get_cwd,[Drive]})).

-spec set_primary_archive(File :: string() | 'undefined', 
			  ArchiveBin :: binary() | 'undefined',
			  FileInfo :: #file_info{} | 'undefined',
			  ParserFun :: fun())
			 -> {ok, [string()]} | {error,_}.

set_primary_archive(undefined, undefined, undefined, ParserFun) ->
    request({set_primary_archive, undefined, undefined, undefined, ParserFun});
set_primary_archive(File, ArchiveBin, FileInfo, ParserFun)
  when is_list(File), is_binary(ArchiveBin), is_record(FileInfo, file_info) ->
    request({set_primary_archive, File, ArchiveBin, FileInfo, ParserFun}).

-spec release_archives() -> 'ok' | {'error', _}.

release_archives() ->
    request(release_archives).

request(Req) ->
    Loader = whereis(erl_prim_loader),
    Loader ! {self(),Req},
    receive
        {Loader,Res} ->
            Res;
        {'EXIT',Loader,_What} ->
            error
    end.

check_file_result(_, _, {error,enoent}) ->
    error;
check_file_result(_, _, {error,enotdir}) ->
    error;
check_file_result(_, _, {error,einval}) ->
    error;
check_file_result(Func, Target, {error,Reason}) ->   
    case (catch atom_to_list(Reason)) of
        {'EXIT',_} ->                           % exit trapped
            error;
        Errno ->                                % errno
            Process = case process_info(self(), registered_name) of
                          {registered_name,R} -> 
                              "Process: " ++ atom_to_list(R) ++ ".";
                          _ -> 
                              ""
                      end,
            TargetStr =
                if is_atom(Target) -> atom_to_list(Target);
                   is_list(Target) -> Target;
                   true -> []
                end,
            Report = 
                case TargetStr of
                    [] ->
                        "File operation error: " ++ Errno ++ ". " ++
                        "Function: " ++ atom_to_list(Func) ++ ". " ++ Process;
                    _ ->
                        "File operation error: " ++ Errno ++ ". " ++
                        "Target: " ++ TargetStr ++ ". " ++
                        "Function: " ++ atom_to_list(Func) ++ ". " ++ Process
                end,
            %% this is equal to calling error_logger:error_report/1 which
            %% we don't want to do from code_server during system boot
            error_logger ! {notify,{error_report,group_leader(),
                                    {self(),std_error,Report}}},
            error
    end;
check_file_result(_, _, Other) ->
    Other.

%%% --------------------------------------------------------
%%% The main loop.
%%% --------------------------------------------------------

loop(State, Parent, Paths) ->
    receive
        {Pid,Req} when is_pid(Pid) ->
            %% erlang:display(Req),
            {Resp,State2,Paths2} =
                case Req of
                    {set_path,NewPaths} ->
                        {ok,State,to_strs(NewPaths)};
                    {get_path,_} ->
                        {{ok,Paths},State,Paths};
                    {get_file,File} ->
                        {Res,State1} = handle_get_file(State, Paths, File),
                        {Res,State1,Paths};
                    {get_files,{ModFiles,Fun}} ->
                        {Res,State1} = handle_get_files(State, ModFiles, Paths, Fun),
                        {Res,State1,Paths};
                    {list_dir,Dir} ->
                        {Res,State1} = handle_list_dir(State, Dir),
                        {Res,State1,Paths};
                    {read_file_info,File} ->
                        {Res,State1} = handle_read_file_info(State, File),
                        {Res,State1,Paths};
                    {read_link_info,File} ->
                        {Res,State1} = handle_read_link_info(State, File),
                        {Res,State1,Paths};
                    {get_cwd,[]} ->
                        {Res,State1} = handle_get_cwd(State, []),
                        {Res,State1,Paths};
                    {get_cwd,[_]=Args} ->
                        {Res,State1} = handle_get_cwd(State, Args),
                        {Res,State1,Paths};
                    {set_primary_archive,File,ArchiveBin,FileInfo,ParserFun} ->
                        {Res,State1} =
			    handle_set_primary_archive(State, File,
						       ArchiveBin, FileInfo,
						       ParserFun),
                        {Res,State1,Paths};
                    release_archives ->
                        {Res,State1} = handle_release_archives(State),
                        {Res,State1,Paths};
                    _Other ->
                        {ignore,State,Paths}
                end,
            if Resp =:= ignore -> ok;
               true -> Pid ! {self(),Resp}, ok
            end,
            if 
                is_record(State2, state) ->
                    loop(State2, Parent, Paths2);
                true ->
                    exit({bad_state, Req, State2})          
            end;
        {'EXIT',Parent,W} ->
            _State1 = handle_stop(State),
            exit(W);
        {'EXIT',P,W} ->
            State1 = handle_exit(State, P, W),
            loop(State1, Parent, Paths);
        _Message ->
            loop(State, Parent, Paths)
    after State#state.timeout ->
            State1 = handle_timeout(State, Parent),
            loop(State1, Parent, Paths)
    end.

handle_get_files(State = #state{multi_get = true}, ModFiles, Paths, Fun) ->
    ?SAFE2(efile_multi_get_file_from_port(State, ModFiles, Paths, Fun), State);
handle_get_files(State, _ModFiles, _Paths, _Fun) ->     % no multi get
    {{error,no_multi_get},State}.
    
handle_get_file(State = #state{loader = efile}, Paths, File) ->
    ?SAFE2(efile_get_file_from_port(State, File, Paths), State);
handle_get_file(State = #state{loader = inet}, Paths, File) ->
    ?SAFE2(inet_get_file_from_port(State, File, Paths), State).

handle_set_primary_archive(State= #state{loader = efile}, File, ArchiveBin, FileInfo, ParserFun) ->
    ?SAFE2(efile_set_primary_archive(State, File, ArchiveBin, FileInfo, ParserFun), State).

handle_release_archives(State= #state{loader = efile}) ->
    ?SAFE2(efile_release_archives(State), State).

handle_list_dir(State = #state{loader = efile}, Dir) ->
    ?SAFE2(efile_list_dir(State, Dir), State);<commit_msg>erl_prim_loader: Break loop/3 into two functions for readability<commit_after>%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2013. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%% A primary loader, provides two different methods to fetch a file:
%% efile and inet. The efile method is simple communication with a
%% port program.
%%
%% The distribution loading was removed and replaced with
%% inet loading
%%
%% The start_it/4 function initializes a record with callback 
%% functions used to handle the interface functions.
%%

-module(erl_prim_loader).

%% If the macro DEBUG is defined during compilation, 
%% debug printouts are done through erlang:display/1.
%% Activate this feature by starting the compiler 
%% with> erlc -DDEBUG ... 
%% or by> setenv ERL_COMPILER_FLAGS DEBUG 
%% before running make (in the OTP make system)
%% (the example is for tcsh)

-include("inet_boot.hrl").

%% Public
-export([start/3, set_path/1, get_path/0, get_file/1, get_files/2,
         list_dir/1, read_file_info/1, read_link_info/1, get_cwd/0, get_cwd/1]).

%% Used by erl_boot_server
-export([prim_init/0, prim_get_file/2, prim_list_dir/2,
         prim_read_file_info/3, prim_get_cwd/2]).

%% Used by escript and code
-export([set_primary_archive/4, release_archives/0]).

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

-type host() :: atom().

-record(prim_state, {debug :: boolean(),
		     cache,
		     primary_archive}).
-type prim_state() :: #prim_state{}.

-record(state, 
        {loader            :: 'efile' | 'inet',
         hosts = []        :: [host()], % hosts list (to boot from)
         id,                      % not used any more?
         data              :: 'noport' | port(), % data port etc
         timeout           :: timeout(),         % idle timeout
	 %% Number of timeouts before archives are released
	 n_timeouts        :: non_neg_integer(),
         multi_get = false :: boolean(),
         prim_state        :: prim_state()}).    % state for efile code loader

-define(IDLE_TIMEOUT, 60000).  %% tear inet connection after 1 minutes
-define(N_TIMEOUTS, 6).        %% release efile archive after 6 minutes

%% Defines for inet as prim_loader
-define(INET_FAMILY, inet).
-define(INET_ADDRESS, {0,0,0,0}).

-ifdef(DEBUG).
-define(dbg(Tag, Data), erlang:display({Tag,Data})).
-else.
-define(dbg(Tag, Data), true).
-endif.

-define(SAFE2(Expr, State), 
        fun() ->
                case catch Expr of
                    {'EXIT',XXXReason} -> {{error,XXXReason}, State};
                    XXXRes -> XXXRes
                end
        end()).

debug(#prim_state{debug = Deb}, Term) ->
    case Deb of
        false -> ok;
        true  -> erlang:display(Term)
    end.

%%% --------------------------------------------------------
%%% Interface Functions. 
%%% --------------------------------------------------------

-spec start(Id, Loader, Hosts) ->
	    {'ok', Pid} | {'error', What} when
      Id :: term(),
      Loader :: atom() | string(),
      Hosts :: Host | [Host],
      Host :: host(),
      Pid :: pid(),
      What :: term().
start(Id, Pgm, Hosts) when is_atom(Hosts) ->
    start(Id, Pgm, [Hosts]);
start(Id, Pgm0, Hosts) ->
    Pgm = if
              is_atom(Pgm0) ->
                  atom_to_list(Pgm0);
              true ->
                  Pgm0
          end,
    Self = self(),
    Pid = spawn_link(fun() -> start_it(Pgm, Id, Self, Hosts) end),
    register(erl_prim_loader, Pid),
    receive
        {Pid,ok} ->
            {ok,Pid};
        {'EXIT',Pid,Reason} ->
            {error,Reason}
    end.

%% Hosts must be a list of form ['1.2.3.4' ...]
start_it("inet", Id, Pid, Hosts) ->
    process_flag(trap_exit, true),
    ?dbg(inet, {Id,Pid,Hosts}),
    AL = ipv4_list(Hosts),
    ?dbg(addresses, AL),
    {ok,Tcp} = find_master(AL),
    init_ack(Pid),
    PS = prim_init(),
    State = #state {loader = inet,
                    hosts = AL,
                    id = Id,
                    data = Tcp,
                    timeout = ?IDLE_TIMEOUT,
		    n_timeouts = ?N_TIMEOUTS,
                    prim_state = PS},
    loop(State, Pid, []);

start_it("efile", Id, Pid, _Hosts) ->
    process_flag(trap_exit, true),
    {ok, Port} = prim_file:start(),
    %% Check that we started in a valid directory.
    case prim_file:get_cwd(Port) of
	{error, _} ->
	    %% At this point in the startup, we have no error_logger at all.
	    Report = "Invalid current directory or invalid filename "
		"mode: loader cannot read current directory\n",
	    erlang:display(Report),
	    exit({error, invalid_current_directory});
	_ ->
	    init_ack(Pid)
    end,
    MultiGet = case erlang:system_info(thread_pool_size) of
                   0 -> false;
                   _ -> true
               end,
    PS = prim_init(),
    State = #state {loader = efile,
                    id = Id,
                    data = Port,
                    timeout = infinity,
                    multi_get = MultiGet,
                    prim_state = PS},
    loop(State, Pid, []).

init_ack(Pid) ->
    Pid ! {self(),ok},
    ok.

-spec set_path(Path) -> 'ok' when
      Path :: [Dir :: string()].
set_path(Paths) when is_list(Paths) ->
    request({set_path,Paths}).

-spec get_path() -> {'ok', Path} when
      Path :: [Dir :: string()].
get_path() ->
    request({get_path,[]}).

-spec get_file(Filename) -> {'ok', Bin, FullName} | 'error' when
      Filename :: atom() | string(),
      Bin :: binary(),
      FullName :: string().
get_file(File) when is_atom(File) ->
    get_file(atom_to_list(File));
get_file(File) ->
    check_file_result(get_file, File, request({get_file,File})).

-spec get_files([{atom(), string()}],
		fun((atom(),binary(),string()) -> 'ok' | {'error', atom()})) ->
			'ok' | {'error', atom()}.
get_files(ModFiles, Fun) ->
    case request({get_files,{ModFiles,Fun}}) of
        E = {error,_M} ->
            E;
        {error,Reason,M} ->
            check_file_result(get_files, M, {error,Reason}),
            {error,M};
        ok ->
            ok
    end.

-spec list_dir(Dir) -> {'ok', Filenames} | 'error' when
      Dir :: string(),
      Filenames :: [Filename :: string()].
list_dir(Dir) ->
    check_file_result(list_dir, Dir, request({list_dir,Dir})).

-spec read_file_info(Filename) -> {'ok', FileInfo} | 'error' when
      Filename :: string(),
      FileInfo :: file:file_info().
read_file_info(File) ->
    check_file_result(read_file_info, File, request({read_file_info,File})).

-spec read_link_info(Filename) -> {'ok', FileInfo} | 'error' when
      Filename :: string(),
      FileInfo :: file:file_info().
read_link_info(File) ->
    check_file_result(read_link_info, File, request({read_link_info,File})).

-spec get_cwd() -> {'ok', string()} | 'error'.
get_cwd() ->
    check_file_result(get_cwd, [], request({get_cwd,[]})).

-spec get_cwd(string()) -> {'ok', string()} | 'error'.
get_cwd(Drive) ->
    check_file_result(get_cwd, Drive, request({get_cwd,[Drive]})).

-spec set_primary_archive(File :: string() | 'undefined', 
			  ArchiveBin :: binary() | 'undefined',
			  FileInfo :: #file_info{} | 'undefined',
			  ParserFun :: fun())
			 -> {ok, [string()]} | {error,_}.

set_primary_archive(undefined, undefined, undefined, ParserFun) ->
    request({set_primary_archive, undefined, undefined, undefined, ParserFun});
set_primary_archive(File, ArchiveBin, FileInfo, ParserFun)
  when is_list(File), is_binary(ArchiveBin), is_record(FileInfo, file_info) ->
    request({set_primary_archive, File, ArchiveBin, FileInfo, ParserFun}).

-spec release_archives() -> 'ok' | {'error', _}.

release_archives() ->
    request(release_archives).

request(Req) ->
    Loader = whereis(erl_prim_loader),
    Loader ! {self(),Req},
    receive
        {Loader,Res} ->
            Res;
        {'EXIT',Loader,_What} ->
            error
    end.

check_file_result(_, _, {error,enoent}) ->
    error;
check_file_result(_, _, {error,enotdir}) ->
    error;
check_file_result(_, _, {error,einval}) ->
    error;
check_file_result(Func, Target, {error,Reason}) ->   
    case (catch atom_to_list(Reason)) of
        {'EXIT',_} ->                           % exit trapped
            error;
        Errno ->                                % errno
            Process = case process_info(self(), registered_name) of
                          {registered_name,R} -> 
                              "Process: " ++ atom_to_list(R) ++ ".";
                          _ -> 
                              ""
                      end,
            TargetStr =
                if is_atom(Target) -> atom_to_list(Target);
                   is_list(Target) -> Target;
                   true -> []
                end,
            Report = 
                case TargetStr of
                    [] ->
                        "File operation error: " ++ Errno ++ ". " ++
                        "Function: " ++ atom_to_list(Func) ++ ". " ++ Process;
                    _ ->
                        "File operation error: " ++ Errno ++ ". " ++
                        "Target: " ++ TargetStr ++ ". " ++
                        "Function: " ++ atom_to_list(Func) ++ ". " ++ Process
                end,
            %% this is equal to calling error_logger:error_report/1 which
            %% we don't want to do from code_server during system boot
            error_logger ! {notify,{error_report,group_leader(),
                                    {self(),std_error,Report}}},
            error
    end;
check_file_result(_, _, Other) ->
    Other.

%%% --------------------------------------------------------
%%% The main loop.
%%% --------------------------------------------------------

loop(St0, Parent, Paths) ->
    receive
	{Pid,{set_path,NewPaths}} when is_pid(Pid) ->
	    Pid ! {self(),ok},
	    loop(St0, Parent, to_strs(NewPaths));
        {Pid,Req} when is_pid(Pid) ->
	    case handle_request(Req, Paths, St0) of
		ignore ->
		    ok;
		{Resp,#state{}=St1} ->
		    Pid ! {self(),Resp},
                    loop(St1, Parent, Paths);
		{_,State2,_} ->
                    exit({bad_state,Req,State2})
            end;
        {'EXIT',Parent,W} ->
            _ = handle_stop(St0),
            exit(W);
        {'EXIT',P,W} ->
            St1 = handle_exit(St0, P, W),
            loop(St1, Parent, Paths);
        _Message ->
            loop(St0, Parent, Paths)
    after St0#state.timeout ->
            St1 = handle_timeout(St0, Parent),
            loop(St1, Parent, Paths)
    end.

handle_request(Req, Paths, St0) ->
    case Req of
	{get_path,_} ->
	    {{ok,Paths},St0};
	{get_file,File} ->
	    handle_get_file(St0, Paths, File);
	{get_files,{ModFiles,Fun}} ->
	    handle_get_files(St0, ModFiles, Paths, Fun);
	{list_dir,Dir} ->
	    handle_list_dir(St0, Dir);
	{read_file_info,File} ->
	    handle_read_file_info(St0, File);
	{read_link_info,File} ->
	    handle_read_link_info(St0, File);
	{get_cwd,[]} ->
	    handle_get_cwd(St0, []);
	{get_cwd,[_]=Args} ->
	    handle_get_cwd(St0, Args);
	{set_primary_archive,File,ArchiveBin,FileInfo,ParserFun} ->
	    handle_set_primary_archive(St0, File, ArchiveBin,
				       FileInfo, ParserFun);
	release_archives ->
	    handle_release_archives(St0);
	_ ->
	    ignore
    end.

handle_get_files(State = #state{multi_get = true}, ModFiles, Paths, Fun) ->
    ?SAFE2(efile_multi_get_file_from_port(State, ModFiles, Paths, Fun), State);
handle_get_files(State, _ModFiles, _Paths, _Fun) ->     % no multi get
    {{error,no_multi_get},State}.
    
handle_get_file(State = #state{loader = efile}, Paths, File) ->
    ?SAFE2(efile_get_file_from_port(State, File, Paths), State);
handle_get_file(State = #state{loader = inet}, Paths, File) ->
    ?SAFE2(inet_get_file_from_port(State, File, Paths), State).

handle_set_primary_archive(State= #state{loader = efile}, File, ArchiveBin, FileInfo, ParserFun) ->
    ?SAFE2(efile_set_primary_archive(State, File, ArchiveBin, FileInfo, ParserFun), State).

handle_release_archives(State= #state{loader = efile}) ->
    ?SAFE2(efile_release_archives(State), State).

handle_list_dir(State = #state{loader = efile}, Dir) ->
    ?SAFE2(efile_list_dir(State, Dir), State);
{'fraction_non_alphanumeric': 0.16033962264150944, 'fraction_numerical': 0.006754716981132076, 'mean_word_length': 2.078290161458938, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 197, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1772115', 'n_tokens_mistral': 8531, 'n_tokens_neox': 8191, 'n_words': 1899}