%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2017. 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% %% %% %% New initial version of init. %% Booting from a script. The script is fetched either from %% a local file or distributed from another erlang node. %% %% Flags: %% -boot File : Absolute file name of the boot script. %% -boot_var Var Value %% : $Var in the boot script is expanded to %% Value. %% -loader LoaderMethod %% : efile, inet %% (Optional - default efile) %% -hosts [Node] : List of hosts from which we can boot. %% (Mandatory if -loader inet) %% -mode embedded : Load all modules at startup, no automatic loading %% -mode interactive : Auto load modules (default system behaviour). %% -path : Override path in bootfile. %% -pa Path+ : Add my own paths first. %% -pz Path+ : Add my own paths last. %% -run : Start own processes. %% -s : Start own processes. %% %% Experimental flags: %% -profile_boot : Use an 'eprof light' to profile boot sequence %% -init_debug : Activate debug printouts in init %% -loader_debug : Activate debug printouts in erl_prim_loader %% -code_path_choice : strict | relaxed -module(init). -export([restart/0,reboot/0,stop/0,stop/1, get_status/0,boot/1,get_arguments/0,get_plain_arguments/0, get_argument/1,script_id/0]). %% for the on_load functionality; not for general use -export([run_on_load_handlers/0]). %% internal exports -export([fetch_loaded/0,ensure_loaded/1,make_permanent/2, notify_when_started/1,wait_until_started/0, objfile_extension/0, archive_extension/0,code_path_choice/0]). -include_lib("kernel/include/file.hrl"). -type internal_status() :: 'starting' | 'started' | 'stopping'. -record(state, {flags = [], args = [], start = [], kernel = [] :: [{atom(), pid()}], bootpid :: pid(), status = {starting, starting} :: {internal_status(), term()}, script_id = [], loaded = [], subscribed = []}). -type state() :: #state{}. %% Data for eval_script/2. -record(es, {init, debug, path, pa, pz, path_choice, prim_load, load_mode, vars }). -define(ON_LOAD_HANDLER, init__boot__on_load_handler). debug(false, _) -> ok; debug(_, T) -> erlang:display(T). -spec get_arguments() -> Flags when Flags :: [{Flag :: atom(), Values :: [string()]}]. get_arguments() -> request(get_arguments). -spec get_plain_arguments() -> [Arg] when Arg :: string(). get_plain_arguments() -> bs2ss(request(get_plain_arguments)). -spec get_argument(Flag) -> {'ok', Arg} | 'error' when Flag :: atom(), Arg :: [Values :: [string()]]. get_argument(Arg) -> request({get_argument, Arg}). -spec script_id() -> Id when Id :: term(). script_id() -> request(script_id). bs2as(L0) when is_list(L0) -> map(fun b2a/1, L0); bs2as(L) -> L. bs2ss(L0) when is_list(L0) -> map(fun b2s/1, L0); bs2ss(L) -> L. -spec get_status() -> {InternalStatus, ProvidedStatus} when InternalStatus :: internal_status(), ProvidedStatus :: term(). get_status() -> request(get_status). -spec fetch_loaded() -> [{module(),file:filename()}]. fetch_loaded() -> request(fetch_loaded). %% Handle dynamic code loading until the %% real code_server has been started. -spec ensure_loaded(atom()) -> 'not_allowed' | {'module', atom()}. ensure_loaded(Module) -> request({ensure_loaded, Module}). -spec make_permanent(file:filename(), 'false' | file:filename()) -> 'ok' | {'error', term()}. make_permanent(Boot,Config) -> request({make_permanent,Boot,Config}). -spec notify_when_started(pid()) -> 'ok' | 'started'. notify_when_started(Pid) -> request({notify_when_started,Pid}). -spec wait_until_started() -> 'ok'. wait_until_started() -> receive {init,started} -> ok end. request(Req) -> init ! {self(),Req}, receive {init,Rep} -> Rep end. -spec restart() -> 'ok'. restart() -> init ! {stop,restart}, ok. -spec reboot() -> 'ok'. reboot() -> init ! {stop,reboot}, ok. -spec stop() -> 'ok'. stop() -> init ! {stop,stop}, ok. -spec stop(Status) -> 'ok' when Status :: non_neg_integer() | string(). stop(Status) when is_integer(Status), Status >= 0 -> stop_1(Status); stop(Status) when is_list(Status) -> case is_bytelist(Status) of true -> stop_1(Status); false -> erlang:error(badarg) end; stop(_) -> erlang:error(badarg). is_bytelist([B|Bs]) when is_integer(B), B >= 0, B < 256 -> is_bytelist(Bs); is_bytelist([]) -> true; is_bytelist(_) -> false. %% Note that we check the type of Status beforehand to ensure that %% the call to halt(Status) by the init process cannot fail stop_1(Status) -> init ! {stop,{stop,Status}}, ok. -spec boot(BootArgs) -> no_return() when BootArgs :: [binary()]. boot(BootArgs) -> register(init, self()), process_flag(trap_exit, true), %% Load the zlib nif zlib:on_load(), %% Load the tracer nif erl_tracer:on_load(), {Start0,Flags,Args} = parse_boot_args(BootArgs), %% We don't get to profile parsing of BootArgs case b2a(get_flag(profile_boot, Flags, false)) of false -> ok; true -> debug_profile_start() end, Start = map(fun prepare_run_args/1, Start0), boot(Start, Flags, Args). prepare_run_args({eval, [Expr]}) -> {eval,Expr}; prepare_run_args({_, L=[]}) -> bs2as(L); prepare_run_args({_, L=[_]}) -> bs2as(L); prepare_run_args({s, [M,F|Args]}) -> [b2a(M), b2a(F) | bs2as(Args)]; prepare_run_args({run, [M,F|Args]}) -> [b2a(M), b2a(F) | bs2ss(Args)]. b2a(Bin) when is_binary(Bin) -> list_to_atom(b2s(Bin)); b2a(A) when is_atom(A) -> A. b2s(Bin) when is_binary(Bin) -> try unicode:characters_to_list(Bin,file:native_name_encoding()) catch _:_ -> binary_to_list(Bin) end; b2s(L) when is_list(L) -> L. map(_F, []) -> []; map(F, [X|Rest]) -> [F(X) | map(F, Rest)]. -spec code_path_choice() -> 'relaxed' | 'strict'. code_path_choice() -> case get_argument(code_path_choice) of {ok,[["strict"]]} -> strict; {ok,[["relaxed"]]} -> relaxed; _Else -> relaxed end. boot(Start,Flags,Args) -> start_on_load_handler_process(), BootPid = do_boot(Flags,Start), State = #state{flags = Flags, args = Args, start = Start, bootpid = BootPid}, boot_loop(BootPid,State). %%% Convert a term to a printable string, if possible. to_string(X, D) when is_list(X), D < 4 -> % assume string F = flatten(X, []), case printable_list(F) of true when length(F) > 0 -> F; _false -> List = [to_string(E, D+1) || E <- X], flatten(["[",join(List),"]"], []) end; to_string(X, _D) when is_list(X) -> "[_]"; to_string(X, _D) when is_atom(X) -> atom_to_list(X); to_string(X, _D) when is_pid(X) -> pid_to_list(X); to_string(X, _D) when is_float(X) -> float_to_list(X); to_string(X, _D) when is_integer(X) -> integer_to_list(X); to_string(X, D) when is_tuple(X), D < 4 -> List = [to_string(E, D+1) || E <- tuple_to_list(X)], flatten(["{",join(List),"}"], []); to_string(X, _D) when is_tuple(X) -> "{_}"; to_string(_X, _D) -> "". % can't do anything with it %% This is an incorrect and narrow definition of printable characters. %% The correct one is in io_lib:printable_list/1 %% printable_list([H|T]) when is_integer(H), H >= 32, H =< 126 -> printable_list(T); printable_list([$\n|T]) -> printable_list(T); printable_list([$\r|T]) -> printable_list(T); printable_list([$\t|T]) -> printable_list(T); printable_list([]) -> true; printable_list(_) -> false. join([] = T) -> T; join([_Elem] = T) -> T; join([Elem|T]) -> [Elem,","|join(T)]. flatten([H|T], Tail) when is_list(H) -> flatten(H, flatten(T, Tail)); flatten([H|T], Tail) -> [H|flatten(T, Tail)]; flatten([], Tail) -> Tail. things_to_string([X|Rest]) -> " (" ++ to_string(X, 0) ++ ")" ++ things_to_string(Rest); things_to_string([]) -> "". halt_string(String, List) -> String ++ things_to_string(List). %% String = string() %% List = [string() | atom() | pid() | number() | list() | tuple()] %% Items in List are truncated if found to be too large -spec crash(_, _) -> no_return(). crash(String, List) -> halt(halt_string(String, List)). %% Status is {InternalStatus,ProvidedStatus} -spec boot_loop(pid(), state()) -> no_return(). boot_loop(BootPid, State) -> receive {BootPid,loaded,NewlyLoaded} -> Loaded = NewlyLoaded ++ State#state.loaded, boot_loop(BootPid, State#state{loaded = Loaded}); {BootPid,started,KernelPid} -> boot_loop(BootPid, new_kernelpid(KernelPid, BootPid, State)); {BootPid,progress,started} -> {InS,_} = State#state.status, notify(State#state.subscribed), boot_loop(BootPid,State#state{status = {InS,started}, subscribed = []}); {BootPid,progress,NewStatus} -> {InS,_} = State#state.status, boot_loop(BootPid,State#state{status = {InS,NewStatus}}); {BootPid,{script_id,Id}} -> boot_loop(BootPid,State#state{script_id = Id}); {'EXIT',BootPid,normal} -> {_,PS} = State#state.status, notify(State#state.subscribed), loop(State#state{status = {started,PS}, subscribed = []}); {'EXIT',BootPid,Reason} -> erlang:display({"init terminating in do_boot",Reason}), crash("init terminating in do_boot", [Reason]); {'EXIT',Pid,Reason} -> Kernel = State#state.kernel, terminate(Pid,Kernel,Reason), %% If Pid is a Kernel pid, halt()! boot_loop(BootPid,State); {stop,Reason} -> stop(Reason,State); {From,fetch_loaded} -> %% Fetch and reset initially loaded modules. From ! {init,State#state.loaded}, garb_boot_loop(BootPid,State#state{loaded = []}); {From,{ensure_loaded,Module}} -> {Res, Loaded} = ensure_loaded(Module, State#state.loaded), From ! {init,Res}, boot_loop(BootPid,State#state{loaded = Loaded}); Msg -> boot_loop(BootPid,handle_msg(Msg,State)) end. ensure_loaded(Module, Loaded) -> case erlang:module_loaded(Module) of true -> {{module, Module}, Loaded}; false -> do_ensure_loaded(Module, Loaded) end. do_ensure_loaded(Module, Loaded) -> File = atom_to_list(Module) ++ objfile_extension(), case erl_prim_loader:get_file(File) of {ok,BinCode,FullName} -> case do_load_module(Module, BinCode) of ok -> {{module, Module}, [{Module, FullName}|Loaded]}; error -> {error, [{Module, FullName}|Loaded]} end; Error -> {Error, Loaded} end. %% Tell subscribed processes the system has started. notify(Pids) -> lists:foreach(fun(Pid) -> Pid ! {init,started} end, Pids). %% Garbage collect all info about initially loaded modules. %% This information is temporarily stored until the code_server %% is started. %% We force the garbage collection as the init process holds %% this information during the initialisation of the system and %% it will be automatically garbed much later (perhaps not at all %% if it is not accessed much). garb_boot_loop(BootPid,State) -> garbage_collect(), boot_loop(BootPid,State). new_kernelpid({Name,{ok,Pid}},BootPid,State) when is_pid(Pid) -> link(Pid), BootPid ! {self(),ok,Pid}, Kernel = State#state.kernel, State#state{kernel = [{Name,Pid}|Kernel]}; new_kernelpid({_Name,ignore},BootPid,State) -> BootPid ! {self(),ignore}, State; new_kernelpid({Name,What},BootPid,State) -> erlang:display({"could not start kernel pid",Name,What}), clear_system(BootPid,State), crash("could not start kernel pid", [Name, What]). %% Here is the main loop after the system has booted. loop(State) -> receive {'EXIT',Pid,Reason} -> Kernel = State#state.kernel, terminate(Pid,Kernel,Reason), %% If Pid is a Kernel pid, halt()! loop(State); {stop,Reason} -> stop(Reason,State); {From,fetch_loaded} -> %% The Loaded info is cleared in Loaded = State#state.loaded, %% boot_loop but is handled here From ! {init,Loaded}, %% anyway. loop(State); Msg -> loop(handle_msg(Msg,State)) end. handle_msg(Msg,State0) -> case catch do_handle_msg(Msg,State0) of {new_state,State} -> State; _ -> State0 end. do_handle_msg(Msg,State) -> #state{flags = Flags, status = Status, script_id = Sid, args = Args, subscribed = Subscribed} = State, case Msg of {From,get_plain_arguments} -> From ! {init,Args}; {From,get_arguments} -> From ! {init,get_arguments(Flags)}; {From,{get_argument,Arg}} -> From ! {init,get_argument(Arg,Flags)}; {From,get_status} -> From ! {init,Status}; {From,script_id} -> From ! {init,Sid}; {From,{make_permanent,Boot,Config}} -> {Res,State1} = make_permanent(Boot,Config,Flags,State), From ! {init,Res}, {new_state,State1}; {From,{notify_when_started,Pid}} -> case Status of {InS,PS} when InS =:= started ; PS =:= started -> From ! {init,started}; _ -> From ! {init,ok}, {new_state,State#state{subscribed = [Pid|Subscribed]}} end; {From, {ensure_loaded, _}} -> From ! {init, not_allowed}; X -> case whereis(user) of undefined -> catch error_logger ! {info, self(), {self(), X, []}}; User -> User ! X, ok end end. %%% ------------------------------------------------- %%% A new release has been installed and made %%% permanent. %%% Both restart/0 and reboot/0 shall startup using %%% the new release. reboot/0 uses new boot script %%% and configuration file pointed out externally. %%% In the restart case we have to set new -boot and %%% -config arguments. %%% ------------------------------------------------- make_permanent(Boot,Config,Flags0,State) -> case set_flag(boot, Boot, Flags0) of {ok,Flags1} -> case set_flag(config, Config, Flags1) of {ok,Flags} -> {ok,State#state{flags = Flags}}; Error -> {Error,State} end; Error -> {Error,State} end. set_flag(_Flag,false,Flags) -> {ok,Flags}; set_flag(Flag,Value,Flags) when is_list(Value) -> %% The flag here can be -boot or -config, which means the value is %% a file name! Thus the file name encoding is used when coverting. Encoding = file:native_name_encoding(), case catch unicode:characters_to_binary(Value,Encoding,Encoding) of {'EXIT',_} -> {error,badarg}; AValue -> {ok,set_argument(Flags,Flag,AValue)} end; set_flag(_,_,_) -> {error,badarg}. %%% ------------------------------------------------- %%% Stop the system. %%% Reason is: restart | reboot | stop %%% According to reason terminate emulator or restart %%% system using the same init process again. %%% ------------------------------------------------- stop(Reason,State) -> BootPid = State#state.bootpid, {_,Progress} = State#state.status, State1 = State#state{status = {stopping, Progress}}, clear_system(BootPid,State1), do_stop(Reason,State1). do_stop(restart,#state{start = Start, flags = Flags, args = Args}) -> boot(Start,Flags,Args); do_stop(reboot,_) -> halt(); do_stop(stop,State) -> stop_heart(State), halt(); do_stop({stop,Status},State) -> stop_heart(State), halt(Status). clear_system(BootPid,State) -> Heart = get_heart(State#state.kernel), shutdown_pids(Heart,BootPid,State), unload(Heart). stop_heart(State) -> case get_heart(State#state.kernel) of false -> ok; Pid -> %% As heart survives a restart the Parent of heart is init. BootPid = self(), %% ignore timeout shutdown_kernel_pid(Pid, BootPid, self(), State) end. shutdown_pids(Heart,BootPid,State) -> Timer = shutdown_timer(State#state.flags), catch shutdown(State#state.kernel,BootPid,Timer,State), kill_all_pids(Heart), % Even the shutdown timer. kill_all_ports(Heart), flush_timout(Timer). get_heart([{heart,Pid}|_Kernel]) -> Pid; get_heart([_|Kernel]) -> get_heart(Kernel); get_heart(_) -> false. shutdown([{heart,_Pid}|Kernel],BootPid,Timer,State) -> shutdown(Kernel, BootPid, Timer, State); shutdown([{_Name,Pid}|Kernel],BootPid,Timer,State) -> shutdown_kernel_pid(Pid, BootPid, Timer, State), shutdown(Kernel,BootPid,Timer,State); shutdown(_,_,_,_) -> true. %% %% A kernel pid must handle the special case message %% {'EXIT',Parent,Reason} and terminate upon it! %% shutdown_kernel_pid(Pid, BootPid, Timer, State) -> Pid ! {'EXIT',BootPid,shutdown}, shutdown_loop(Pid, Timer, State, []). %% %% We have to handle init requests here in case a process %% performs such a request and cannot shutdown (deadlock). %% Keep all other EXIT messages in case it was another %% kernel process. Resend these messages and handle later. %% shutdown_loop(Pid,Timer,State,Exits) -> receive {'EXIT',Pid,_} -> resend(reverse(Exits)), ok; {Timer,timeout} -> erlang:display({init,shutdown_timeout}), throw(timeout); {stop,_} -> shutdown_loop(Pid,Timer,State,Exits); {From,fetch_loaded} -> From ! {init,State#state.loaded}, shutdown_loop(Pid,Timer,State,Exits); {'EXIT',OtherP,Reason} -> shutdown_loop(Pid,Timer,State, [{'EXIT',OtherP,Reason}|Exits]); Msg -> State1 = handle_msg(Msg,State), shutdown_loop(Pid,Timer,State1,Exits) end. resend([ExitMsg|Exits]) -> self() ! ExitMsg, resend(Exits); resend(_) -> ok. %% %% Kill all existing pids in the system (except init and heart). kill_all_pids(Heart) -> case get_pids(Heart) of [] -> ok; Pids -> kill_em(Pids), kill_all_pids(Heart) % Continue until all are really killed. end. %% All except system processes. get_pids(Heart) -> Pids = [P || P <- processes(), not erts_internal:is_system_process(P)], delete(Heart,self(),Pids). delete(Heart,Init,[Heart|Pids]) -> delete(Heart,Init,Pids); delete(Heart,Init,[Init|Pids]) -> delete(Heart,Init,Pids); delete(Heart,Init,[Pid|Pids]) -> [Pid|delete(Heart,Init,Pids)]; delete(_,_,[]) -> []. kill_em([Pid|Pids]) -> exit(Pid,kill), kill_em(Pids); kill_em([]) -> ok. %% %% Kill all existing ports in the system (except the heart port), %% i.e. ports still existing after all processes have been killed. %% %% System ports like the async driver port will nowadays be immortal; %% therefore, it is ok to send them exit signals... %% kill_all_ports(Heart) -> kill_all_ports(Heart,erlang:ports()). kill_all_ports(Heart,[P|Ps]) -> case erlang:port_info(P,connected) of {connected,Heart} -> kill_all_ports(Heart,Ps); _ -> exit(P,kill), kill_all_ports(Heart,Ps) end; kill_all_ports(_,_) -> ok. unload(false) -> do_unload(sub(erlang:pre_loaded(),erlang:loaded())); unload(_) -> do_unload(sub([heart|erlang:pre_loaded()],erlang:loaded())). do_unload([M|Mods]) -> catch erlang:purge_module(M), catch erlang:delete_module(M), catch erlang:purge_module(M), do_unload(Mods); do_unload([]) -> ok. sub([H|T],L) -> sub(T,del(H,L)); sub([],L) -> L. del(Item, [Item|T]) -> T; del(Item, [H|T]) -> [H|del(Item, T)]; del(_Item, []) -> []. %%% ------------------------------------------------- %%% If the terminated Pid is one of the processes %%% added to the Kernel, take down the system brutally. %%% We are not sure that ANYTHING can work anymore, %%% i.e. halt the system. %%% Sleep awhile, it is thus possible for the %%% error_logger (if it is still alive) to write errors %%% using the simplest method. %%% ------------------------------------------------- terminate(Pid,Kernel,Reason) -> case kernel_pid(Pid,Kernel) of {ok,Name} -> sleep(500), %% Flush error printouts! erlang:display({"Kernel pid terminated",Name,Reason}), crash("Kernel pid terminated", [Name, Reason]); _ -> false end. kernel_pid(Pid,[{Name,Pid}|_]) -> {ok,Name}; kernel_pid(Pid,[_|T]) -> kernel_pid(Pid,T); kernel_pid(_,_) -> false. sleep(T) -> receive after T -> ok end. %%% ------------------------------------------------- %%% Start the loader. %%% The loader shall run for ever! %%% ------------------------------------------------- start_prim_loader(Init, Path0, {Pa,Pz}) -> Path = case Path0 of false -> Pa ++ ["."|Pz]; _ -> Path0 end, case erl_prim_loader:start() of {ok,Pid} -> erl_prim_loader:set_path(Path), add_to_kernel(Init, Pid); {error,Reason} -> erlang:display({"cannot start loader",Reason}), exit(Reason) end. add_to_kernel(Init,Pid) -> Init ! {self(),started,{erl_prim_loader,{ok,Pid}}}, receive {Init,ok,Pid} -> unlink(Pid), ok end. %%% ------------------------------------------------- %%% The boot process fetches a boot script and loads %%% all modules specified and starts spec. processes. %%% Processes specified with -s or -run are finally started. %%% ------------------------------------------------- do_boot(Flags,Start) -> Self = self(), spawn_link(fun() -> do_boot(Self,Flags,Start) end). do_boot(Init,Flags,Start) -> process_flag(trap_exit,true), Root = get_root(Flags), Path = get_flag_list(path, Flags, false), {Pa,Pz} = PathFls = path_flags(Flags), start_prim_loader(Init, bs2ss(Path), PathFls), BootFile = bootfile(Flags,Root), BootList = get_boot(BootFile,Root), LoadMode = b2a(get_flag(mode, Flags, false)), Deb = b2a(get_flag(init_debug, Flags, false)), catch ?ON_LOAD_HANDLER ! {init_debug_flag,Deb}, BootVars = get_boot_vars(Root, Flags), PathChoice = code_path_choice(), Es = #es{init=Init,debug=Deb,path=Path,pa=Pa,pz=Pz, path_choice=PathChoice, prim_load=true,load_mode=LoadMode, vars=BootVars}, eval_script(BootList, Es), %% To help identifying Purify windows that pop up, %% print the node name into the Purify log. (catch erlang:system_info({purify, "Node: " ++ atom_to_list(node())})), start_em(Start), case b2a(get_flag(profile_boot,Flags,false)) of false -> ok; true -> debug_profile_format_mfas(debug_profile_mfas()), debug_profile_stop() end, ok. get_root(Flags) -> case get_argument(root, Flags) of {ok,[[Root]]} -> Root; _ -> exit(no_or_multiple_root_variables) end. get_boot_vars(Root, Flags) -> BootVars = get_boot_vars_1(#{}, Flags), RootKey = <<"ROOT">>, BootVars#{RootKey=>Root}. get_boot_vars_1(Vars, [{boot_var,[Key,Value]}|T]) -> get_boot_vars_1(Vars#{Key=>Value}, T); get_boot_vars_1(_, [{boot_var,_}|_]) -> exit(invalid_boot_var_argument); get_boot_vars_1(Vars, [_|T]) -> get_boot_vars_1(Vars, T); get_boot_vars_1(Vars, []) -> Vars. bootfile(Flags,Root) -> b2s(get_flag(boot, Flags, Root++"/bin/start")). path_flags(Flags) -> Pa = append(reverse(get_flag_args(pa, Flags))), Pz = append(get_flag_args(pz, Flags)), {bs2ss(Pa),bs2ss(Pz)}. get_boot(BootFile0,Root) -> BootFile = BootFile0 ++ ".boot", case get_boot(BootFile) of {ok, CmdList} -> CmdList; not_found -> %% Check for default. BootF = Root ++ "/bin/" ++ BootFile, case get_boot(BootF) of {ok, CmdList} -> CmdList; not_found -> exit({'cannot get bootfile',list_to_atom(BootFile)}); _ -> exit({'bootfile format error',list_to_atom(BootF)}) end; _ -> exit({'bootfile format error',list_to_atom(BootFile)}) end. get_boot(BootFile) -> case erl_prim_loader:get_file(BootFile) of {ok,Bin,_} -> case binary_to_term(Bin) of {script,Id,CmdList} when is_list(CmdList) -> init ! {self(),{script_id,Id}}, % ;-) {ok, CmdList}; _ -> error end; _ -> not_found end. %% %% Eval a boot script. %% Load modules and start processes. %% If a start command does not spawn a new process the %% boot process hangs (we want to ensure syncronicity). %% eval_script([{progress,Info}=Progress|T], #es{debug=Deb}=Es) -> debug(Deb, Progress), init ! {self(),progress,Info}, eval_script(T, Es); eval_script([{preLoaded,_}|T], #es{}=Es) -> eval_script(T, Es); eval_script([{path,Path}|T], #es{path=false,pa=Pa,pz=Pz, path_choice=PathChoice, vars=Vars}=Es) -> RealPath0 = make_path(Pa, Pz, Path, Vars), RealPath = patch_path(RealPath0, PathChoice), erl_prim_loader:set_path(RealPath), eval_script(T, Es); eval_script([{path,_}|T], #es{}=Es) -> %% Ignore, use the command line -path flag. eval_script(T, Es); eval_script([{kernel_load_completed}|T], #es{load_mode=Mode}=Es0) -> Es = case Mode of embedded -> Es0; _ -> Es0#es{prim_load=false} end, eval_script(T, Es); eval_script([{primLoad,Mods}|T], #es{init=Init,prim_load=PrimLoad}=Es) when is_list(Mods) -> case PrimLoad of true -> load_modules(Mods, Init); false -> %% Do not load now, code_server does that dynamically! ok end, eval_script(T, Es); eval_script([{kernelProcess,Server,{Mod,Fun,Args}}|T], #es{init=Init,debug=Deb}=Es) -> debug(Deb, {start,Server}), start_in_kernel(Server, Mod, Fun, Args, Init), eval_script(T, Es); eval_script([{apply,{Mod,Fun,Args}}=Apply|T], #es{debug=Deb}=Es) -> debug(Deb, Apply), apply(Mod, Fun, Args), eval_script(T, Es); eval_script([], #es{}) -> ok; eval_script(What, #es{}) -> exit({'unexpected command in bootfile',What}). load_modules(Mods0, Init) -> Mods = [M || M <- Mods0, not erlang:module_loaded(M)], F = prepare_loading_fun(), case erl_prim_loader:get_modules(Mods, F) of {ok,{Prep0,[]}} -> Prep = [Code || {_,{prepared,Code,_}} <- Prep0], ok = erlang:finish_loading(Prep), Loaded = [{Mod,Full} || {Mod,{_,_,Full}} <- Prep0], Init ! {self(),loaded,Loaded}, Beams = [{M,Beam,Full} || {M,{on_load,Beam,Full}} <- Prep0], load_rest(Beams, Init); {ok,{_,[_|_]=Errors}} -> Ms = [M || {M,_} <- Errors], exit({load_failed,Ms}) end. load_rest([{Mod,Beam,Full}|T], Init) -> do_load_module(Mod, Beam), Init ! {self(),loaded,[{Mod,Full}]}, load_rest(T, Init); load_rest([], _) -> ok. prepare_loading_fun() -> fun(Mod, FullName, Beam) -> case erlang:prepare_loading(Mod, Beam) of {error,_}=Error -> Error; Prepared -> case erlang:has_prepared_code_on_load(Prepared) of true -> {ok,{on_load,Beam,FullName}}; false -> {ok,{prepared,Prepared,FullName}} end end end. make_path(Pa, Pz, Path, Vars) -> append([Pa,append([fix_path(Path,Vars),Pz])]). %% For all Paths starting with $ROOT add rootdir and for those %% starting with $xxx/, expand $xxx to the value supplied with -boot_var! %% If $xxx cannot be expanded this process terminates. fix_path([Path|Ps], Vars) when is_atom(Path) -> [add_var(atom_to_list(Path), Vars)|fix_path(Ps, Vars)]; fix_path([Path|Ps], Vars) -> [add_var(Path, Vars)|fix_path(Ps, Vars)]; fix_path(_, _) -> []. add_var("$"++Path0, Vars) -> {Var,Path} = extract_var(Path0, []), Key = list_to_binary(Var), case Vars of #{Key:=Value0} -> Value = b2s(Value0), Value ++ "/" ++ Path; _ -> Error0 = "cannot expand $" ++ Var ++ " in bootfile", Error = list_to_atom(Error0), exit(Error) end; add_var(Path, _) -> Path. extract_var([$/|Path],Var) -> {reverse(Var),Path}; extract_var([H|T],Var) -> extract_var(T,[H|Var]); extract_var([],Var) -> {reverse(Var),[]}. patch_path(Dirs, strict) -> Dirs; patch_path(Dirs, relaxed) -> ArchiveExt = archive_extension(), [patch_dir(Dir, ArchiveExt) || Dir <- Dirs]. patch_dir(Orig, ArchiveExt) -> case funny_split(Orig, $/) of ["nibe", RevApp, RevArchive | RevTop] -> App = reverse(RevApp), case funny_splitwith(RevArchive, $.) of {Ext, Base} when Ext =:= ArchiveExt, Base =:= App -> %% Orig archive Top = reverse([reverse(C) || C <- RevTop]), Dir = join(Top ++ [App, "ebin"], "/"), Archive = Orig; _ -> %% Orig directory Top = reverse([reverse(C) || C <- [RevArchive | RevTop]]), Archive = join(Top ++ [App ++ ArchiveExt, App, "ebin"], "/"), Dir = Orig end, %% First try dir, second try archive and at last use orig if both fails case erl_prim_loader:read_file_info(Dir) of {ok, #file_info{type = directory}} -> Dir; _ -> case erl_prim_loader:read_file_info(Archive) of {ok, #file_info{type = directory}} -> Archive; _ -> Orig end end; _ -> Orig end. %% 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]. %% Returns {BeforeSep, AfterSep} where BeforeSep is in reverse order funny_splitwith(List, Sep) -> funny_splitwith(List, Sep, [], List). funny_splitwith([Sep | Tail], Sep, Acc, _Orig) -> {Acc, Tail}; funny_splitwith([Head | Tail], Sep, Acc, Orig) -> funny_splitwith(Tail, Sep, [Head | Acc], Orig); funny_splitwith([], _Sep, _Acc, Orig) -> {[], Orig}. -spec join([string()], string()) -> string(). join([H1, H2 | T], S) -> H1 ++ S ++ join([H2 | T], S); join([H], _) -> H. %% Servers that are located in the init kernel are linked %% and supervised by init. start_in_kernel(Server,Mod,Fun,Args,Init) -> Res = apply(Mod,Fun,Args), Init ! {self(),started,{Server,Res}}, receive {Init,ok,Pid} -> unlink(Pid), %% Just for sure... ok; {Init,ignore} -> ignore end. %% Do start all processes specified at command line using -s! %% Use apply here instead of spawn to ensure syncronicity for %% those servers that wish to have it so. %% Disadvantage: anything started with -s that does not %% eventually spawn will hang the startup routine. %% We also handle -eval here. The argument is an arbitrary %% expression that should be parsed and evaluated. start_em([S|Tail]) -> case whereis(user) of undefined -> ok; P when is_pid(P) -> %Let's set the group_leader() erlang:group_leader(P, self()) end, start_it(S), start_em(Tail); start_em([]) -> ok. start_it([]) -> ok; start_it({eval,Bin}) -> Str = b2s(Bin), {ok,Ts,_} = erl_scan:string(Str), Ts1 = case reverse(Ts) of [{dot,_}|_] -> Ts; TsR -> reverse([{dot,erl_anno:new(1)} | TsR]) end, {ok,Expr} = erl_parse:parse_exprs(Ts1), {value, _Value, _Bs} = erl_eval:exprs(Expr, erl_eval:new_bindings()), ok; start_it([_|_]=MFA) -> case MFA of [M] -> M:start(); [M,F] -> M:F(); [M,F|Args] -> M:F(Args) % Args is a list end. %% Load a module. do_load_module(Mod, BinCode) -> case erlang:load_module(Mod, BinCode) of {module,Mod} -> ok; {error,on_load} -> ?ON_LOAD_HANDLER ! {loaded,Mod}, ok; _ -> error end. %% -------------------------------------------------------- %% If -shutdown_time is specified at the command line %% this timer will inform the init process that it has to %% force processes to terminate. It cannot be handled %% softly any longer. %% -------------------------------------------------------- shutdown_timer(Flags) -> case get_flag(shutdown_time, Flags, infinity) of infinity -> self(); Time -> case catch list_to_integer(binary_to_list(Time)) of T when is_integer(T) -> Pid = spawn(fun() -> timer(T) end), receive {Pid, started} -> Pid end; _ -> self() end end. flush_timout(Pid) -> receive {Pid, timeout} -> true after 0 -> true end. timer(T) -> init ! {self(), started}, receive after T -> init ! {self(), timeout} end. %% -------------------------------------------------------- %% Parse the command line arguments and extract things to start, flags %% and other arguments. We keep the relative of the groups. %% -------------------------------------------------------- parse_boot_args(Args) -> parse_boot_args(Args, [], [], []). parse_boot_args([B|Bs], Ss, Fs, As) -> case check(B) of start_extra_arg -> {reverse(Ss),reverse(Fs),lists:reverse(As, Bs)}; % BIF start_arg -> {S,Rest} = get_args(Bs, []), parse_boot_args(Rest, [{s, S}|Ss], Fs, As); start_arg2 -> {S,Rest} = get_args(Bs, []), parse_boot_args(Rest, [{run, S}|Ss], Fs, As); eval_arg -> {Expr,Rest} = get_args(Bs, []), parse_boot_args(Rest, [{eval, Expr}|Ss], Fs, As); {flag,A} -> {F,Rest} = get_args(Bs, []), Fl = {A,F}, parse_boot_args(Rest, Ss, [Fl|Fs], As); arg -> parse_boot_args(Bs, Ss, Fs, [B|As]); end_args -> parse_boot_args(Bs, Ss, Fs, As) end; parse_boot_args([], Start, Flags, Args) -> {reverse(Start),reverse(Flags),reverse(Args)}. check(<<"-extra">>) -> start_extra_arg; check(<<"-s">>) -> start_arg; check(<<"-run">>) -> start_arg2; check(<<"-eval">>) -> eval_arg; check(<<"--">>) -> end_args; check(<<"-",Flag/binary>>) -> {flag,b2a(Flag)}; check(_) -> arg. get_args([B|Bs], As) -> case check(B) of start_extra_arg -> {reverse(As), [B|Bs]}; start_arg -> {reverse(As), [B|Bs]}; start_arg2 -> {reverse(As), [B|Bs]}; eval_arg -> {reverse(As), [B|Bs]}; end_args -> {reverse(As), Bs}; {flag,_} -> {reverse(As), [B|Bs]}; arg -> get_args(Bs, [B|As]) end; get_args([], As) -> {reverse(As),[]}. %% %% Internal get_flag function, with default value. %% Return: true if flag given without args %% atom() if a single arg was given. %% list(atom()) if several args were given. %% get_flag(F, Flags, Default) -> case lists:keyfind(F, 1, Flags) of {F,[]} -> true; {F,[V]} -> V; {F,V} -> V; _ -> Default end. %% %% Internal get_flag function, with default value. %% Return: list(atom()) %% get_flag_list(F, Flags, Default) -> case lists:keyfind(F, 1, Flags) of {F,[_|_]=V} -> V; _ -> Default end. %% %% Internal get_flag function. %% Fetch all occurrences of flag. %% Return: [Args,Args,...] where Args ::= list(atom()) %% get_flag_args(F,Flags) -> get_flag_args(F,Flags,[]). get_flag_args(F,[{F,V}|Flags],Acc) -> get_flag_args(F,Flags,[V|Acc]); get_flag_args(F,[_|Flags],Acc) -> get_flag_args(F,Flags,Acc); get_flag_args(_,[],Acc) -> reverse(Acc). get_arguments([{F,V}|Flags]) -> [{F,to_strings(V)}|get_arguments(Flags)]; get_arguments([]) -> []. to_strings([H|T]) when is_atom(H) -> [atom_to_list(H)|to_strings(T)]; to_strings([H|T]) when is_binary(H) -> [b2s(H)|to_strings(T)]; to_strings([]) -> []. get_argument(Arg, Flags) -> case get_argument1(Arg, Flags) of [] -> error; Value -> {ok,Value} end. get_argument1(Arg, [{Arg,V}|Args]) -> [to_strings(V)|get_argument1(Arg, Args)]; get_argument1(Arg, [_|Args]) -> get_argument1(Arg, Args); get_argument1(_, []) -> []. set_argument([{Flag,_}|Flags],Flag,Value) -> [{Flag,[Value]}|Flags]; set_argument([Item|Flags],Flag,Value) -> [Item|set_argument(Flags,Flag,Value)]; set_argument([],Flag,Value) -> [{Flag,[Value]}]. append([E]) -> E; append([H|T]) -> H ++ append(T); append([]) -> []. reverse([] = L) -> L; reverse([_] = L) -> L; reverse([A, B]) -> [B, A]; reverse([A, B | L]) -> lists:reverse(L, [B, A]). % BIF -spec objfile_extension() -> nonempty_string(). objfile_extension() -> ".beam". %% case erlang:system_info(machine) of %% "JAM" -> ".jam"; %% "VEE" -> ".vee"; %% "BEAM" -> ".beam" %% end. -spec archive_extension() -> nonempty_string(). archive_extension() -> ".ez". %%% %%% Support for handling of on_load functions. %%% run_on_load_handlers() -> Ref = monitor(process, ?ON_LOAD_HANDLER), catch ?ON_LOAD_HANDLER ! run_on_load, receive {'DOWN',Ref,process,_,noproc} -> %% There is no on_load handler process, %% probably because init:restart/0 has been %% called and it is not the first time we %% pass through here. ok; {'DOWN',Ref,process,_,on_load_done} -> ok; {'DOWN',Ref,process,_,Res} -> %% Failure to run an on_load handler. %% This is fatal during start-up. exit(Res) end. start_on_load_handler_process() -> register(?ON_LOAD_HANDLER, spawn(fun on_load_handler_init/0)). on_load_handler_init() -> on_load_loop([], false). on_load_loop(Mods, Debug0) -> receive {init_debug_flag,Debug} -> on_load_loop(Mods, Debug); {loaded,Mod} -> on_load_loop([Mod|Mods], Debug0); run_on_load -> run_on_load_handlers(Mods, Debug0), exit(on_load_done) end. run_on_load_handlers([M|Ms], Debug) -> debug(Debug, {running_on_load_handler,M}), Fun = fun() -> Res = erlang:call_on_load_function(M), exit(Res) end, {Pid,Ref} = spawn_monitor(Fun), receive {'DOWN',Ref,process,Pid,OnLoadRes} -> Keep = OnLoadRes =:= ok, erlang:finish_after_on_load(M, Keep), case Keep of false -> Error = {on_load_function_failed,M}, debug(Debug, Error), exit(Error); true -> debug(Debug, {on_load_handler_returned_ok,M}), run_on_load_handlers(Ms, Debug) end end; run_on_load_handlers([], _) -> ok. %% debug profile (light variant of eprof) debug_profile_start() -> _ = erlang:trace_pattern({'_','_','_'},true,[call_time]), _ = erlang:trace_pattern(on_load,true,[call_time]), _ = erlang:trace(all,true,[call]), ok. debug_profile_stop() -> _ = erlang:trace_pattern({'_','_','_'},false,[call_time]), _ = erlang:trace_pattern(on_load,false,[call_time]), _ = erlang:trace(all,false,[call]), ok. debug_profile_mfas() -> _ = erlang:trace_pattern({'_','_','_'},pause,[call_time]), _ = erlang:trace_pattern(on_load,pause,[call_time]), MFAs = collect_loaded_mfas() ++ erlang:system_info(snifs), collect_mfas(MFAs,[]). %% debug_profile_format_mfas should be called at the end of the boot phase %% so all pertinent modules should be loaded at that point. debug_profile_format_mfas(MFAs0) -> MFAs = lists:sort(MFAs0), lists:foreach(fun({{Us,C},{M,F,A}}) -> Str = io_lib:format("~w:~w/~w", [M,F,A]), io:format(standard_error,"~55s - ~6w : ~w us~n", [Str,C,Us]) end, MFAs), ok. collect_loaded_mfas() -> Ms = [M || M <- [element(1, Mi) || Mi <- code:all_loaded()]], collect_loaded_mfas(Ms,[]). collect_loaded_mfas([],MFAs) -> MFAs; collect_loaded_mfas([M|Ms],MFAs0) -> MFAs = [{M,F,A} || {F,A} <- M:module_info(functions)], collect_loaded_mfas(Ms,MFAs ++ MFAs0). collect_mfas([], Info) -> Info; collect_mfas([MFA|MFAs],Info) -> case erlang:trace_info(MFA,call_time) of {call_time, []} -> collect_mfas(MFAs,Info); {call_time, false} -> collect_mfas(MFAs,Info); {call_time, Data} -> case collect_mfa(MFA,Data,0,0) of {{0,_},_} -> %% ignore mfas with zero time collect_mfas(MFAs,Info); MfaData -> collect_mfas(MFAs,[MfaData|Info]) end end. collect_mfa(Mfa,[],Count,Time) -> {{Time,Count},Mfa}; collect_mfa(Mfa,[{_Pid,C,S,Us}|Data],Count,Time) -> collect_mfa(Mfa,Data,Count + C,Time + S * 1000000 + Us).