aboutsummaryrefslogtreecommitdiffstats
path: root/erts/preloaded/src/erl_prim_loader.erl
diff options
context:
space:
mode:
Diffstat (limited to 'erts/preloaded/src/erl_prim_loader.erl')
-rw-r--r--erts/preloaded/src/erl_prim_loader.erl1406
1 files changed, 1406 insertions, 0 deletions
diff --git a/erts/preloaded/src/erl_prim_loader.erl b/erts/preloaded/src/erl_prim_loader.erl
new file mode 100644
index 0000000000..399c2bb55d
--- /dev/null
+++ b/erts/preloaded/src/erl_prim_loader.erl
@@ -0,0 +1,1406 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% 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, 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/2, prim_get_cwd/2]).
+
+%% Used by escript and code
+-export([set_primary_archive/2, release_archives/0]).
+
+%% Internal function. Exported to avoid dialyzer warnings
+-export([concat/1]).
+
+-include_lib("kernel/include/file.hrl").
+
+-type host() :: atom().
+
+-record(state,
+ {loader :: 'efile' | 'inet',
+ hosts = [] :: [host()], % hosts list (to boot from)
+ id, % not used any more?
+ data, % data port etc
+ timeout, % idle timeout
+ n_timeouts, % Number of timeouts before archives are released
+ multi_get = false :: boolean(),
+ 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()).
+
+-record(prim_state, {debug, cache, primary_archive}).
+
+debug(#prim_state{debug = Deb}, Term) ->
+ case Deb of
+ false -> ok;
+ true -> erlang:display(Term)
+ end.
+
+%%% --------------------------------------------------------
+%%% Interface Functions.
+%%% --------------------------------------------------------
+
+-spec start(term(), atom() | string(), host() | [host()]) ->
+ {'ok', pid()} | {'error', 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.
+
+start_it("ose_inet"=Cmd, Id, Pid, Hosts) ->
+ %% Setup reserved port for ose_inet driver (only OSE)
+ case catch erlang:open_port({spawn,Cmd},[binary]) of
+ {'EXIT',Why} ->
+ ?dbg(ose_inet_port_open_fail, Why),
+ Why;
+ OseInetPort ->
+ ?dbg(ose_inet_port, OseInetPort),
+ OseInetPort
+ end,
+ start_it("inet", Id, Pid, Hosts);
+
+%% Hosts must be a list on 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:open([binary]),
+ init_ack(Pid),
+ 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([string()]) -> 'ok'.
+set_path(Paths) when is_list(Paths) ->
+ request({set_path,Paths}).
+
+-spec get_path() -> {'ok', [string()]}.
+get_path() ->
+ request({get_path,[]}).
+
+-spec get_file(atom() | string()) -> {'ok', binary(), string()} | 'error'.
+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(string()) -> {'ok', [string()]} | 'error'.
+list_dir(Dir) ->
+ check_file_result(list_dir, Dir, request({list_dir,Dir})).
+
+%% -> {ok,Info} | error
+-spec read_file_info(string()) -> {'ok', tuple()} | 'error'.
+
+read_file_info(File) ->
+ check_file_result(read_file_info, File, request({read_file_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')
+ -> {ok, [string()]} | {error,_}.
+
+set_primary_archive(undefined, undefined) ->
+ request({set_primary_archive, undefined, undefined});
+set_primary_archive(File, ArchiveBin)
+ when is_list(File), is_binary(ArchiveBin) ->
+ request({set_primary_archive, File, ArchiveBin}).
+
+-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(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};
+ {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,Bin} ->
+ {Res,State1} = handle_set_primary_archive(State, File, Bin),
+ {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}
+ end,
+ if
+ is_record(State2, state) ->
+ loop(State2, Parent, Paths2);
+ true ->
+ exit({bad_state, Req, State2})
+ end;
+ {'EXIT',Parent,W} ->
+ 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, Bin) ->
+ ?SAFE2(efile_set_primary_archive(State, File, Bin), 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);
+handle_list_dir(State = #state{loader = inet}, Dir) ->
+ ?SAFE2(inet_list_dir(State, Dir), State).
+
+handle_read_file_info(State = #state{loader = efile}, File) ->
+ ?SAFE2(efile_read_file_info(State, File), State);
+handle_read_file_info(State = #state{loader = inet}, File) ->
+ ?SAFE2(inet_read_file_info(State, File), State).
+
+handle_get_cwd(State = #state{loader = efile}, Drive) ->
+ ?SAFE2(efile_get_cwd(State, Drive), State);
+handle_get_cwd(State = #state{loader = inet}, Drive) ->
+ ?SAFE2(inet_get_cwd(State, Drive), State).
+
+handle_stop(State = #state{loader = efile}) ->
+ efile_stop_port(State);
+handle_stop(State = #state{loader = inet}) ->
+ inet_stop_port(State).
+
+handle_exit(State = #state{loader = efile}, Who, Reason) ->
+ efile_exit_port(State, Who, Reason);
+handle_exit(State = #state{loader = inet}, Who, Reason) ->
+ inet_exit_port(State, Who, Reason).
+
+handle_timeout(State = #state{loader = efile}, Parent) ->
+ efile_timeout_handler(State, Parent);
+handle_timeout(State = #state{loader = inet}, Parent) ->
+ inet_timeout_handler(State, Parent).
+
+%%% --------------------------------------------------------
+%%% Functions which handles efile as prim_loader (default).
+%%% --------------------------------------------------------
+
+%%% Reading many files in parallel is an optimization.
+%%% See also comment in init.erl.
+
+%% -> {ok,State} | {{error,Module},State} | {{error,Reason,Module},State}
+efile_multi_get_file_from_port(State, ModFiles, Paths, Fun) ->
+ Ref = make_ref(),
+ %% More than 200 processes is no gain.
+ Max = min(200, erlang:system_info(thread_pool_size)),
+ efile_multi_get_file_from_port2(ModFiles, 0, Max, State, Paths, Fun, Ref, ok).
+
+efile_multi_get_file_from_port2([MF | MFs], Out, Max, State, Paths, Fun, Ref, Ret) when Out < Max ->
+ Self = self(),
+ _Pid = spawn(fun() -> efile_par_get_file(Ref, State, MF, Paths, Self, Fun) end),
+ efile_multi_get_file_from_port2(MFs, Out+1, Max, State, Paths, Fun, Ref, Ret);
+efile_multi_get_file_from_port2(MFs, Out, Max, _State, Paths, Fun, Ref, Ret) when Out > 0 ->
+ receive
+ {Ref, ok, State1} ->
+ efile_multi_get_file_from_port2(MFs, Out-1, Max, State1, Paths, Fun, Ref, Ret);
+ {Ref, {error,_Mod} = Error, State1} ->
+ efile_multi_get_file_from_port2(MFs, Out-1, Max, State1, Paths, Fun, Ref, Error);
+ {Ref, MF, {error,emfile,State1}} ->
+ %% Max can take negative values. Out cannot.
+ efile_multi_get_file_from_port2([MF | MFs], Out-1, Max-1, State1, Paths, Fun, Ref, Ret);
+ {Ref, {M,_F}, {error,Error,State1}} ->
+ efile_multi_get_file_from_port2(MFs, Out-1, 0, State1, Paths, Fun, Ref, {error,Error,M})
+ end;
+efile_multi_get_file_from_port2(_MFs, 0, _Max, State, _Paths, _Fun, _Ref, Ret) ->
+ {Ret,State}.
+
+efile_par_get_file(Ref, State, {Mod,File} = MF, Paths, Pid, Fun) ->
+ %% One port for each file read in "parallel":
+ case prim_file:open([binary]) of
+ {ok, Port} ->
+ Port0 = State#state.data,
+ State1 = State#state{data = Port},
+ R = case efile_get_file_from_port(State1, File, Paths) of
+ {{error,Reason},State2} ->
+ {Ref,MF,{error,Reason,State2}};
+ {{ok,BinFile,Full},State2} ->
+ %% Fun(...) -> ok | {error,Mod}
+ {Ref,Fun(Mod, BinFile, Full),State2#state{data=Port0}}
+ end,
+ prim_file:close(Port),
+ Pid ! R;
+ {error, Error} ->
+ Pid ! {Ref,MF,{error,Error,State}}
+ end.
+
+%% -> {{ok,BinFile,File},State} | {{error,Reason},State}
+efile_get_file_from_port(State, File, Paths) ->
+ case is_basename(File) of
+ false -> % get absolute file name.
+ efile_get_file_from_port2(State, File);
+ true when Paths =:= [] -> % get plain file name.
+ efile_get_file_from_port2(State, File);
+ true -> % use paths.
+ efile_get_file_from_port3(State, File, Paths)
+ end.
+
+efile_get_file_from_port2(#state{prim_state = PS} = State, File) ->
+ {Res, PS2} = prim_get_file(PS, File),
+ case Res of
+ {error,port_died} ->
+ exit('prim_load port died');
+ {error,Reason} ->
+ {{error,Reason},State#state{prim_state = PS2}};
+ {ok,BinFile} ->
+ {{ok,BinFile,File},State#state{prim_state = PS2}}
+ end.
+
+efile_get_file_from_port3(State, File, [P | Paths]) ->
+ case efile_get_file_from_port2(State, concat([P,"/",File])) of
+ {{error,Reason},State1} when Reason =/= emfile ->
+ case Paths of
+ [] -> % return last error
+ {{error,Reason},State1};
+ _ -> % try more paths
+ efile_get_file_from_port3(State1, File, Paths)
+ end;
+ Result ->
+ Result
+ end;
+efile_get_file_from_port3(State, _File, []) ->
+ {{error,enoent},State}.
+
+efile_set_primary_archive(#state{prim_state = PS} = State, File, Bin) ->
+ {Res, PS2} = prim_set_primary_archive(PS, File, Bin),
+ {Res,State#state{prim_state = PS2}}.
+
+efile_release_archives(#state{prim_state = PS} = State) ->
+ {Res, PS2} = prim_release_archives(PS),
+ {Res,State#state{prim_state = PS2}}.
+
+efile_list_dir(#state{prim_state = PS} = State, Dir) ->
+ {Res, PS2} = prim_list_dir(PS, Dir),
+ {Res, State#state{prim_state = PS2}}.
+
+efile_read_file_info(#state{prim_state = PS} = State, File) ->
+ {Res, PS2} = prim_read_file_info(PS, File),
+ {Res, State#state{prim_state = PS2}}.
+
+efile_get_cwd(#state{prim_state = PS} = State, Drive) ->
+ {Res, PS2} = prim_get_cwd(PS, Drive),
+ {Res, State#state{prim_state = PS2}}.
+
+efile_stop_port(#state{data=Port}=State) ->
+ prim_file:close(Port),
+ State#state{data=noport}.
+
+efile_exit_port(State, Port, Reason) when State#state.data =:= Port ->
+ exit({port_died,Reason});
+efile_exit_port(State, _Port, _Reason) ->
+ State.
+
+efile_timeout_handler(#state{n_timeouts = N} = State, _Parent) ->
+ if
+ N =< 0 ->
+ {_Res, State2} = efile_release_archives(State),
+ State2#state{n_timeouts = ?N_TIMEOUTS};
+ true ->
+ State#state{n_timeouts = N - 1}
+ end.
+
+%%% --------------------------------------------------------
+%%% Functions which handles inet prim_loader
+%%% --------------------------------------------------------
+
+%%
+%% Connect to a boot master
+%% return {ok, Socket} TCP
+%% AL is a list of boot servers (including broadcast addresses)
+%%
+find_master(AL) ->
+ find_master(AL, ?EBOOT_RETRY, ?EBOOT_REQUEST_DELAY, ?EBOOT_SHORT_RETRY_SLEEP,
+ ?EBOOT_UNSUCCESSFUL_TRIES, ?EBOOT_LONG_RETRY_SLEEP).
+
+find_master(AL, Retry, ReqDelay, SReSleep, Tries, LReSleep) ->
+ {ok,U} = ll_udp_open(0),
+ find_master(U, Retry, AL, ReqDelay, SReSleep, [], Tries, LReSleep).
+
+%%
+%% Master connect loop
+%%
+find_master(U, Retry, AddrL, ReqDelay, SReSleep, Ignore, Tries, LReSleep) ->
+ case find_loop(U, Retry, AddrL, ReqDelay, SReSleep, Ignore,
+ Tries, LReSleep) of
+ [] ->
+ find_master(U, Retry, AddrL, ReqDelay, SReSleep, Ignore,
+ Tries, LReSleep);
+ Servers ->
+ ?dbg(servers, Servers),
+ case connect_master(Servers) of
+ {ok, Socket} ->
+ ll_close(U),
+ {ok, Socket};
+ _Error ->
+ find_master(U, Retry, AddrL, ReqDelay, SReSleep,
+ Servers ++ Ignore, Tries, LReSleep)
+ end
+ end.
+
+connect_master([{_Prio,IP,Port} | Servers]) ->
+ case ll_tcp_connect(0, IP, Port) of
+ {ok, S} -> {ok, S};
+ _Error -> connect_master(Servers)
+ end;
+connect_master([]) ->
+ {error, ebusy}.
+
+%%
+%% Always return a list of boot servers or hang.
+%%
+find_loop(U, Retry, AL, ReqDelay, SReSleep, Ignore, Tries, LReSleep) ->
+ case find_loop(U, Retry, AL, ReqDelay, []) of
+ [] -> % no response from any server
+ erlang:display({erl_prim_loader,'no server found'}), % lifesign
+ Tries1 = if Tries > 0 ->
+ sleep(SReSleep),
+ Tries - 1;
+ true ->
+ sleep(LReSleep),
+ 0
+ end,
+ find_loop(U, Retry, AL, ReqDelay, SReSleep, Ignore, Tries1, LReSleep);
+ Servers ->
+ keysort(1, Servers -- Ignore)
+ end.
+
+%% broadcast or send
+find_loop(_U, 0, _AL, _Delay, Acc) ->
+ Acc;
+find_loop(U, Retry, AL, Delay, Acc) ->
+ send_all(U, AL, [?EBOOT_REQUEST, erlang:system_info(version)]),
+ find_collect(U, Retry-1, AL, Delay, Acc).
+
+find_collect(U,Retry,AL,Delay,Acc) ->
+ receive
+ {udp, U, IP, _Port, [$E,$B,$O,$O,$T,$R,Priority,T1,T0 | _Version]} ->
+ Elem = {Priority,IP,T1*256+T0},
+ ?dbg(got, Elem),
+ case member(Elem, Acc) of
+ false -> find_collect(U, Retry, AL, Delay, [Elem | Acc]);
+ true -> find_collect(U, Retry, AL, Delay, Acc)
+ end;
+ _Garbage ->
+ ?dbg(collect_garbage, _Garbage),
+ find_collect(U, Retry, AL, Delay, Acc)
+
+ after Delay ->
+ ?dbg(collected, Acc),
+ case keymember(0, 1, Acc) of %% got high priority server?
+ true -> Acc;
+ false -> find_loop(U, Retry, AL, Delay, Acc)
+ end
+ end.
+
+
+sleep(Time) ->
+ receive after Time -> ok end.
+
+inet_exit_port(State, Port, _Reason) when State#state.data =:= Port ->
+ State#state { data = noport, timeout = infinity };
+inet_exit_port(State, _, _) ->
+ State.
+
+
+inet_timeout_handler(State, _Parent) ->
+ Tcp = State#state.data,
+ if is_port(Tcp) -> ll_close(Tcp);
+ true -> ok
+ end,
+ State#state { timeout = infinity, data = noport }.
+
+%% -> {{ok,BinFile,Tag},State} | {{error,Reason},State}
+inet_get_file_from_port(State, File, Paths) ->
+ case is_basename(File) of
+ false -> % get absolute file name.
+ inet_send_and_rcv({get,File}, File, State);
+ true when Paths =:= [] -> % get plain file name.
+ inet_send_and_rcv({get,File}, File, State);
+ true -> % use paths.
+ inet_get_file_from_port1(File, Paths, State)
+ end.
+
+inet_get_file_from_port1(File, [P | Paths], State) ->
+ File1 = concat([P,"/",File]),
+ case inet_send_and_rcv({get,File1}, File1, State) of
+ {{error,Reason},State1} ->
+ case Paths of
+ [] -> % return last error
+ {{error,Reason},State1};
+ _ -> % try more paths
+ inet_get_file_from_port1(File, Paths, State1)
+ end;
+ Result -> Result
+ end;
+inet_get_file_from_port1(_File, [], State) ->
+ {{error,file_not_found},State}.
+
+inet_send_and_rcv(Msg, Tag, State) when State#state.data =:= noport ->
+ {ok,Tcp} = find_master(State#state.hosts), %% reconnect
+ inet_send_and_rcv(Msg, Tag, State#state { data = Tcp,
+ timeout = ?IDLE_TIMEOUT });
+inet_send_and_rcv(Msg, Tag, #state{data=Tcp,timeout=Timeout}=State) ->
+ prim_inet:send(Tcp, term_to_binary(Msg)),
+ receive
+ {tcp,Tcp,BinMsg} ->
+ case catch binary_to_term(BinMsg) of
+ {get,{ok,BinFile}} ->
+ {{ok,BinFile,Tag},State};
+ {_Cmd,Res={ok,_}} ->
+ {Res,State};
+ {_Cmd,{error,Error}} ->
+ {{error,Error},State};
+ {error,Error} ->
+ {{error,Error},State};
+ {'EXIT',Error} ->
+ {{error,Error},State}
+ end;
+ {tcp_closed,Tcp} ->
+ %% Ok we must reconnect
+ inet_send_and_rcv(Msg, Tag, State#state { data = noport });
+ {tcp_error,Tcp,_Reason} ->
+ %% Ok we must reconnect
+ inet_send_and_rcv(Msg, Tag, inet_stop_port(State));
+ {'EXIT', Tcp, _} ->
+ %% Ok we must reconnect
+ inet_send_and_rcv(Msg, Tag, State#state { data = noport })
+ after Timeout ->
+ %% Ok we must reconnect
+ inet_send_and_rcv(Msg, Tag, inet_stop_port(State))
+ end.
+
+%% -> {{ok,List},State} | {{error,Reason},State}
+inet_list_dir(State, Dir) ->
+ inet_send_and_rcv({list_dir,Dir}, list_dir, State).
+
+%% -> {{ok,Info},State} | {{error,Reason},State}
+inet_read_file_info(State, File) ->
+ inet_send_and_rcv({read_file_info,File}, read_file_info, State).
+
+%% -> {{ok,Cwd},State} | {{error,Reason},State}
+inet_get_cwd(State, []) ->
+ inet_send_and_rcv(get_cwd, get_cwd, State);
+inet_get_cwd(State, [Drive]) ->
+ inet_send_and_rcv({get_cwd,Drive}, get_cwd, State).
+
+inet_stop_port(#state{data=Tcp}=State) ->
+ prim_inet:close(Tcp),
+ State#state{data=noport}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Direct inet_drv access
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+tcp_options() ->
+ [{mode,binary}, {packet,4}, {active, true}, {deliver,term}].
+
+tcp_timeout() ->
+ 15000.
+
+%% options for udp [list, {broadcast, true}, {active,true}]
+udp_options() ->
+ [{mode,list}, {active, true}, {deliver,term}, {broadcast,true}].
+%%
+%% INET version IPv4 addresses
+%%
+ll_tcp_connect(LocalPort, IP, RemotePort) ->
+ case ll_open_set_bind(tcp, ?INET_FAMILY, tcp_options(),
+ ?INET_ADDRESS, LocalPort) of
+ {ok,S} ->
+ case prim_inet:connect(S, IP, RemotePort, tcp_timeout()) of
+ ok -> {ok, S};
+ Error -> port_error(S, Error)
+ end;
+ Error -> Error
+ end.
+
+%%
+%% Open and initialize an udp port for broadcast
+%%
+ll_udp_open(P) ->
+ ll_open_set_bind(udp, ?INET_FAMILY, udp_options(), ?INET_ADDRESS, P).
+
+
+ll_open_set_bind(Protocol, Family, SOpts, IP, Port) ->
+ case prim_inet:open(Protocol, Family) of
+ {ok, S} ->
+ case prim_inet:setopts(S, SOpts) of
+ ok ->
+ case prim_inet:bind(S, IP, Port) of
+ {ok,_} ->
+ {ok, S};
+ Error -> port_error(S, Error)
+ end;
+ Error -> port_error(S, Error)
+ end;
+ Error -> Error
+ end.
+
+
+ll_close(S) ->
+ unlink(S),
+ exit(S, kill).
+
+port_error(S, Error) ->
+ unlink(S),
+ prim_inet:close(S),
+ Error.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+prim_init() ->
+ Deb =
+ case init:get_argument(loader_debug) of
+ {ok, _} -> true;
+ error -> false
+ end,
+ cache_new(#prim_state{debug = Deb}).
+
+prim_release_archives(PS) ->
+ debug(PS, release_archives),
+ {Res, PS2}= prim_do_release_archives(PS, get(), []),
+ debug(PS2, {return, Res}),
+ {Res, PS2}.
+
+prim_do_release_archives(PS, [{ArchiveFile, DictVal} | KeyVals], Acc) ->
+ Res =
+ case DictVal of
+ {primary, _PrimZip} ->
+ ok; % Keep primary archive
+ {_Mtime, Cache} ->
+ debug(PS, {release, cache, ArchiveFile}),
+ erase(ArchiveFile),
+ clear_cache(ArchiveFile, Cache)
+ end,
+ case Res of
+ ok ->
+ prim_do_release_archives(PS, KeyVals, Acc);
+ {error, Reason} ->
+ prim_do_release_archives(PS, KeyVals, [{ArchiveFile, Reason} | Acc])
+ end;
+prim_do_release_archives(PS, [], []) ->
+ {ok, PS#prim_state{primary_archive = undefined}};
+prim_do_release_archives(PS, [], Errors) ->
+ {{error, Errors}, PS#prim_state{primary_archive = undefined}}.
+
+prim_set_primary_archive(PS, undefined, undefined) ->
+ debug(PS, {set_primary_archive, clean}),
+ case PS#prim_state.primary_archive of
+ undefined ->
+ Res = {error, enoent},
+ debug(PS, {return, Res}),
+ {Res, PS};
+ ArchiveFile ->
+ {primary, PrimZip} = erase(ArchiveFile),
+ ok = prim_zip:close(PrimZip),
+ PS2 = PS#prim_state{primary_archive = undefined},
+ Res = {ok, []},
+ debug(PS2, {return, Res}),
+ {Res, PS2}
+ end;
+prim_set_primary_archive(PS, ArchiveFile, ArchiveBin)
+ when is_list(ArchiveFile), is_binary(ArchiveBin) ->
+ %% Try the archive file
+ debug(PS, {set_primary_archive, ArchiveFile, byte_size(ArchiveBin)}),
+ {Res3, PS3} =
+ case PS#prim_state.primary_archive of
+ undefined ->
+ Fun =
+ fun({Funny, _GI, _GB}, A) ->
+ case Funny of
+ ["", "nibe", RevApp] -> % Reverse ebin
+ %% Collect ebin directories in archive
+ Ebin = reverse(RevApp) ++ "/ebin",
+ {true, [Ebin | A]};
+ _ ->
+ {true, A}
+ end
+ end,
+ Ebins0 = [ArchiveFile],
+ case open_archive({ArchiveFile, ArchiveBin}, Ebins0, Fun) of
+ {ok, PrimZip, RevEbins} ->
+ Ebins = reverse(RevEbins),
+ debug(PS, {set_primary_archive, Ebins}),
+ put(ArchiveFile, {primary, PrimZip}),
+ {{ok, Ebins}, PS#prim_state{primary_archive = ArchiveFile}};
+ Error ->
+ debug(PS, {set_primary_archive, Error}),
+ {Error, PS}
+ end;
+ OldArchiveFile ->
+ debug(PS, {set_primary_archive, clean}),
+ PrimZip = erase(OldArchiveFile),
+ ok = prim_zip:close(PrimZip),
+ PS2 = PS#prim_state{primary_archive = undefined},
+ prim_set_primary_archive(PS2, ArchiveFile, ArchiveBin)
+ end,
+ debug(PS3, {return, Res3}),
+ {Res3, PS3}.
+
+prim_get_file(PS, File) ->
+ debug(PS, {get_file, File}),
+ {Res2, PS2} =
+ case name_split(PS#prim_state.primary_archive, File) of
+ {file, PrimFile} ->
+ Res = prim_file:read_file(PrimFile),
+ {Res, PS};
+ {archive, ArchiveFile, FileInArchive} ->
+ debug(PS, {archive_get_file, ArchiveFile, FileInArchive}),
+ FunnyFile = funny_split(FileInArchive, $/),
+ Fun =
+ fun({Funny, _GetInfo, GetBin}, Acc) ->
+ if
+ Funny =:= FunnyFile ->
+ {false, {ok, GetBin()}};
+ true ->
+ {true, Acc}
+ end
+ end,
+ apply_archive(PS, Fun, {error, enoent}, ArchiveFile)
+ end,
+ debug(PS, {return, Res2}),
+ {Res2, PS2}.
+
+%% -> {{ok,List},State} | {{error,Reason},State}
+prim_list_dir(PS, Dir) ->
+ debug(PS, {list_dir, Dir}),
+ {Res2, PS3} =
+ case name_split(PS#prim_state.primary_archive, Dir) of
+ {file, PrimDir} ->
+ Res = prim_file:list_dir(PrimDir),
+ {Res, PS};
+ {archive, ArchiveFile, FileInArchive} ->
+ debug(PS, {archive_list_dir, ArchiveFile, FileInArchive}),
+ FunnyDir = funny_split(FileInArchive, $/),
+ Fun =
+ fun({Funny, _GetInfo, _GetBin}, {Status, Names} = Acc) ->
+ case Funny of
+ [RevName | FD] when FD =:= FunnyDir ->
+ case RevName of
+ "" ->
+ %% The listed directory
+ {true, {ok, Names}};
+ _ ->
+ %% Plain file
+ Name = reverse(RevName),
+ {true, {Status, [Name | Names]}}
+ end;
+ ["", RevName | FD] when FD =:= FunnyDir ->
+ %% Directory
+ Name = reverse(RevName),
+ {true, {Status, [Name | Names]}};
+ [RevName] when FunnyDir =:= [""] ->
+ %% Top file
+ Name = reverse(RevName),
+ {true, {ok, [Name | Names]}};
+ ["", RevName] when FunnyDir =:= [""] ->
+ %% Top file
+ Name = reverse(RevName),
+ {true, {ok, [Name | Names]}};
+ _ ->
+ %% No match
+ {true, Acc}
+ end
+ end,
+ {{Status, Names}, PS2} =
+ apply_archive(PS, Fun, {error, []}, ArchiveFile),
+ case Status of
+ ok -> {{ok, Names}, PS2};
+ error -> {{error, enotdir}, PS2}
+ end
+ end,
+ debug(PS, {return, Res2}),
+ {Res2, PS3}.
+
+%% -> {{ok,Info},State} | {{error,Reason},State}
+prim_read_file_info(PS, File) ->
+ debug(PS, {read_file_info, File}),
+ {Res2, PS2} =
+ case name_split(PS#prim_state.primary_archive, File) of
+ {file, PrimFile} ->
+ Res = prim_file:read_file_info(PrimFile),
+ {Res, PS};
+ {archive, ArchiveFile, []} ->
+ %% Fake top directory
+ debug(PS, {archive_read_file_info, ArchiveFile}),
+ case prim_file:read_file_info(ArchiveFile) of
+ {ok, FI} ->
+ {{ok, FI#file_info{type = directory}}, PS};
+ Other ->
+ {Other, PS}
+ end;
+ {archive, ArchiveFile, FileInArchive} ->
+ debug(PS, {archive_read_file_info, File}),
+ FunnyFile = funny_split(FileInArchive, $/),
+ Fun =
+ fun({Funny, GetInfo, _GetBin}, Acc) ->
+ if
+ hd(Funny) =:= "",
+ tl(Funny) =:= FunnyFile ->
+ %% Directory
+ {false, {ok, GetInfo()}};
+ Funny =:= FunnyFile ->
+ %% Plain file
+ {false, {ok, GetInfo()}};
+ true ->
+ %% No match
+ {true, Acc}
+ end
+ end,
+ apply_archive(PS, Fun, {error, enoent}, ArchiveFile)
+ end,
+ debug(PS2, {return, Res2}),
+ {Res2, PS2}.
+
+prim_get_cwd(PS, []) ->
+ debug(PS, {get_cwd, []}),
+ Res = prim_file:get_cwd(),
+ debug(PS, {return, Res}),
+ {Res, PS};
+prim_get_cwd(PS, [Drive]) ->
+ debug(PS, {get_cwd, Drive}),
+ Res = prim_file:get_cwd(Drive),
+ debug(PS, {return, Res}),
+ {Res, PS}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+apply_archive(PS, Fun, Acc, Archive) ->
+ case get(Archive) of
+ undefined ->
+ case prim_file:read_file_info(Archive) of
+ {ok, #file_info{mtime = Mtime}} ->
+ case open_archive(Archive, Acc, Fun) of
+ {ok, PrimZip, Acc2} ->
+ debug(PS, {cache, ok}),
+ put(Archive, {Mtime, {ok, PrimZip}}),
+ {Acc2, PS};
+ Error ->
+ debug(PS, {cache, Error}),
+ put(Archive, {Mtime, Error}),
+ {Error, PS}
+ end;
+ Error ->
+ debug(PS, {cache, Error}),
+ {Error, PS}
+ end;
+ {primary, PrimZip} ->
+ case foldl_archive(PrimZip, Acc, Fun) of
+ {ok, _PrimZip2, Acc2} ->
+ {Acc2, PS};
+ Error ->
+ debug(PS, {primary, Error}),
+ {Error, PS}
+ end;
+ {Mtime, Cache} ->
+ case prim_file:read_file_info(Archive) of
+ {ok, #file_info{mtime = Mtime2}} when Mtime2 =:= Mtime ->
+ case Cache of
+ {ok, PrimZip} ->
+ case foldl_archive(PrimZip, Acc, Fun) of
+ {ok, _PrimZip2, Acc2} ->
+ {Acc2, PS};
+ Error ->
+ debug(PS, {cache, {clear, Error}}),
+ clear_cache(Archive, Cache),
+ debug(PS, {cache, Error}),
+ put(Archive, {Mtime, Error}),
+ {Error, PS}
+ end;
+ Error ->
+ debug(PS, {cache, Error}),
+ {Error, PS}
+ end;
+ Error ->
+ debug(PS, {cache, {clear, Error}}),
+ clear_cache(Archive, Cache),
+ apply_archive(PS, Fun, Acc, Archive)
+ end
+ end.
+
+open_archive(Archive, Acc, Fun) ->
+ Wrapper =
+ fun({N, GI, GB}, A) ->
+ %% Ensure full iteration at open
+ Funny = funny_split(N, $/),
+ {_Continue, A2} = Fun({Funny, GI, GB}, A),
+ {true, {true, Funny}, A2}
+ end,
+ prim_zip:open(Wrapper, Acc, Archive).
+
+foldl_archive(PrimZip, Acc, Fun) ->
+ Wrapper =
+ fun({N, GI, GB}, A) ->
+ %% Allow partial iteration at foldl
+ {Continue, A2} = Fun({N, GI, GB}, A),
+ {Continue, true, A2}
+ end,
+ prim_zip:foldl(Wrapper, Acc, PrimZip).
+
+cache_new(PS) ->
+ PS.
+
+clear_cache(Archive, Cache) ->
+ erase(Archive),
+ case Cache of
+ {ok, PrimZip} ->
+ prim_zip:close(PrimZip);
+ {error, _} ->
+ ok
+ end.
+
+%%% --------------------------------------------------------
+%%% Misc. functions.
+%%% --------------------------------------------------------
+
+%%% Look for directory separators
+is_basename(File) ->
+ case deep_member($/, File) of
+ true ->
+ false;
+ false ->
+ case erlang:system_info(os_type) of
+ {win32, _} ->
+ case File of
+ [_,$: | _] ->
+ false;
+ _ ->
+ not deep_member($\\, File)
+ end;
+ _ ->
+ true
+ end
+ end.
+
+send_all(U, [IP | AL], Cmd) ->
+ ?dbg(sendto, {U, IP, ?EBOOT_PORT, Cmd}),
+ prim_inet:sendto(U, IP, ?EBOOT_PORT, Cmd),
+ send_all(U, AL, Cmd);
+send_all(_U, [], _) -> ok.
+
+concat([A|T]) when is_atom(A) -> %Atom
+ atom_to_list(A) ++ concat(T);
+concat([C|T]) when C >= 0, C =< 255 ->
+ [C|concat(T)];
+concat([S|T]) -> %String
+ S ++ concat(T);
+concat([]) ->
+ [].
+
+member(X, [X|_]) -> true;
+member(X, [_|Y]) -> member(X, Y);
+member(_X, []) -> false.
+
+deep_member(X, [X|_]) ->
+ true;
+deep_member(X, [List | Y]) when is_list(List) ->
+ deep_member(X, List) orelse deep_member(X, Y);
+deep_member(X, [Atom | Y]) when is_atom(Atom) ->
+ deep_member(X, atom_to_list(Atom)) orelse deep_member(X, Y);
+deep_member(X, [_ | Y]) ->
+ deep_member(X, Y);
+deep_member(_X, []) ->
+ false.
+
+keymember(X, I, [Y | _]) when element(I,Y) =:= X -> true;
+keymember(X, I, [_ | T]) -> keymember(X, I, T);
+keymember(_X, _I, []) -> false.
+
+keysort(I, L) -> keysort(I, L, []).
+
+keysort(I, [X | L], Ls) ->
+ keysort(I, L, keyins(X, I, Ls));
+keysort(_I, [], Ls) -> Ls.
+
+keyins(X, I, [Y | T]) when X < element(I,Y) -> [X,Y|T];
+keyins(X, I, [Y | T]) -> [Y | keyins(X, I, T)];
+keyins(X, _I, []) -> [X].
+
+min(X, Y) when X < Y -> X;
+min(_X, Y) -> Y.
+
+to_strs([P|Paths]) when is_atom(P) ->
+ [atom_to_list(P)|to_strs(Paths)];
+to_strs([P|Paths]) when is_list(P) ->
+ [P|to_strs(Paths)];
+to_strs([_|Paths]) ->
+ to_strs(Paths);
+to_strs([]) ->
+ [].
+
+reverse([] = L) ->
+ L;
+reverse([_] = L) ->
+ L;
+reverse([A, B]) ->
+ [B, A];
+reverse([A, B | L]) ->
+ lists:reverse(L, [B, A]). % BIF
+
+%% Returns all lists in reverse order
+funny_split(List, Sep) ->
+ funny_split(List, Sep, [], []).
+
+funny_split([Sep | Tail], Sep, Path, Paths) ->
+ funny_split(Tail, Sep, [], [Path | Paths]);
+funny_split([Head | Tail], Sep, Path, Paths) ->
+ funny_split(Tail, Sep, [Head | Path], Paths);
+funny_split([], _Sep, Path, Paths) ->
+ [Path | Paths].
+
+name_split(ArchiveFile, File0) ->
+ File = absname(File0),
+ do_name_split(ArchiveFile, File).
+
+do_name_split(undefined, File) ->
+ %% Ignore primary archive
+ case string_split(File, init:archive_extension(), []) of
+ no_split ->
+ %% Plain file
+ {file, File};
+ {split, _RevArchiveBase, RevArchiveFile, []} ->
+ %% Top dir in archive
+ ArchiveFile = reverse(RevArchiveFile),
+ {archive, ArchiveFile, []};
+ {split, _RevArchiveBase, RevArchiveFile, [$/ | FileInArchive]} ->
+ %% File in archive
+ ArchiveFile = reverse(RevArchiveFile),
+ {archive, ArchiveFile, FileInArchive};
+ {split, _RevArchiveBase, _RevArchiveFile, _FileInArchive} ->
+ %% False match. Assume plain file
+ {file, File}
+ end;
+do_name_split(ArchiveFile0, File) ->
+ %% Look first in primary archive
+ ArchiveFile = absname(ArchiveFile0),
+ case string_match(File, ArchiveFile, []) of
+ no_match ->
+ %% Archive or plain file
+ do_name_split(undefined, File);
+ {match, _RevPrimArchiveFile, FileInArchive} ->
+ %% Primary archive
+ case FileInArchive of
+ [$/ | FileInArchive2] ->
+ {archive, ArchiveFile, FileInArchive2};
+ _ ->
+ {archive, ArchiveFile, FileInArchive}
+ end
+ end.
+
+string_match([Char | File], [Char | Archive], RevTop) ->
+ string_match(File, Archive, [Char | RevTop]);
+string_match(File, [], RevTop) ->
+ {match, RevTop, File};
+string_match(_File, _Archive, _RevTop) ->
+ no_match.
+
+string_split([Char | File], [Char | Ext] = FullExt, RevTop) ->
+ RevTop2 = [Char | RevTop],
+ string_split2(File, Ext, RevTop, RevTop2, File, FullExt, RevTop2);
+string_split([Char | File], Ext, RevTop) ->
+ string_split(File, Ext, [Char | RevTop]);
+string_split([], _Ext, _RevTop) ->
+ no_split.
+
+string_split2([Char | File], [Char | Ext], RevBase, RevTop, SaveFile, SaveExt, SaveTop) ->
+ string_split2(File, Ext, RevBase, [Char | RevTop], SaveFile, SaveExt, SaveTop);
+string_split2(File, [], RevBase, RevTop, _SaveFile, _SaveExt, _SaveTop) ->
+ {split, RevBase, RevTop, File};
+string_split2(_, _Ext, _RevBase, _RevTop, SaveFile, SaveExt, SaveTop) ->
+ string_split(SaveFile, SaveExt, SaveTop).
+
+%% Parse list of ipv4 addresses
+ipv4_list([H | T]) ->
+ IPV = if is_atom(H) -> ipv4_address(atom_to_list(H));
+ is_list(H) -> ipv4_address(H);
+ true -> {error,einal}
+ end,
+ case IPV of
+ {ok,IP} -> [IP | ipv4_list(T)];
+ _ -> ipv4_list(T)
+ end;
+ipv4_list([]) -> [].
+
+%%
+%% Parse Ipv4 address: d1.d2.d3.d4 (from inet_parse)
+%%
+%% Return {ok, IP} | {error, einval}
+%%
+ipv4_address(Cs) ->
+ case catch ipv4_addr(Cs, []) of
+ {'EXIT',_} -> {error,einval};
+ Addr -> {ok,Addr}
+ end.
+
+ipv4_addr([C | Cs], IP) when C >= $0, C =< $9 -> ipv4_addr(Cs, C-$0, IP).
+
+ipv4_addr([$.|Cs], N, IP) when N < 256 -> ipv4_addr(Cs, [N|IP]);
+ipv4_addr([C|Cs], N, IP) when C >= $0, C =< $9 ->
+ ipv4_addr(Cs, N*10 + (C-$0), IP);
+ipv4_addr([], D, [C,B,A]) when D < 256 -> {A,B,C,D}.
+
+%% A simplified version of filename:absname/1
+absname(Name) ->
+ Name2 = normalize(Name, []),
+ case pathtype(Name2) of
+ absolute ->
+ Name2;
+ relative ->
+ case prim_file:get_cwd() of
+ {ok, Cwd} ->
+ Cwd ++ "/" ++ Name2;
+ {error, _} ->
+ Name2
+ end;
+ volumerelative ->
+ case prim_file:get_cwd() of
+ {ok, Cwd} ->
+ absname_vr(Name2, Cwd);
+ {error, _} ->
+ Name2
+ end
+ end.
+
+%% Assumes normalized name
+absname_vr([$/ | NameRest], [Drive, $\: | _]) ->
+ %% Absolute path on current drive.
+ [Drive, $\: | NameRest];
+absname_vr([Drive, $\: | NameRest], [Drive, $\: | _] = Cwd) ->
+ %% Relative to current directory on current drive.
+ Cwd ++ "/" ++ NameRest;
+absname_vr([Drive, $\: | NameRest], _) ->
+ %% Relative to current directory on another drive.
+ case prim_file:get_cwd([Drive, $\:]) of
+ {ok, DriveCwd} ->
+ DriveCwd ++ "/" ++ NameRest;
+ {error, _} ->
+ [Drive, $\:, $/] ++ NameRest
+ end.
+
+%% Assumes normalized name
+pathtype(Name) when is_list(Name) ->
+ case erlang:system_info(os_type) of
+ {unix, _} ->
+ unix_pathtype(Name);
+ {win32, _} ->
+ win32_pathtype(Name);
+ {vxworks, _} ->
+ case vxworks_first(Name) of
+ {device, _Rest, _Dev} ->
+ absolute;
+ _ ->
+ relative
+ end;
+ {ose,_} ->
+ unix_pathtype(Name)
+ end.
+
+unix_pathtype(Name) ->
+ case Name of
+ [$/|_] ->
+ absolute;
+ [List|Rest] when is_list(List) ->
+ unix_pathtype(List++Rest);
+ [Atom|Rest] when is_atom(Atom) ->
+ atom_to_list(Atom)++Rest;
+ _ ->
+ relative
+ end.
+
+win32_pathtype(Name) ->
+ case Name of
+ [List|Rest] when is_list(List) ->
+ win32_pathtype(List++Rest);
+ [Atom|Rest] when is_atom(Atom) ->
+ win32_pathtype(atom_to_list(Atom)++Rest);
+ [Char, List | Rest] when is_list(List) ->
+ win32_pathtype([Char | List++Rest]);
+ [$/, $/|_] ->
+ absolute;
+ [$\\, $/|_] ->
+ absolute;
+ [$/, $\\|_] ->
+ absolute;
+ [$\\, $\\|_] ->
+ absolute;
+ [$/|_] ->
+ volumerelative;
+ [$\\|_] ->
+ volumerelative;
+ [C1, C2, List | Rest] when is_list(List) ->
+ pathtype([C1, C2|List ++ Rest]);
+ [_Letter, $:, $/|_] ->
+ absolute;
+ [_Letter, $:, $\\|_] ->
+ absolute;
+ [_Letter, $:|_] ->
+ volumerelative;
+ _ ->
+ relative
+ end.
+
+vxworks_first(Name) ->
+ case Name of
+ [] ->
+ {not_device, [], []};
+ [$/ | T] ->
+ vxworks_first2(device, T, [$/]);
+ [$\\ | T] ->
+ vxworks_first2(device, T, [$/]);
+ [H | T] when is_list(H) ->
+ vxworks_first(H ++ T);
+ [H | T] ->
+ vxworks_first2(not_device, T, [H])
+ end.
+
+vxworks_first2(Devicep, Name, FirstComp) ->
+ case Name of
+ [] ->
+ {Devicep, [], FirstComp};
+ [$/ |T ] ->
+ {Devicep, [$/ | T], FirstComp};
+ [$\\ | T] ->
+ {Devicep, [$/ | T], FirstComp};
+ [$: | T]->
+ {device, T, [$: | FirstComp]};
+ [H | T] when is_list(H) ->
+ vxworks_first2(Devicep, H ++ T, FirstComp);
+ [H | T] ->
+ vxworks_first2(Devicep, T, [H | FirstComp])
+ end.
+
+normalize(Name, Acc) ->
+ case Name of
+ [List | Rest] when is_list(List) ->
+ normalize(List ++ Rest, Acc);
+ [Atom | Rest] when is_atom(Atom) ->
+ normalize(atom_to_list(Atom) ++ Rest, Acc);
+ [$\\ | Chars] ->
+ normalize(Chars, [$/ | Acc]);
+ [Char | Chars] ->
+ normalize(Chars, [Char | Acc]);
+ [] ->
+ reverse(Acc)
+ end.