diff options
Diffstat (limited to 'lib/sasl/src')
-rw-r--r-- | lib/sasl/src/release_handler.erl | 488 | ||||
-rw-r--r-- | lib/sasl/src/release_handler_1.erl | 308 | ||||
-rw-r--r-- | lib/sasl/src/sasl.appup.src | 26 | ||||
-rw-r--r-- | lib/sasl/src/systools_lib.erl | 7 | ||||
-rw-r--r-- | lib/sasl/src/systools_make.erl | 324 | ||||
-rw-r--r-- | lib/sasl/src/systools_rc.erl | 40 | ||||
-rw-r--r-- | lib/sasl/src/systools_relup.erl | 221 |
7 files changed, 1076 insertions, 338 deletions
diff --git a/lib/sasl/src/release_handler.erl b/lib/sasl/src/release_handler.erl index ab54b1d00b..4e8cb4628c 100644 --- a/lib/sasl/src/release_handler.erl +++ b/lib/sasl/src/release_handler.erl @@ -25,9 +25,10 @@ -export([start_link/0, create_RELEASES/1, create_RELEASES/2, create_RELEASES/4, unpack_release/1, - check_install_release/1, install_release/1, install_release/2, - remove_release/1, - which_releases/0, make_permanent/1, reboot_old_release/1, + check_install_release/1, check_install_release/2, + install_release/1, install_release/2, new_emulator_upgrade/2, + remove_release/1, which_releases/0, which_releases/1, + make_permanent/1, reboot_old_release/1, set_unpacked/2, set_removed/1, install_file/2]). -export([upgrade_app/2, downgrade_app/2, downgrade_app/3, upgrade_script/2, downgrade_script/3, @@ -40,7 +41,7 @@ %% Internal exports, a client release_handler may call this functions. -export([do_write_release/3, do_copy_file/2, do_copy_files/2, do_copy_files/1, do_rename_files/1, do_remove_files/1, - do_write_file/2, do_ensure_RELEASES/1]). + remove_file/1, do_write_file/2, do_ensure_RELEASES/1]). -record(state, {unpurged = [], root, @@ -61,10 +62,11 @@ %% remove - %% current make_permanent permanent %% install other old +%% restart node unpacked %% remove - %% permanent make other permanent old %% install permanent -%% old reboot permanen +%% old reboot_old permanent %% install current %% remove - %%----------------------------------------------------------------- @@ -74,6 +76,14 @@ -define(timeout, 10000). %%----------------------------------------------------------------- +%% The version set on the temporary release that will be used when the +%% emulator is upgraded. +-define(tmp_vsn(__BaseVsn__), "__new_emulator__"++__BaseVsn__). + + + + +%%----------------------------------------------------------------- %% Assumes the following file structure: %% root --- lib --- Appl-Vsn1 --- <src> %% | | |- ebin @@ -149,25 +159,49 @@ unpack_release(ReleaseName) -> %%----------------------------------------------------------------- %% Purpose: Checks the relup script for the specified version. %% The release must be unpacked. +%% Options = [purge] - all old code that can be soft purged +%% will be purged if all checks succeeds. This can be usefull +%% in order to reduce time needed in the following call to +%% install_release. %% Returns: {ok, FromVsn, Descr} | {error, Reason} -%% Reason = {already_installed, Vsn} | +%% Reason = {illegal_option, IllegalOpt} | +%% {already_installed, Vsn} | %% {bad_relup_file, RelFile} | %% {no_such_release, Vsn} | %% {no_such_from_vsn, Vsn} | %% exit_reason() %%----------------------------------------------------------------- check_install_release(Vsn) -> - call({check_install_release, Vsn}). + check_install_release(Vsn, []). + +check_install_release(Vsn, Opts) -> + case check_check_install_options(Opts, false) of + {ok,Purge} -> + call({check_install_release, Vsn, Purge}); + Error -> + Error + end. + +check_check_install_options([purge|Opts], _) -> + check_check_install_options(Opts, true); +check_check_install_options([Illegal|_],_Purge) -> + {error,{illegal_option,Illegal}}; +check_check_install_options([],Purge) -> + {ok,Purge}. %%----------------------------------------------------------------- %% Purpose: Executes the relup script for the specified version. %% The release must be unpacked. -%% Returns: {ok, FromVsn, Descr} | {error, Reason} +%% Returns: {ok, FromVsn, Descr} | +%% {continue_after_restart, FromVsn, Descr} | +%% {error, Reason} %% Reason = {already_installed, Vsn} | %% {bad_relup_file, RelFile} | %% {no_such_release, Vsn} | %% {no_such_from_vsn, Vsn} | +%% {could_not_create_hybrid_boot,Why} | +%% {missing_base_app,Vsn,App} | %% {illegal_option, Opt}} | %% exit_reason() %%----------------------------------------------------------------- @@ -210,6 +244,21 @@ check_timeout(Int) when is_integer(Int), Int > 0 -> true; check_timeout(_Else) -> false. %%----------------------------------------------------------------- +%% Purpose: Called by boot script after emulator is restarted due to +%% new erts version. +%% Returns: Same as install_release/2 +%% If this crashes, the emulator restart will fail +%% (since the function is called from the boot script) +%% and there will be a rollback. +%%----------------------------------------------------------------- +new_emulator_upgrade(Vsn, Opts) -> + Result = call({install_release, Vsn, reboot, Opts}), + error_logger:info_msg( + "~p:install_release(~p,~p) completed after node restart " + "with new emulator version~nResult: ~p~n",[?MODULE,Vsn,Opts,Result]), + Result. + +%%----------------------------------------------------------------- %% Purpose: Makes the specified release version be the one that is %% used when the system starts (or restarts). %% The release must be installed (not unpacked). @@ -285,6 +334,14 @@ which_releases() -> call(which_releases). %%----------------------------------------------------------------- +%% Returns: [{Name, Vsn, [LibName], Status}] +%% Status = unpacked | current | permanent | old +%%----------------------------------------------------------------- +which_releases(Status) -> + Releases = which_releases(), + get_releases_with_status(Releases, Status, []). + +%%----------------------------------------------------------------- %% check_script(Script, LibDirs) -> ok | {error, Reason} %%----------------------------------------------------------------- check_script(Script, LibDirs) -> @@ -293,7 +350,7 @@ check_script(Script, LibDirs) -> %%----------------------------------------------------------------- %% eval_script(Script, Apps, LibDirs, NewLibs, Opts) -> %% {ok, UnPurged} | -%% restart_new_emulator | +%% restart_emulator | %% {error, Error} %% {'EXIT', Reason} %% If sync_nodes is present, the calling process must have called @@ -330,7 +387,7 @@ create_RELEASES(Root, RelDir, RelFile, LibDirs) -> %%----------------------------------------------------------------- %% Func: upgrade_app(App, Dir) -> {ok, Unpurged} -%% | restart_new_emulator +%% | restart_emulator %% | {error, Error} %% Types: %% App = atom() @@ -350,7 +407,7 @@ upgrade_app(App, NewDir) -> %%----------------------------------------------------------------- %% Func: downgrade_app(App, Dir) %% downgrade_app(App, Vsn, Dir) -> {ok, Unpurged} -%% | restart_new_emulator +%% | restart_emulator %% | {error, Error} %% Types: %% App = atom() @@ -541,11 +598,12 @@ handle_call({unpack_release, ReleaseName}, _From, S) handle_call({unpack_release, _ReleaseName}, _From, S) -> {reply, {error, client_node}, S}; -handle_call({check_install_release, Vsn}, _From, S) -> +handle_call({check_install_release, Vsn, Purge}, _From, S) -> case catch do_check_install_release(S#state.rel_dir, Vsn, S#state.releases, - S#state.masters) of + S#state.masters, + Purge) of {ok, CurrentVsn, Descr} -> {reply, {ok, CurrentVsn, Descr}, S}; {error, Reason} -> @@ -557,7 +615,7 @@ handle_call({check_install_release, Vsn}, _From, S) -> handle_call({install_release, Vsn, ErrorAction, Opts}, From, S) -> NS = resend_sync_nodes(S), case catch do_install_release(S, Vsn, Opts) of - {ok, NewReleases, CurrentVsn, Descr} -> + {ok, NewReleases, [], CurrentVsn, Descr} -> {reply, {ok, CurrentVsn, Descr}, NS#state{releases=NewReleases}}; {ok, NewReleases, Unpurged, CurrentVsn, Descr} -> Timer = @@ -572,10 +630,14 @@ handle_call({install_release, Vsn, ErrorAction, Opts}, From, S) -> {reply, {ok, CurrentVsn, Descr}, NewS}; {error, Reason} -> {reply, {error, Reason}, NS}; - {restart_new_emulator, CurrentVsn, Descr} -> + {restart_emulator, CurrentVsn, Descr} -> gen_server:reply(From, {ok, CurrentVsn, Descr}), init:reboot(), {noreply, NS}; + {restart_new_emulator, CurrentVsn, Descr} -> + gen_server:reply(From, {continue_after_restart, CurrentVsn, Descr}), + init:reboot(), + {noreply, NS}; {'EXIT', Reason} -> io:format("release_handler:" "install_release(Vsn=~p Opts=~p) failed, " @@ -855,7 +917,7 @@ check_path_response(Path, {ok, _Info}) -> check_path_response(Path, {error, _Reason}) -> throw({error, {no_such_directory, Path}}). -do_check_install_release(RelDir, Vsn, Releases, Masters) -> +do_check_install_release(RelDir, Vsn, Releases, Masters, Purge) -> case lists:keysearch(Vsn, #release.vsn, Releases) of {value, #release{status = current}} -> {error, {already_installed, Vsn}}; @@ -880,7 +942,20 @@ do_check_install_release(RelDir, Vsn, Releases, Masters) -> case get_rh_script(LatestRelease, Release, RelDir, Masters) of {ok, {CurrentVsn, Descr, Script}} -> case catch check_script(Script, Libs) of - ok -> + {ok,SoftPurgeMods} when Purge=:=true -> + %% Get modules with brutal_purge + %% instructions, but that can be + %% soft purged + {ok,BrutalPurgeMods} = + release_handler_1:check_old_processes( + Script,brutal_purge), + lists:foreach( + fun(Mod) -> + catch erlang:purge_module(Mod) + end, + SoftPurgeMods ++ BrutalPurgeMods), + {ok, CurrentVsn, Descr}; + {ok,_} -> {ok, CurrentVsn, Descr}; Else -> Else @@ -907,7 +982,38 @@ do_install_release(#state{start_prg = StartPrg, {value, Release} -> LatestRelease = get_latest_release(Releases), case get_rh_script(LatestRelease, Release, RelDir, Masters) of + {ok, {_CurrentVsn, _Descr, [restart_new_emulator|_Script]}} + when Static == true -> + throw(static_emulator); + {ok, {CurrentVsn, Descr, [restart_new_emulator|_Script]}} -> + %% This will only happen if the upgrade includes + %% an emulator upgrade (and it is not a downgrade) + %% - then the new emulator must be started before + %% new code can be loaded. + %% Create a temporary release which includes new + %% emulator, kernel, stdlib and sasl - and old + %% versions of other applications. + {TmpVsn,TmpRelease} = + new_emulator_make_tmp_release(LatestRelease,Release, + RelDir,Opts,Masters), + NReleases = [TmpRelease|Releases], + + %% Then uppgrade to the temporary release. + %% The rest of the upgrade will continue after the restart + prepare_restart_new_emulator(StartPrg, RootDir, + RelDir, TmpVsn, TmpRelease, + NReleases, Masters), + {restart_new_emulator, CurrentVsn, Descr}; {ok, {CurrentVsn, Descr, Script}} -> + %% In case there has been an emulator upgrade, + %% remove the temporary release + NReleases = + new_emulator_rm_tmp_release( + LatestRelease#release.vsn, + LatestRelease#release.erts_vsn, + Vsn,RelDir,Releases,Masters), + + %% Then execute the relup script mon_nodes(true), EnvBefore = application_controller:prep_config_change(), Apps = change_appl_data(RelDir, Release, Masters), @@ -915,31 +1021,19 @@ do_install_release(#state{start_prg = StartPrg, NewLibs = get_new_libs(LatestRelease#release.libs, Release#release.libs), case eval_script(Script, Apps, LibDirs, NewLibs, Opts) of - {ok, []} -> - application_controller:config_change(EnvBefore), - mon_nodes(false), - NewReleases = set_status(Vsn, current, Releases), - {ok, NewReleases, CurrentVsn, Descr}; {ok, Unpurged} -> application_controller:config_change(EnvBefore), mon_nodes(false), - NewReleases = set_status(Vsn, current, Releases), - {ok, NewReleases, Unpurged, CurrentVsn, Descr}; - restart_new_emulator when Static == true -> + NReleases1 = set_status(Vsn, current, NReleases), + {ok, NReleases1, Unpurged, CurrentVsn, Descr}; + restart_emulator when Static == true -> throw(static_emulator); - restart_new_emulator -> + restart_emulator -> mon_nodes(false), - {value, PermanentRelease} = - lists:keysearch(permanent, #release.status, - Releases), - NReleases = set_status(Vsn, current, Releases), - NReleases2 = set_status(Vsn,tmp_current,NReleases), - write_releases(RelDir, NReleases2, Masters), prepare_restart_new_emulator(StartPrg, RootDir, - RelDir, Release, - PermanentRelease, - Masters), - {restart_new_emulator, CurrentVsn, Descr}; + RelDir, Vsn, Release, + NReleases, Masters), + {restart_emulator, CurrentVsn, Descr}; Else -> application_controller:config_change(EnvBefore), mon_nodes(false), @@ -952,6 +1046,145 @@ do_install_release(#state{start_prg = StartPrg, {error, {no_such_release, Vsn}} end. +new_emulator_make_tmp_release(CurrentRelease,ToRelease,RelDir,Opts,Masters) -> + CurrentVsn = CurrentRelease#release.vsn, + ToVsn = ToRelease#release.vsn, + TmpVsn = ?tmp_vsn(CurrentVsn), + BaseApps = [kernel,stdlib,sasl], + BaseLibs = [{App,Vsn,Lib} || {App,Vsn,Lib} <- ToRelease#release.libs, + lists:member(App,BaseApps)], + check_base_libs(BaseLibs,ToVsn), + OldBaseLibs = [{App,Vsn,Lib} || {App,Vsn,Lib} <- CurrentRelease#release.libs, + lists:member(App,BaseApps)], + check_base_libs(OldBaseLibs,CurrentVsn), + RestLibs = [{App,Vsn,Lib} || {App,Vsn,Lib} <- CurrentRelease#release.libs, + not lists:member(App,BaseApps)], + TmpRelease = CurrentRelease#release{vsn=TmpVsn, + erts_vsn=ToRelease#release.erts_vsn, + libs = BaseLibs ++ RestLibs, + status = unpacked}, + new_emulator_make_hybrid_boot(CurrentVsn,ToVsn,TmpVsn,BaseLibs, + RelDir,Opts,Masters), + new_emulator_make_hybrid_config(CurrentVsn,ToVsn,TmpVsn,RelDir,Masters), + {TmpVsn,TmpRelease}. + +check_base_libs([_,_,_]=BaseLibs,_Vsn) -> + [Kernel,Sasl,Stdlib] = lists:keysort(1,BaseLibs), + [Kernel,Stdlib,Sasl]; +check_base_libs(SomeMissing,Vsn) -> + find_missing(SomeMissing,[kernel,stdlib,sasl],Vsn). + +find_missing(SomeMissing,[H|T],Vsn) -> + case lists:keymember(H,1,SomeMissing) of + true -> + find_missing(SomeMissing,T,Vsn); + false -> + throw({error,{missing_base_app,Vsn,H}}) + end. + +new_emulator_make_hybrid_boot(CurrentVsn,ToVsn,TmpVsn,BaseLibs,RelDir,Opts,Masters) -> + FromBootFile = filename:join([RelDir,CurrentVsn,"start.boot"]), + ToBootFile = filename:join([RelDir,ToVsn,"start.boot"]), + TmpBootFile = filename:join([RelDir,TmpVsn,"start.boot"]), + ensure_dir(TmpBootFile,Masters), + Args = [ToVsn,Opts], + {ok,FromBoot} = read_file(FromBootFile,Masters), + {ok,ToBoot} = read_file(ToBootFile,Masters), + [KernelPath,SaslPath,StdlibPath] = + [filename:join(Path,ebin) || {_,_,Path} <- lists:keysort(1,BaseLibs)], + Paths = {KernelPath,StdlibPath,SaslPath}, + case systools_make:make_hybrid_boot(TmpVsn,FromBoot,ToBoot,Paths,Args) of + {ok,TmpBoot} -> + write_file(TmpBootFile,TmpBoot,Masters); + {error,Reason} -> + throw({error,{could_not_create_hybrid_boot,Reason}}) + end. + +new_emulator_make_hybrid_config(CurrentVsn,ToVsn,TmpVsn,RelDir,Masters) -> + FromFile = filename:join([RelDir,CurrentVsn,"sys.config"]), + ToFile = filename:join([RelDir,ToVsn,"sys.config"]), + TmpFile = filename:join([RelDir,TmpVsn,"sys.config"]), + + FromConfig = + case consult(FromFile,Masters) of + {ok,[FC]} -> + FC; + {error,Error1} -> + io:format("Warning: ~p can not read ~p: ~p~n", + [?MODULE,FromFile,Error1]), + [] + end, + + [Kernel,Stdlib,Sasl] = + case consult(ToFile,Masters) of + {ok,[ToConfig]} -> + [lists:keyfind(App,1,ToConfig) || App <- [kernel,stdlib,sasl]]; + {error,Error2} -> + io:format("Warning: ~p can not read ~p: ~p~n", + [?MODULE,ToFile,Error2]), + [false,false,false] + end, + + Config1 = replace_config(kernel,FromConfig,Kernel), + Config2 = replace_config(stdlib,Config1,Stdlib), + Config3 = replace_config(sasl,Config2,Sasl), + + ConfigStr = io_lib:format("~p.~n",[Config3]), + write_file(TmpFile,ConfigStr,Masters). + +%% Take the configuration for application App from the new config and +%% insert in the old config. +%% If no entry exists in the new config, then delete the entry (if it exists) +%% from the old config. +%% If entry exists in the new config, but not in the old config, then +%% add the entry. +replace_config(App,Config,false) -> + lists:keydelete(App,1,Config); +replace_config(App,Config,AppConfig) -> + lists:keystore(App,1,Config,AppConfig). + +%% Remove all files related to the temporary release +new_emulator_rm_tmp_release(?tmp_vsn(_)=TmpVsn,EVsn,NewVsn, + RelDir,Releases,Masters) -> + case os:type() of + {win32, nt} -> + rename_tmp_service(EVsn,TmpVsn,NewVsn); + _ -> + ok + end, + remove_dir(filename:join(RelDir,TmpVsn),Masters), + lists:keydelete(TmpVsn,#release.vsn,Releases); +new_emulator_rm_tmp_release(_,_,_,_,Releases,_) -> + Releases. + +%% Rename the tempoarary service (for erts ugprade) to the real ToVsn +rename_tmp_service(EVsn,TmpVsn,NewVsn) -> + FromName = hd(string:tokens(atom_to_list(node()),"@")) ++ "_" ++ TmpVsn, + ToName = hd(string:tokens(atom_to_list(node()),"@")) ++ "_" ++ NewVsn, + case erlsrv:get_service(EVsn,ToName) of + {error, _Error} -> + ok; + _Data -> + erlsrv:remove_service(ToName) + end, + rename_service(EVsn,FromName,ToName). + + +%% Rename a service and check that it succeeded +rename_service(EVsn,FromName,ToName) -> + case erlsrv:rename_service(EVsn,FromName,ToName) of + {ok,_} -> + case erlsrv:get_service(EVsn,ToName) of + {error,Error1} -> + throw({error,Error1}); + _Data2 -> + ok + end; + Error2 -> + throw({error,{service_rename_failed, Error2}}) + end. + + %%% This code chunk updates the services in one of two ways, %%% Either the emulator is restarted, in which case the old service %%% is to be removed and the new enabled, or the emulator is NOT restarted @@ -968,26 +1201,16 @@ do_make_services_permanent(PermanentVsn,Vsn, PermanentEVsn, EVsn) -> %% rename. case os:getenv("ERLSRV_SERVICE_NAME") == PermName of true -> - case erlsrv:rename_service(EVsn,PermName,Name) of - {ok,_} -> - case erlsrv:get_service(EVsn,Name) of - {error,Error2} -> - throw({error,Error2}); - _Data2 -> - %% The interfaces for doing this are - %% NOT published and may be subject to - %% change. Do NOT do this anywhere else! - - os:putenv("ERLSRV_SERVICE_NAME", Name), - - %% Restart heart port program, this - %% function is only to be used here. - heart:cycle(), - ok - end; - Error3 -> - throw({error,{service_rename_failed, Error3}}) - end; + rename_service(EVsn,PermName,Name), + %% The interfaces for doing this are + %% NOT published and may be subject to + %% change. Do NOT do this anywhere else! + + os:putenv("ERLSRV_SERVICE_NAME", Name), + + %% Restart heart port program, this + %% function is only to be used here. + heart:cycle(); false -> throw({error,service_name_missmatch}) end; @@ -1226,21 +1449,31 @@ do_set_removed(RelDir, Vsn, Releases, Masters) -> %% corresponding relup instructions, we check if it's possible to %% downgrade from CurrentVsn to ToVsn. %%----------------------------------------------------------------- +get_rh_script(#release{vsn = ?tmp_vsn(CurrentVsn)}, + #release{vsn = ToVsn}, + RelDir, + Masters) -> + {ok,{Vsn,Descr,[restart_new_emulator|Script]}} = + do_get_rh_script(CurrentVsn,ToVsn,RelDir,Masters), + {ok,{Vsn,Descr,Script}}; get_rh_script(#release{vsn = CurrentVsn}, - #release{vsn = Vsn}, + #release{vsn = ToVsn}, RelDir, Masters) -> - Relup = filename:join([RelDir, Vsn, "relup"]), - case try_upgrade(Vsn, CurrentVsn, Relup, Masters) of + do_get_rh_script(CurrentVsn,ToVsn,RelDir,Masters). + +do_get_rh_script(CurrentVsn, ToVsn, RelDir, Masters) -> + Relup = filename:join([RelDir, ToVsn, "relup"]), + case try_upgrade(ToVsn, CurrentVsn, Relup, Masters) of {ok, RhScript} -> {ok, RhScript}; _ -> Relup2 = filename:join([RelDir, CurrentVsn,"relup"]), - case try_downgrade(Vsn, CurrentVsn, Relup2, Masters) of + case try_downgrade(ToVsn, CurrentVsn, Relup2, Masters) of {ok, RhScript} -> {ok, RhScript}; _ -> - throw({error, {no_matching_relup, Vsn, CurrentVsn}}) + throw({error, {no_matching_relup, ToVsn, CurrentVsn}}) end end. @@ -1453,6 +1686,15 @@ prepare_restart_nt(#release{erts_vsn = EVsn, vsn = Vsn}, %% restart is performed by calling init:reboot() higher up. %%----------------------------------------------------------------- prepare_restart_new_emulator(StartPrg, RootDir, RelDir, + Vsn, Release, Releases, Masters) -> + {value, PRelease} = lists:keysearch(permanent, #release.status,Releases), + NReleases1 = set_status(Vsn, current, Releases), + NReleases2 = set_status(Vsn,tmp_current,NReleases1), + write_releases(RelDir, NReleases2, Masters), + prepare_restart_new_emulator(StartPrg, RootDir, RelDir, + Release, PRelease, Masters). + +prepare_restart_new_emulator(StartPrg, RootDir, RelDir, Release, PRelease, Masters) -> #release{erts_vsn = EVsn, vsn = Vsn} = Release, Data = EVsn ++ " " ++ Vsn, @@ -1478,19 +1720,10 @@ check_start_prg({do_check, StartPrg}, Masters) -> check_start_prg({_, StartPrg}, _) -> StartPrg. -write_new_start_erl(Data, RelDir, false) -> - DataFile = filename:join([RelDir, "new_start_erl.data"]), - case do_write_file(DataFile, Data) of - ok -> DataFile; - Error -> throw(Error) - end; write_new_start_erl(Data, RelDir, Masters) -> DataFile = filename:join([RelDir, "new_start_erl.data"]), - case at_all_masters(Masters, ?MODULE, do_write_file, - [DataFile, Data]) of - ok -> DataFile; - Error -> throw(Error) - end. + write_file(DataFile, Data, Masters), + DataFile. %%----------------------------------------------------------------- %% When a new emulator shall be restarted, the current release @@ -1504,27 +1737,41 @@ write_new_start_erl(Data, RelDir, Masters) -> %% If the release is made permanent, this is written to disk. %%----------------------------------------------------------------- transform_release(ReleaseDir, Releases, Masters) -> - F = fun(Release) when Release#release.status == tmp_current -> - Release#release{status = unpacked}; - (Release) -> Release - end, - case lists:map(F, Releases) of - Releases -> - Releases; - DReleases -> + case init:script_id() of + {Name, ?tmp_vsn(_)=TmpVsn} -> + %% This is was a reboot due to a new emulator version. The + %% current release is a temporary internal release, which + %% must be removed. It is the "real new release" that is + %% set to unpacked on disk and current in memory. + DReleases = lists:keydelete(TmpVsn,#release.vsn,Releases), write_releases(ReleaseDir, DReleases, Masters), - F1 = fun(Release) when Release#release.status == tmp_current -> - case init:script_id() of - {_Name, Vsn} when Release#release.vsn == Vsn -> - Release#release{status = current}; - _ -> - Release#release{status = unpacked} - end; - (Release) -> Release - end, - lists:map(F1, Releases) + set_current({Name,TmpVsn},Releases); + ScriptId -> + F = fun(Release) when Release#release.status == tmp_current -> + Release#release{status = unpacked}; + (Release) -> Release + end, + case lists:map(F, Releases) of + Releases -> + Releases; + DReleases -> + write_releases(ReleaseDir, DReleases, Masters), + set_current(ScriptId, Releases) + end end. +set_current(ScriptId, Releases) -> + F1 = fun(Release) when Release#release.status == tmp_current -> + case ScriptId of + {_Name,Vsn} when Release#release.vsn == Vsn -> + Release#release{status = current}; + _ -> + Release#release{status = unpacked} + end; + (Release) -> Release + end, + lists:map(F1, Releases). + %%----------------------------------------------------------------- %% Functions handling files, RELEASES, start_erl.data etc. %% This functions consider if the release_handler is a client and @@ -1583,12 +1830,25 @@ extract_tar(Root, Tar) -> throw({error, {cannot_extract_file, Name, Reason}}) end. -write_releases(Dir, NewReleases, false) -> +write_releases(Dir, Releases, Masters) -> + %% We must never write 'current' to disk, since this will confuse + %% us after a node restart - since we would then have a permanent + %% release running, but state set to current for a non-running + %% release. + NewReleases = lists:zf(fun(Release) when Release#release.status == current -> + {true, Release#release{status = unpacked}}; + (_) -> + true + end, Releases), + write_releases_1(Dir, NewReleases, Masters). + + +write_releases_1(Dir, NewReleases, false) -> case do_write_release(Dir, "RELEASES", NewReleases) of ok -> ok; Error -> throw(Error) end; -write_releases(Dir, NewReleases, Masters) -> +write_releases_1(Dir, NewReleases, Masters) -> all_masters(Masters), write_releases_m(Dir, NewReleases, Masters). @@ -1810,6 +2070,37 @@ read_file(File, false) -> read_file(File, Masters) -> read_master(Masters, File). +write_file(File, Data, false) -> + case file:write_file(File, Data) of + ok -> ok; + Error -> throw(Error) + end; +write_file(File, Data, Masters) -> + case at_all_masters(Masters, file, write_file, [File, Data]) of + ok -> ok; + Error -> throw(Error) + end. + +ensure_dir(File, false) -> + case filelib:ensure_dir(File) of + ok -> ok; + Error -> throw(Error) + end; +ensure_dir(File, Masters) -> + case at_all_masters(Masters,filelib,ensure_dir,[File]) of + ok -> ok; + Error -> throw(Error) + end. + +remove_dir(Dir, false) -> + remove_file(Dir); +remove_dir(Dir, Masters) -> + case at_all_masters(Masters,?MODULE,remove_file,[Dir]) of + ok -> ok; + Error -> throw(Error) + end. + + %% Ignore status of each delete ! remove_files(Master, Files, Masters) -> takewhile(Master, Masters, ?MODULE, do_remove_files, [Files]). @@ -1967,7 +2258,7 @@ safe_write_file_m(File, Data, Masters) -> %% 'update_paths' option to release_handler:install_release/2 if the %% code path shall be updated then. %% ----------------------------------------------------------------- -get_new_libs([{App,Vsn,LibDir}|CurrentLibs], NewLibs) -> +get_new_libs([{App,Vsn,_LibDir}|CurrentLibs], NewLibs) -> case lists:keyfind(App,1,NewLibs) of {App,NewVsn,_} = LibInfo when NewVsn =/= Vsn -> [LibInfo | get_new_libs(CurrentLibs,NewLibs)]; @@ -1976,3 +2267,14 @@ get_new_libs([{App,Vsn,LibDir}|CurrentLibs], NewLibs) -> end; get_new_libs([],_) -> []. + +%%----------------------------------------------------------------- +%% Return a list of releases witch a specific status +%%----------------------------------------------------------------- +get_releases_with_status([], _, Acc) -> + Acc; +get_releases_with_status([ {_, _, _, ReleaseStatus } = Head | Tail], + Status, Acc) when ReleaseStatus == Status -> + get_releases_with_status(Tail, Status, [Head | Acc]); +get_releases_with_status([_ | Tail], Status, Acc) -> + get_releases_with_status(Tail, Status, Acc). diff --git a/lib/sasl/src/release_handler_1.erl b/lib/sasl/src/release_handler_1.erl index ff62f847ac..b4b288646f 100644 --- a/lib/sasl/src/release_handler_1.erl +++ b/lib/sasl/src/release_handler_1.erl @@ -19,8 +19,9 @@ -module(release_handler_1). %% External exports --export([eval_script/1, eval_script/5, check_script/2]). --export([get_current_vsn/1]). %% exported because used in a test case +-export([eval_script/1, eval_script/5, + check_script/2, check_old_processes/2]). +-export([get_current_vsn/1, get_supervised_procs/0]). %% exported because used in a test case -record(eval_state, {bins = [], stopped = [], suspended = [], apps = [], libdirs, unpurged = [], vsns = [], newlibs = [], @@ -46,30 +47,45 @@ %%%----------------------------------------------------------------- %%% This is a low-level release handler. %%%----------------------------------------------------------------- +check_script([restart_new_emulator|Script], LibDirs) -> + %% There is no need to check for old processes, since the node + %% will be restarted before anything else happens. + do_check_script(Script, LibDirs, []); check_script(Script, LibDirs) -> - case catch check_old_processes(Script) of - ok -> - {Before, _After} = split_instructions(Script), - case catch lists:foldl(fun(Instruction, EvalState1) -> - eval(Instruction, EvalState1) - end, - #eval_state{libdirs = LibDirs}, - Before) of - EvalState2 when is_record(EvalState2, eval_state) -> ok; - {error, Error} -> {error, Error}; - Other -> {error, Other} - end; + case catch check_old_processes(Script,soft_purge) of + {ok, PurgeMods} -> + do_check_script(Script, LibDirs, PurgeMods); {error, Mod} -> {error, {old_processes, Mod}} end. +do_check_script(Script, LibDirs, PurgeMods) -> + {Before, After} = split_instructions(Script), + case catch lists:foldl(fun(Instruction, EvalState1) -> + eval(Instruction, EvalState1) + end, + #eval_state{libdirs = LibDirs}, + Before) of + EvalState2 when is_record(EvalState2, eval_state) -> + case catch syntax_check_script(After) of + ok -> + {ok,PurgeMods}; + Other -> + {error,Other} + end; + {error, Error} -> + {error, Error}; + Other -> + {error, Other} + end. + %% eval_script/1 - For testing only - no apps added, just testing instructions eval_script(Script) -> eval_script(Script, [], [], [], []). eval_script(Script, Apps, LibDirs, NewLibs, Opts) -> - case catch check_old_processes(Script) of - ok -> + case catch check_old_processes(Script,soft_purge) of + {ok,_} -> {Before, After} = split_instructions(Script), case catch lists:foldl(fun(Instruction, EvalState1) -> eval(Instruction, EvalState1) @@ -79,22 +95,30 @@ eval_script(Script, Apps, LibDirs, NewLibs, Opts) -> newlibs = NewLibs, opts = Opts}, Before) of - EvalState2 when is_record(EvalState2, eval_state) -> - case catch lists:foldl(fun(Instruction, EvalState3) -> - eval(Instruction, EvalState3) - end, - EvalState2, - After) of - EvalState4 when is_record(EvalState4, eval_state) -> - {ok, EvalState4#eval_state.unpurged}; - restart_new_emulator -> - restart_new_emulator; - Error -> - {'EXIT', Error} - end; - {error, Error} -> {error, Error}; - Other -> {error, Other} - end; + EvalState2 when is_record(EvalState2, eval_state) -> + case catch syntax_check_script(After) of + ok -> + case catch lists:foldl( + fun(Instruction, EvalState3) -> + eval(Instruction, + EvalState3) + end, + EvalState2, + After) of + EvalState4 + when is_record(EvalState4, eval_state) -> + {ok, EvalState4#eval_state.unpurged}; + restart_emulator -> + restart_emulator; + Error -> + {'EXIT', Error} + end; + Other -> + {error,Other} + end; + {error, Error} -> {error, Error}; + Other -> {error, Other} + end; {error, Mod} -> {error, {old_processes, Mod}} end. @@ -112,32 +136,99 @@ split_instructions([], Before) -> {[], lists:reverse(Before)}. %%----------------------------------------------------------------- -%% Func: check_old_processes/1 +%% Func: check_old_processes/2 %% Args: Script = [instruction()] +%% PrePurgeMethod = soft_purge | brutal_purge %% Purpose: Check if there is any process that runs an old version -%% of a module that should be soft_purged, (i.e. not purged -%% at all if there is any such process). Returns {error, Mod} -%% if so, ok otherwise. -%% Returns: ok | {error, Mod} +%% of a module that should be purged according to PrePurgeMethod. +%% Returns a list of modules that can be soft_purged. +%% +%% If PrePurgeMethod == soft_purge, the function will succeed +%% only if there is no process running old code of any of the +%% modules. Else it will throw {error,Mod}, where Mod is the +%% first module found that can not be soft_purged. +%% +%% If PrePurgeMethod == brutal_purge, the function will +%% always succeed and return a list of all modules that are +%% specified in the script with PrePurgeMethod brutal_purge, +%% but that can be soft_purged. +%% +%% Returns: {ok,PurgeMods} | {error, Mod} +%% PurgeMods = [Mod] %% Mod = atom() %%----------------------------------------------------------------- -check_old_processes(Script) -> - lists:foreach(fun({load, {Mod, soft_purge, _PostPurgeMethod}}) -> - check_old_code(Mod); - ({remove, {Mod, soft_purge, _PostPurgeMethod}}) -> - check_old_code(Mod); - (_) -> ok - end, - Script). +check_old_processes(Script,PrePurgeMethod) -> + Procs = erlang:processes(), + {ok,lists:flatmap( + fun({load, {Mod, PPM, _PostPurgeMethod}}) when PPM==PrePurgeMethod -> + check_old_code(Mod,Procs,PrePurgeMethod); + ({remove, {Mod, PPM, _PostPurgeMethod}}) when PPM==PrePurgeMethod -> + check_old_code(Mod,Procs,PrePurgeMethod); + (_) -> [] + end, + Script)}. + +check_old_code(Mod,Procs,PrePurgeMethod) -> + case erlang:check_old_code(Mod) of + true when PrePurgeMethod==soft_purge -> + do_check_old_code(Mod,Procs); + true when PrePurgeMethod==brutal_purge -> + case catch do_check_old_code(Mod,Procs) of + {error,Mod} -> []; + R -> R + end; + false -> + [] + end. + + +do_check_old_code(Mod,Procs) -> + lists:foreach( + fun(Pid) -> + case erlang:check_process_code(Pid, Mod) of + false -> ok; + true -> throw({error, Mod}) + end + end, + Procs), + [Mod]. + + +%% Check the last part of the script, i.e. the part after point_of_no_return. +%% More or less a syntax check in case the script was handwritten. +syntax_check_script([point_of_no_return | Script]) -> + syntax_check_script(Script); +syntax_check_script([{load, {_,_,_}} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{remove, {_,_,_}} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{purge, _} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{suspend, _} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{resume, _} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{code_change, _} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{code_change, _, _} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{stop, _} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{start, _} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{sync_nodes, _, {_,_,_}} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{sync_nodes, _, _} | Script]) -> + syntax_check_script(Script); +syntax_check_script([{apply, {_,_,_}} | Script]) -> + syntax_check_script(Script); +syntax_check_script([restart_emulator | Script]) -> + syntax_check_script(Script); +syntax_check_script([Illegal | _Script]) -> + throw({illegal_instruction_after_point_of_no_return,Illegal}); +syntax_check_script([]) -> + ok. -check_old_code(Mod) -> - lists:foreach(fun(Pid) -> - case erlang:check_process_code(Pid, Mod) of - false -> ok; - true -> throw({error, Mod}) - end - end, - erlang:processes()). %%----------------------------------------------------------------- %% An unpurged module is a module for which there exist an old @@ -208,7 +299,7 @@ check_old_code(Mod) -> %% must also exectue the same line. Waits for all these nodes to get %% to this line. %% point_of_no_return -%% restart_new_emulator +%% restart_emulator %% {stop_application, Appl} - Impl with apply %% {unload_application, Appl} - Impl with {remove..} %% {load_application, Appl} - Impl with {load..} @@ -367,8 +458,8 @@ eval({sync_nodes, Id, Nodes}, EvalState) -> eval({apply, {M, F, A}}, EvalState) -> apply(M, F, A), EvalState; -eval(restart_new_emulator, _EvalState) -> - throw(restart_new_emulator). +eval(restart_emulator, _EvalState) -> + throw(restart_emulator). get_opt(Tag, EvalState, Default) -> case lists:keysearch(Tag, 1, EvalState#eval_state.opts) of @@ -458,6 +549,19 @@ start(Procs) -> %% supervisor module, we should load the new version, and then %% delete the old. Then we should perform the start changes %% manually, by adding/deleting children. +%% +%% Recent changes to this code cause the upgrade error out and +%% log the case where a suspended supervisor has which_children +%% called against it. This retains the behavior of causing a VM +%% restart to the *old* version of a release but has the +%% advantage of logging the pid and supervisor that had the +%% issue. +%% +%% A second case where this can occur is if a child spec is +%% incorrect and get_modules is called against a process that +%% can't respond to the gen:call. Again an error is logged, +%% an error returned and a VM restart is issued. +%% %% Returns: [{SuperPid, ChildName, ChildPid, Mods}] %%----------------------------------------------------------------- %% OTP-3452. For each application the first item contains the pid @@ -467,49 +571,81 @@ start(Procs) -> get_supervised_procs() -> lists:foldl( fun(Application, Procs) -> - case application_controller:get_master(Application) of - Pid when is_pid(Pid) -> - {Root, _AppMod} = application_master:get_child(Pid), - case get_supervisor_module(Root) of - {ok, SupMod} -> - get_procs(supervisor:which_children(Root), - Root) ++ - [{undefined, undefined, Root, [SupMod]} | - Procs]; - {error, _} -> - error_logger:error_msg("release_handler: " - "cannot find top " - "supervisor for " - "application ~w~n", - [Application]), - get_procs(supervisor:which_children(Root), - Root) ++ Procs - end; - _ -> Procs - end + get_master_procs(Application, + Procs, + application_controller:get_master(Application)) end, [], - lists:map(fun({Application, _Name, _Vsn}) -> - Application - end, - application:which_applications())). + get_application_names()). + +get_supervised_procs(_, Root, Procs, {ok, SupMod}) -> + get_procs(maybe_supervisor_which_children(get_proc_state(Root), SupMod, Root), Root) ++ + [{undefined, undefined, Root, [SupMod]} | Procs]; +get_supervised_procs(Application, Root, Procs, {error, _}) -> + error_logger:error_msg("release_handler: cannot find top supervisor for " + "application ~w~n", [Application]), + get_procs(maybe_supervisor_which_children(get_proc_state(Root), Application, Root), Root) ++ Procs. + +get_application_names() -> + lists:map(fun({Application, _Name, _Vsn}) -> + Application + end, + application:which_applications()). + +get_master_procs(Application, Procs, Pid) when is_pid(Pid) -> + {Root, _AppMod} = application_master:get_child(Pid), + get_supervised_procs(Application, Root, Procs, get_supervisor_module(Root)); +get_master_procs(_, Procs, _) -> + Procs. get_procs([{Name, Pid, worker, dynamic} | T], Sup) when is_pid(Pid) -> - Mods = get_dynamic_mods(Pid), + Mods = maybe_get_dynamic_mods(Name, Pid), [{Sup, Name, Pid, Mods} | get_procs(T, Sup)]; get_procs([{Name, Pid, worker, Mods} | T], Sup) when is_pid(Pid), is_list(Mods) -> [{Sup, Name, Pid, Mods} | get_procs(T, Sup)]; get_procs([{Name, Pid, supervisor, Mods} | T], Sup) when is_pid(Pid) -> - [{Sup, Name, Pid, Mods} | get_procs(T, Sup)] ++ - get_procs(supervisor:which_children(Pid), Pid); + [{Sup, Name, Pid, Mods} | get_procs(T, Sup)] ++ + get_procs(maybe_supervisor_which_children(get_proc_state(Pid), Name, Pid), Pid); get_procs([_H | T], Sup) -> get_procs(T, Sup); get_procs(_, _Sup) -> []. -get_dynamic_mods(Pid) -> - {ok,Res} = gen:call(Pid, self(), get_modules), - Res. +get_proc_state(Proc) -> + {status, _, {module, _}, [_, State, _, _, _]} = sys:get_status(Proc), + State. + +maybe_supervisor_which_children(suspended, Name, Pid) -> + error_logger:error_msg("release_handler: a which_children call" + " to ~p (~p) was avoided. This supervisor" + " is suspended and should likely be upgraded" + " differently. Exiting ...~n", [Name, Pid]), + error(suspended_supervisor); + +maybe_supervisor_which_children(State, Name, Pid) -> + case catch supervisor:which_children(Pid) of + Res when is_list(Res) -> + Res; + Other -> + error_logger:error_msg("release_handler: ~p~nerror during" + " a which_children call to ~p (~p)." + " [State: ~p] Exiting ... ~n", + [Other, Name, Pid, State]), + error(which_children_failed) + end. + +maybe_get_dynamic_mods(Name, Pid) -> + case catch gen:call(Pid, self(), get_modules) of + {ok, Res} -> + Res; + Other -> + error_logger:error_msg("release_handler: ~p~nerror during a" + " get_modules call to ~p (~p)," + " there may be an error in it's" + " childspec. Exiting ...~n", + [Other, Name, Pid]), + error(get_modules_failed) + end. %% XXXX %% Note: The following is a terrible hack done in order to resolve the diff --git a/lib/sasl/src/sasl.appup.src b/lib/sasl/src/sasl.appup.src index 64c653a4e5..ce4aa1f8f8 100644 --- a/lib/sasl/src/sasl.appup.src +++ b/lib/sasl/src/sasl.appup.src @@ -1,25 +1,27 @@ -%% +%% -*- erlang -*- %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1999-2011. 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% %% - +%% %CopyrightEnd% {"%VSN%", - [{"2.1.4", [{load_module, release_handler}, - {load_module, systools_relup}]}], - [{"2.1.4", [{load_module, release_handler}, - {load_module, systools_relup}]}] + %% Up from - max two major revisions back + [{<<"2\\.2(\\.[0-9]+)*">>,[restart_new_emulator]}, %% R15 + {<<"2\\.1\\.10(\\.[0-9]+)*">>,[restart_new_emulator]}, %% R14B04 (and later?) + {<<"2\\.1\\.[6-9](\\.[0-9]+)*">>,[restart_new_emulator]}],%% R13B-R14B03 + %% Down to - max two major revisions back + [{<<"2\\.2(\\.[0-9]+)*">>,[restart_new_emulator]}, %% R15 + {<<"2\\.1\\.10(\\.[0-9]+)*">>,[restart_new_emulator]}, %% R14B04 (and later?) + {<<"2\\.1\\.[6-9](\\.[0-9]+)*">>,[restart_new_emulator]}] %% R13B-R14B03 }. diff --git a/lib/sasl/src/systools_lib.erl b/lib/sasl/src/systools_lib.erl index f951647b79..1b6ea125d9 100644 --- a/lib/sasl/src/systools_lib.erl +++ b/lib/sasl/src/systools_lib.erl @@ -24,7 +24,7 @@ %% -export([file_term2binary/2, read_term/1, read_term_from_stream/2, - get_dirs/1, get_path/1]). + get_dirs/1, get_path/1, werror/2]). -include_lib("kernel/include/file.hrl"). @@ -219,6 +219,7 @@ flat([H|T], Ack) -> flat(T, [H|Ack]); flat([], Ack) -> lists:reverse(Ack). - - + +werror(Options, Warnings) -> + lists:member(warnings_as_errors, Options) andalso Warnings =/= []. diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl index 7489ee58d2..ce37f3c2ce 100644 --- a/lib/sasl/src/systools_make.erl +++ b/lib/sasl/src/systools_make.erl @@ -31,6 +31,8 @@ -export([read_application/4]). +-export([make_hybrid_boot/5]). + -import(lists, [filter/2, keysort/2, keysearch/3, map/2, reverse/1, append/1, foldl/3, member/2, foreach/2]). @@ -44,10 +46,12 @@ %%----------------------------------------------------------------- %% Create a boot script from a release file. -%% Options is a list of {path, Path} | silent | local where path sets -%% the search path, silent supresses error message printing on console, -%% local generates a script with references to the directories there -%% the applications are found. +%% Options is a list of {path, Path} | silent | local +%% | warnings_as_errors +%% where path sets the search path, silent supresses error message +%% printing on console, local generates a script with references +%% to the directories there the applications are found, +%% and warnings_as_errors treats warnings as errors. %% %% New options: {path,Path} can contain wildcards %% src_tests @@ -85,11 +89,16 @@ make_script(RelName, Output, Flags) when is_list(RelName), ModTestP = {member(src_tests, Flags),xref_p(Flags)}, case get_release(RelName, Path, ModTestP, machine(Flags)) of {ok, Release, Appls, Warnings} -> - case generate_script(Output,Release,Appls,Flags) of - ok -> + case systools_lib:werror(Flags, Warnings) of + true -> return(ok,Warnings,Flags); - Error -> - return(Error,Warnings,Flags) + false -> + case generate_script(Output,Release,Appls,Flags) of + ok -> + return(ok,Warnings,Flags); + Error -> + return(Error,Warnings,Flags) + end end; Error -> return(Error,[],Flags) @@ -130,10 +139,21 @@ get_outdir(Flags) -> return(ok,Warnings,Flags) -> case member(silent,Flags) of true -> - {ok,?MODULE,Warnings}; + case systools_lib:werror(Flags, Warnings) of + true -> + error; + false -> + {ok,?MODULE,Warnings} + end; _ -> - io:format("~s",[format_warning(Warnings)]), - ok + case member(warnings_as_errors,Flags) of + true -> + io:format("~s",[format_warning(Warnings, true)]), + error; + false -> + io:format("~s",[format_warning(Warnings)]), + ok + end end; return({error,Mod,Error},_,Flags) -> case member(silent,Flags) of @@ -144,6 +164,118 @@ return({error,Mod,Error},_,Flags) -> error end. + +%%----------------------------------------------------------------- +%% Make hybrid boot file for upgrading emulator. The resulting boot +%% file is a combination of the two input files, where kernel, stdlib +%% and sasl versions are taken from the second file (the boot file of +%% the new release), and all other application versions from the first +%% file (the boot file of the old release). +%% +%% The most important thing that can fail here is that the input boot +%% files do not contain all three base applications - kernel, stdlib +%% and sasl. +%% +%% TmpVsn = string(), +%% Paths = {KernelPath,StdlibPath,SaslPath} +%% Returns {ok,Boot} | {error,Reason} +%% Boot1 = Boot2 = Boot = binary() +%% Reason = {app_not_found,App} | {app_not_replaced,App} +%% App = kernel | stdlib | sasl +make_hybrid_boot(TmpVsn, Boot1, Boot2, Paths, Args) -> + catch do_make_hybrid_boot(TmpVsn, Boot1, Boot2, Paths, Args). +do_make_hybrid_boot(TmpVsn, Boot1, Boot2, Paths, Args) -> + {script,{_RelName1,_RelVsn1},Script1} = binary_to_term(Boot1), + {script,{RelName2,_RelVsn2},Script2} = binary_to_term(Boot2), + MatchPaths = get_regexp_path(Paths), + NewScript1 = replace_paths(Script1,MatchPaths), + {Kernel,Stdlib,Sasl} = get_apps(Script2,undefined,undefined,undefined), + NewScript2 = replace_apps(NewScript1,Kernel,Stdlib,Sasl), + NewScript3 = add_apply_upgrade(NewScript2,Args), + Boot = term_to_binary({script,{RelName2,TmpVsn},NewScript3}), + {ok,Boot}. + +%% For each app, compile a regexp that can be used for finding its path +get_regexp_path({KernelPath,StdlibPath,SaslPath}) -> + {ok,KernelMP} = re:compile("kernel-[0-9\.]+",[unicode]), + {ok,StdlibMP} = re:compile("stdlib-[0-9\.]+",[unicode]), + {ok,SaslMP} = re:compile("sasl-[0-9\.]+",[unicode]), + [{KernelMP,KernelPath},{StdlibMP,StdlibPath},{SaslMP,SaslPath}]. + +%% For each path in the script, check if it matches any of the MPs +%% found above, and if so replace it with the correct new path. +replace_paths([{path,Path}|Script],MatchPaths) -> + [{path,replace_path(Path,MatchPaths)}|replace_paths(Script,MatchPaths)]; +replace_paths([Stuff|Script],MatchPaths) -> + [Stuff|replace_paths(Script,MatchPaths)]; +replace_paths([],_) -> + []. + +replace_path([Path|Paths],MatchPaths) -> + [do_replace_path(Path,MatchPaths)|replace_path(Paths,MatchPaths)]; +replace_path([],_) -> + []. + +do_replace_path(Path,[{MP,ReplacePath}|MatchPaths]) -> + case re:run(Path,MP,[{capture,none}]) of + nomatch -> do_replace_path(Path,MatchPaths); + match -> ReplacePath + end; +do_replace_path(Path,[]) -> + Path. + +%% Return the entries for loading the three base applications +get_apps([{kernelProcess,application_controller, + {application_controller,start,[{application,kernel,_}]}}=Kernel| + Script],_,Stdlib,Sasl) -> + get_apps(Script,Kernel,Stdlib,Sasl); +get_apps([{apply,{application,load,[{application,stdlib,_}]}}=Stdlib|Script], + Kernel,_,Sasl) -> + get_apps(Script,Kernel,Stdlib,Sasl); +get_apps([{apply,{application,load,[{application,sasl,_}]}}=Sasl|_Script], + Kernel,Stdlib,_) -> + {Kernel,Stdlib,Sasl}; +get_apps([_|Script],Kernel,Stdlib,Sasl) -> + get_apps(Script,Kernel,Stdlib,Sasl); +get_apps([],undefined,_,_) -> + throw({error,{app_not_found,kernel}}); +get_apps([],_,undefined,_) -> + throw({error,{app_not_found,stdlib}}); +get_apps([],_,_,undefined) -> + throw({error,{app_not_found,sasl}}). + + +%% Replace the entries for loading the base applications +replace_apps([{kernelProcess,application_controller, + {application_controller,start,[{application,kernel,_}]}}| + Script],Kernel,Stdlib,Sasl) -> + [Kernel|replace_apps(Script,undefined,Stdlib,Sasl)]; +replace_apps([{apply,{application,load,[{application,stdlib,_}]}}|Script], + Kernel,Stdlib,Sasl) -> + [Stdlib|replace_apps(Script,Kernel,undefined,Sasl)]; +replace_apps([{apply,{application,load,[{application,sasl,_}]}}|Script], + _Kernel,_Stdlib,Sasl) -> + [Sasl|Script]; +replace_apps([Stuff|Script],Kernel,Stdlib,Sasl) -> + [Stuff|replace_apps(Script,Kernel,Stdlib,Sasl)]; +replace_apps([],undefined,undefined,_) -> + throw({error,{app_not_replaced,sasl}}); +replace_apps([],undefined,_,_) -> + throw({error,{app_not_replaced,stdlib}}); +replace_apps([],_,_,_) -> + throw({error,{app_not_replaced,kernel}}). + + +%% Finally add an apply of release_handler:new_emulator_upgrade - which will +%% complete the execution of the upgrade script (relup). +add_apply_upgrade(Script,Args) -> + [{progress, started} | RevScript] = lists:reverse(Script), + lists:reverse([{progress,started}, + {apply,{release_handler,new_emulator_upgrade,Args}} | + RevScript]). + + + %%----------------------------------------------------------------- %% Create a release package from a release file. %% Options is a list of {path, Path} | silent | @@ -232,13 +364,13 @@ get_release(File, Path, ModTestP, Machine) -> end. get_release1(File, Path, ModTestP, Machine) -> - {ok, Release} = read_release(File, Path), + {ok, Release, Warnings1} = read_release(File, Path), {ok, Appls0} = collect_applications(Release, Path), {ok, Appls1} = check_applications(Appls0), {ok, Appls2} = sort_included_applications(Appls1, Release), % OTP-4121 - {ok, Warnings} = check_modules(Appls2, Path, ModTestP, Machine), + {ok, Warnings2} = check_modules(Appls2, Path, ModTestP, Machine), {ok, Appls} = sort_appls(Appls2), - {ok, Release, Appls, Warnings}. + {ok, Release, Appls, Warnings1 ++ Warnings2}. %%______________________________________________________________________ %% read_release(File, Path) -> {ok, #release} | throw({error, What}) @@ -253,11 +385,12 @@ read_release(File, Path) -> check_rel(Release) -> case catch check_rel1(Release) of - {ok, {Name,Vsn,Evsn,Appl,Incl}} -> + {ok, {Name,Vsn,Evsn,Appl,Incl}, Ws} -> {ok, #release{name=Name, vsn=Vsn, erts_vsn=Evsn, applications=Appl, - incl_apps=Incl}}; + incl_apps=Incl}, + Ws}; {error, Error} -> throw({error,?MODULE,Error}); Error -> @@ -268,8 +401,8 @@ check_rel1({release,{Name,Vsn},{erts,EVsn},Appl}) when is_list(Appl) -> check_name(Name), check_vsn(Vsn), check_evsn(EVsn), - {Appls,Incls} = check_appl(Appl), - {ok, {Name,Vsn,EVsn,Appls,Incls}}; + {{Appls,Incls},Ws} = check_appl(Appl), + {ok, {Name,Vsn,EVsn,Appls,Incls},Ws}; check_rel1(_) -> {error, badly_formatted_release}. @@ -322,8 +455,8 @@ check_appl(Appl) -> end, Appl) of [] -> - mandatory_applications(Appl), - split_app_incl(Appl); + {ok,Ws} = mandatory_applications(Appl), + {split_app_incl(Appl),Ws}; Illegal -> throw({error, {illegal_applications,Illegal}}) end. @@ -334,7 +467,12 @@ mandatory_applications(Appl) -> Mand = mandatory_applications(), case filter(fun(X) -> member(X, AppNames) end, Mand) of Mand -> - ok; + case member(sasl,AppNames) of + true -> + {ok,[]}; + _ -> + {ok, [{warning,missing_sasl}]} + end; _ -> throw({error, {missing_mandatory_app, Mand}}) end. @@ -1612,9 +1750,9 @@ var_dir(_Dir, _, _, []) -> false. appDir(AppDir) -> - case reverse(filename:split(AppDir)) of - ["ebin"|Dir] -> filename:join(reverse(Dir)); - _ -> AppDir + case filename:basename(AppDir) of + "ebin" -> filename:dirname(AppDir); + _ -> AppDir end. add_modules(Modules, Tar, AppDir, ToDir, Ext) -> @@ -1833,78 +1971,89 @@ get_flag(_,_) -> false. %% Check Options for make_script check_args_script(Args) -> cas(Args, - {undef, undef, undef, undef, undef, undef, undef, undef, []}). + {undef, undef, undef, undef, undef, undef, undef, undef, + undef, []}). -cas([], {_Path,_Sil,_Loc,_Test,_Var,_Mach,_Xref,_XrefApps, X}) -> +cas([], {_Path,_Sil,_Loc,_Test,_Var,_Mach,_Xref,_XrefApps,_Werror, X}) -> X; %%% path --------------------------------------------------------------- -cas([{path, P} | Args], {Path, Sil, Loc, Test, Var, Mach, - Xref, XrefApps, X}) when is_list(P) -> +cas([{path, P} | Args], {Path, Sil, Loc, Test, Var, Mach, Xref, + XrefApps, Werror, X}) when is_list(P) -> case check_path(P) of ok -> - cas(Args, {P, Sil, Loc, Test, Var, Mach, Xref, XrefApps,X}); + cas(Args, {P, Sil, Loc, Test, Var, Mach, Xref, XrefApps, + Werror, X}); error -> cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, - X++[{path,P}]}) + Werror, X++[{path,P}]}) end; %%% silent ------------------------------------------------------------- -cas([silent | Args], {Path, _Sil, Loc, Test, Var, Mach, - Xref, XrefApps, X}) -> - cas(Args, {Path, silent, Loc, Test, Var, Mach, Xref, XrefApps, X}); +cas([silent | Args], {Path, _Sil, Loc, Test, Var, Mach, Xref, + XrefApps, Werror, X}) -> + cas(Args, {Path, silent, Loc, Test, Var, Mach, Xref, XrefApps, + Werror, X}); %%% local -------------------------------------------------------------- -cas([local | Args], {Path, Sil, _Loc, Test, Var, Mach, - Xref, XrefApps, X}) -> - cas(Args, {Path, Sil, local, Test, Var, Mach, Xref, XrefApps, X}); +cas([local | Args], {Path, Sil, _Loc, Test, Var, Mach, Xref, + XrefApps, Werror, X}) -> + cas(Args, {Path, Sil, local, Test, Var, Mach, Xref, XrefApps, + Werror, X}); %%% src_tests ------------------------------------------------------- -cas([src_tests | Args], {Path, Sil, Loc, _Test, Var, Mach, - Xref, XrefApps, X}) -> +cas([src_tests | Args], {Path, Sil, Loc, _Test, Var, Mach, Xref, + XrefApps, Werror, X}) -> cas(Args, - {Path, Sil, Loc, src_tests, Var, Mach, Xref, XrefApps,X}); + {Path, Sil, Loc, src_tests, Var, Mach, Xref, Werror, XrefApps,X}); %%% variables ---------------------------------------------------------- -cas([{variables, V} | Args], {Path, Sil, Loc, Test, Var, Mach, - Xref, XrefApps, X}) when is_list(V) -> +cas([{variables, V} | Args], {Path, Sil, Loc, Test, Var, Mach, Xref, + XrefApps, Werror, X}) when is_list(V) -> case check_vars(V) of ok -> cas(Args, - {Path, Sil, Loc, Test, V, Mach, Xref, XrefApps, X}); + {Path, Sil, Loc, Test, V, Mach, Xref, XrefApps, Werror, X}); error -> cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, - X++[{variables, V}]}) + Werror, X++[{variables, V}]}) end; %%% machine ------------------------------------------------------------ -cas([{machine, M} | Args], {Path, Sil, Loc, Test, Var, Mach, - Xref, XrefApps, X}) when is_atom(M) -> - cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, X}); +cas([{machine, M} | Args], {Path, Sil, Loc, Test, Var, Mach, Xref, + XrefApps, Werror, X}) when is_atom(M) -> + cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, Werror, X}); %%% exref -------------------------------------------------------------- -cas([exref | Args], {Path, Sil, Loc, Test, Var, Mach, - _Xref, XrefApps, X}) -> - cas(Args, {Path, Sil, Loc, Test, Var, Mach, exref, XrefApps, X}); +cas([exref | Args], {Path, Sil, Loc, Test, Var, Mach, _Xref, + XrefApps, Werror, X}) -> + cas(Args, {Path, Sil, Loc, Test, Var, Mach, exref, XrefApps, Werror, X}); %%% exref Apps --------------------------------------------------------- -cas([{exref, Apps} | Args], {Path, Sil, Loc, Test, Var, Mach, - Xref, XrefApps, X}) when is_list(Apps) -> +cas([{exref, Apps} | Args], {Path, Sil, Loc, Test, Var, Mach, Xref, + XrefApps, Werror, X}) when is_list(Apps) -> case check_apps(Apps) of ok -> cas(Args, {Path, Sil, Loc, Test, Var, Mach, - Xref, Apps, X}); + Xref, Apps, Werror, X}); error -> cas(Args, {Path, Sil, Loc, Test, Var, Mach, - Xref, XrefApps, X++[{exref, Apps}]}) + Xref, XrefApps, Werror, X++[{exref, Apps}]}) end; %%% outdir Dir --------------------------------------------------------- -cas([{outdir, Dir} | Args], {Path, Sil, Loc, Test, Var, Mach, - Xref, XrefApps, X}) when is_list(Dir) -> - cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, X}); +cas([{outdir, Dir} | Args], {Path, Sil, Loc, Test, Var, Mach, Xref, + XrefApps, Werror, X}) when is_list(Dir) -> + cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, Werror, X}); %%% otp_build (secret, not documented) --------------------------------- -cas([otp_build | Args], {Path, Sil, Loc, Test, Var, Mach, - Xref, XrefApps, X}) -> - cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, X}); +cas([otp_build | Args], {Path, Sil, Loc, Test, Var, Mach, Xref, + XrefApps, Werror, X}) -> + cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, Werror, X}); %%% no_module_tests (kept for backwards compatibility, but ignored) ---- -cas([no_module_tests | Args], {Path, Sil, Loc, Test, Var, Mach, - Xref, XrefApps, X}) -> - cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps,X}); +cas([no_module_tests | Args], {Path, Sil, Loc, Test, Var, Mach, Xref, + XrefApps, Werror, X}) -> + cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, Werror, X}); +%%% warnings_as_errors (kept for backwards compatibility, but ignored) ---- +cas([warnings_as_errors | Args], {Path, Sil, Loc, Test, Var, Mach, Xref, + XrefApps, _Werror, X}) -> + cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, + warnings_as_errors, X}); %%% ERROR -------------------------------------------------------------- -cas([Y | Args], {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, X}) -> - cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps,X++[Y]}). +cas([Y | Args], {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, + Werror, X}) -> + cas(Args, {Path, Sil, Loc, Test, Var, Mach, Xref, XrefApps, Werror, + X++[Y]}). @@ -2030,7 +2179,6 @@ check_apps([H|T]) when is_atom(H) -> check_apps(_) -> error. - %% Format error format_error(badly_formatted_release) -> @@ -2144,21 +2292,35 @@ form_tar_err({add, File, Error}) -> %% Format warning format_warning(Warnings) -> - map(fun({warning,W}) -> form_warn(W) end, Warnings). - -form_warn({source_not_found,{Mod,_,App,_,_}}) -> - io_lib:format("*WARNING* ~p: Source code not found: ~p.erl~n", - [App,Mod]); -form_warn({{parse_error, File},{_,_,App,_,_}}) -> - io_lib:format("*WARNING* ~p: Parse error: ~p~n", - [App,File]); -form_warn({obj_out_of_date,{Mod,_,App,_,_}}) -> - io_lib:format("*WARNING* ~p: Object code (~p) out of date~n",[App,Mod]); -form_warn({exref_undef, Undef}) -> - F = fun({M,F,A}) -> - io_lib:format("*WARNING* Undefined function ~p:~p/~p~n", - [M,F,A]) + format_warning(Warnings, false). + +format_warning(Warnings, Werror) -> + Prefix = case Werror of + true -> + ""; + false -> + "*WARNING* " + end, + map(fun({warning,W}) -> form_warn(Prefix, W) end, Warnings). + +form_warn(Prefix, {source_not_found,{Mod,_,App,_,_}}) -> + io_lib:format("~s~p: Source code not found: ~p.erl~n", + [Prefix,App,Mod]); +form_warn(Prefix, {{parse_error, File},{_,_,App,_,_}}) -> + io_lib:format("~s~p: Parse error: ~p~n", + [Prefix,App,File]); +form_warn(Prefix, {obj_out_of_date,{Mod,_,App,_,_}}) -> + io_lib:format("~s~p: Object code (~p) out of date~n", + [Prefix,App,Mod]); +form_warn(Prefix, {exref_undef, Undef}) -> + F = fun({M,F,A}) -> + io_lib:format("~sUndefined function ~p:~p/~p~n", + [Prefix,M,F,A]) end, map(F, Undef); -form_warn(What) -> - io_lib:format("*WARNING* ~p~n", [What]). +form_warn(Prefix, missing_sasl) -> + io_lib:format("~s: Missing application sasl. " + "Can not upgrade with this release~n", + [Prefix]); +form_warn(Prefix, What) -> + io_lib:format("~s ~p~n", [Prefix,What]). diff --git a/lib/sasl/src/systools_rc.erl b/lib/sasl/src/systools_rc.erl index daadb79967..c16f6aa845 100644 --- a/lib/sasl/src/systools_rc.erl +++ b/lib/sasl/src/systools_rc.erl @@ -54,6 +54,7 @@ %% {sync_nodes, Id, Nodes} %% {apply, {M, F, A}} %% restart_new_emulator +%% restart_emulator %%----------------------------------------------------------------- %% High-level instructions that contain dependencies @@ -144,7 +145,10 @@ translate_merged_script(Mode, Script, Appls, PreAppls) -> {Before2, After2} = translate_dependent_instrs(Mode, Before1, After1, Appls), Before3 = merge_load_object_code(Before2), - NewScript = Before3 ++ [point_of_no_return | After2], + + {Before4,After4} = sort_emulator_restart(Mode,Before3,After2), + NewScript = Before4 ++ [point_of_no_return | After4], + check_syntax(NewScript), {ok, NewScript}. @@ -699,6 +703,39 @@ mlo([{load_object_code, {Lib, LibVsn, Mods}} | T]) -> mlo([]) -> []. %%----------------------------------------------------------------- +%% RESTART EMULATOR +%% ----------------------------------------------------------------- +%% ----------------------------------------------------------------- +%% Check if there are any 'restart_new_emulator' instructions (i.e. if +%% the emulator or core application version is changed). If so, this +%% must be done first for upgrade and last for downgrade. +%% Check if there are any 'restart_emulator' instructions, if so +%% remove all and place one the end. +%% ----------------------------------------------------------------- +sort_emulator_restart(Mode,Before,After) -> + {Before1,After1} = + case filter_out(restart_new_emulator, After) of + After -> + {Before,After}; + A1 when Mode==up -> + {[restart_new_emulator|Before],A1}; + A1 when Mode==dn -> + {Before,A1++[restart_emulator]} + end, + After2 = + case filter_out(restart_emulator, After1) of + After1 -> + After1; + A2 -> + A2++[restart_emulator] + end, + {Before1,After2}. + + +filter_out(What,List) -> + lists:filter(fun(X) when X=:=What -> false; (_) -> true end, List). + +%%----------------------------------------------------------------- %% SYNTAX CHECK %%----------------------------------------------------------------- %%----------------------------------------------------------------- @@ -817,6 +854,7 @@ check_op({apply, {M, F, A}}) -> check_func(F), check_args(A); check_op(restart_new_emulator) -> ok; +check_op(restart_emulator) -> ok; check_op(X) -> throw({error, {bad_instruction, X}}). check_mod(Mod) when is_atom(Mod) -> ok; diff --git a/lib/sasl/src/systools_relup.erl b/lib/sasl/src/systools_relup.erl index ec5486226c..7fb623bb85 100644 --- a/lib/sasl/src/systools_relup.erl +++ b/lib/sasl/src/systools_relup.erl @@ -112,6 +112,11 @@ -export([mk_relup/3, mk_relup/4, format_error/1, format_warning/1]). -include("systools.hrl"). +-define(R15_SASL_VSN,"2.2"). + +%% For test purposes only - used by kernel, stdlib and sasl tests +-export([appup_search_for_version/2]). + %%----------------------------------------------------------------- %% mk_relup(TopRelFile, BaseUpRelDcs, BaseDnRelDcs) %% mk_relup(TopRelFile, BaseUpRelDcs, BaseDnRelDcs, Opts) -> Ret @@ -122,7 +127,7 @@ %% rel_filename() = description() = string() %% Opts = [opt()] %% opt() = {path, [path()]} | silent | noexec | restart_emulator -%% | {outdir, string()} +%% | {outdir, string()} | warnings_as_errors %% path() = [string()] %% Ret = ok | error | {ok, Relup, Module, Warnings} | {error, Module, Error} %% @@ -139,8 +144,9 @@ %% %% The option `path' sets search path, `silent' suppresses printing of %% error messages to the console, `noexec' inhibits the creation of -%% the output "relup" file, and restart_emulator ensures that the new -%% emulator is restarted (as the final step). +%% the output "relup" file, restart_emulator ensures that the new +%% emulator is restarted (as the final step), and `warnings_as_errors' +%% treats warnings as errors. %% ---------------------------------------------------------------- mk_relup(TopRelFile, BaseUpRelDcs, BaseDnRelDcs) -> mk_relup(TopRelFile, BaseUpRelDcs, BaseDnRelDcs, []). @@ -153,14 +159,29 @@ mk_relup(TopRelFile, BaseUpRelDcs, BaseDnRelDcs, Opts) -> {false, false} -> case R of {ok, _Res, _Mod, Ws} -> - print_warnings(Ws), - ok; + print_warnings(Ws, Opts), + case systools_lib:werror(Opts, Ws) of + true -> + error; + false -> + ok + end; Other -> print_error(Other), error end; - _ -> - R + _ -> + case R of + {ok, _Res, _Mod, Ws} -> + case systools_lib:werror(Opts, Ws) of + true -> + error; + false -> + R + end; + R -> + R + end end; BadArg -> erlang:error({badarg, BadArg}) @@ -184,7 +205,13 @@ do_mk_relup(TopRelFile, BaseUpRelDcs, BaseDnRelDcs, Path, Opts) -> %% TopRel = #release %% NameVsnApps = [{{Name, Vsn}, #application}] {ok, TopRel, NameVsnApps, Ws0} -> - %% + case lists:member({warning,missing_sasl},Ws0) of + true -> + throw({error,?MODULE,{missing_sasl,TopRel}}); + false -> + ok + end, + %% TopApps = [#application] TopApps = lists:map(fun({_, App}) -> App end, NameVsnApps), @@ -195,7 +222,12 @@ do_mk_relup(TopRelFile, BaseUpRelDcs, BaseDnRelDcs, Path, Opts) -> {Dn, Ws2} = foreach_baserel_dn(TopRel, TopApps, BaseDnRelDcs, Path, Opts, Ws1), Relup = {TopRel#release.vsn, Up, Dn}, - write_relup_file(Relup, Opts), + case systools_lib:werror(Opts, Ws2) of + true -> + ok; + false -> + write_relup_file(Relup, Opts) + end, {ok, Relup, ?MODULE, Ws2}; Other -> throw(Other) @@ -226,7 +258,21 @@ foreach_baserel_up(TopRel, TopApps, [BaseRelDc|BaseRelDcs], Path, Opts, Ws, Acc) -> BaseRelFile = extract_filename(BaseRelDc), - {ok, BaseRel} = systools_make:read_release(BaseRelFile, Path), + {BaseRel, {BaseNameVsns, BaseApps}, Ws0} = + case systools_make:get_release(BaseRelFile, Path) of + {ok, BR, NameVsnApps, Warns} -> + case lists:member({warning,missing_sasl},Warns) of + true -> + throw({error,?MODULE,{missing_sasl,BR}}); + false -> + %% NameVsnApps = [{{Name,Vsn},#application}] + %% Gives two lists - [{Name,Vsn}] and [#application] + {BR,lists:unzip(NameVsnApps),Warns} + end; + Other1 -> + throw(Other1) + end, + %% %% BaseRel = #release @@ -236,29 +282,23 @@ foreach_baserel_up(TopRel, TopApps, [BaseRelDc|BaseRelDcs], Path, Opts, %% application, one for each added applications, and one for %% each removed applications. %% - {RUs1, Ws1} = collect_appup_scripts(up, TopApps, BaseRel, Ws, []), + {RUs1, Ws1} = collect_appup_scripts(up, TopApps, BaseRel, Ws0++Ws, []), {RUs2, Ws2} = create_add_app_scripts(BaseRel, TopRel, RUs1, Ws1), {RUs3, Ws3} = create_remove_app_scripts(BaseRel, TopRel, RUs2, Ws2), - {RUs4, Ws4} = - check_for_emulator_restart(TopRel, BaseRel, RUs3, Ws3, Opts), - - BaseApps = - case systools_make:get_release(BaseRelFile, Path) of - {ok, _, NameVsnApps, _Warns} -> - lists:map(fun({_,App}) -> App end, NameVsnApps); - Other1 -> - throw(Other1) - end, + {RUs4, Ws4} = check_for_emulator_restart(TopRel, BaseRel, RUs3, Ws3, Opts), case systools_rc:translate_scripts(up, RUs4, TopApps, BaseApps) of - {ok, RUs} -> + {ok, RUs5} -> + + {RUs, Ws5} = fix_r15_sasl_upgrade(RUs5,Ws4,BaseNameVsns), + VDR = {BaseRel#release.vsn, extract_description(BaseRelDc), RUs}, foreach_baserel_up(TopRel, TopApps, BaseRelDcs, Path, - Opts, Ws4, [VDR| Acc]); + Opts, Ws5, [VDR| Acc]); XXX -> throw(XXX) end; @@ -273,42 +313,41 @@ foreach_baserel_dn(TopRel, TopApps, [BaseRelDc|BaseRelDcs], Path, Opts, Ws, Acc) -> BaseRelFile = extract_filename(BaseRelDc), - {ok, BaseRel} = systools_make:read_release(BaseRelFile, Path), - - %% BaseRel = #release - - %% RUs = (release upgrade scripts) - %% - {RUs1, Ws1} = collect_appup_scripts(dn, TopApps, BaseRel, Ws, []), - - {BaseApps, Ws2} = + {BaseRel, BaseApps, Ws0} = case systools_make:get_release(BaseRelFile, Path) of %% %% NameVsnApps = [{{Name, Vsn}, #application}] - {ok, _, NameVsnApps, Warns} -> - %% - %% NApps = [#application] - NApps = lists:map(fun({_,App}) -> App end, NameVsnApps), - {NApps, Warns ++ Ws1}; + {ok, BR, NameVsnApps, Warns} -> + case lists:member({warning,missing_sasl},Warns) of + true -> + throw({error,?MODULE,{missing_sasl,BR}}); + false -> + %% NApps = [#application] + NApps = lists:map(fun({_,App}) -> App end, NameVsnApps), + {BR, NApps, Warns} + end; Other -> throw(Other) end, - RUs2 = RUs1, + %% BaseRel = #release + + %% RUs = (release upgrade scripts) + %% + {RUs1, Ws1} = collect_appup_scripts(dn, TopApps, BaseRel, Ws0++Ws, []), - {RUs3, Ws3} = create_add_app_scripts(TopRel, BaseRel, RUs2, Ws2), + {RUs2, Ws2} = create_add_app_scripts(TopRel, BaseRel, RUs1, Ws1), - {RUs4, Ws4} = create_remove_app_scripts(TopRel, BaseRel, RUs3, Ws3), + {RUs3, Ws3} = create_remove_app_scripts(TopRel, BaseRel, RUs2, Ws2), - {RUs5, Ws5} = check_for_emulator_restart(TopRel, BaseRel, - RUs4, Ws4, Opts), + {RUs4, Ws4} = check_for_emulator_restart(TopRel, BaseRel, RUs3, Ws3, Opts), - case systools_rc:translate_scripts(dn, RUs5, BaseApps, TopApps) of + case systools_rc:translate_scripts(dn, RUs4, BaseApps, TopApps) of {ok, RUs} -> VDR = {BaseRel#release.vsn, extract_description(BaseRelDc), RUs}, foreach_baserel_dn(TopRel, TopApps, BaseRelDcs, Path, - Opts, Ws5, [VDR| Acc]); + Opts, Ws4, [VDR| Acc]); XXX -> throw(XXX) end; @@ -322,14 +361,42 @@ foreach_baserel_dn( _, _, [], _, _, Ws, Acc) -> %% check_for_emulator_restart(#release{erts_vsn = Vsn1, name = N1}, #release{erts_vsn = Vsn2, name = N2}, RUs, Ws, - _Opts) when Vsn1 /= Vsn2 -> - {RUs++[[restart_new_emulator]], [{erts_vsn_changed, {N1, N2}} | Ws]}; + Opts) when Vsn1 /= Vsn2 -> + %% Automatically insert a restart_new_emulator instruction when + %% erts version is changed. Also allow extra restart at the end of + %% the upgrade if restart_emulator option is given. + NewRUs = [[restart_new_emulator]|RUs], + NewWs = [{erts_vsn_changed, {{N1,Vsn1}, {N2,Vsn2}}} | Ws], + check_for_restart_emulator_opt(NewRUs, NewWs, Opts); check_for_emulator_restart(_, _, RUs, Ws, Opts) -> + check_for_restart_emulator_opt(RUs, Ws, Opts). + +check_for_restart_emulator_opt(RUs, Ws, Opts) -> case get_opt(restart_emulator, Opts) of - true -> {RUs++[[restart_new_emulator]], Ws}; + true -> {RUs++[[restart_emulator]], Ws}; _ -> {RUs, Ws} end. + +%% Special handling of the upgrade from pre R15 to post R15. In R15, +%% upgrade of the emulator was improved by moving the restart of the +%% emulator before the rest of the upgrade instructions. However, it +%% can only work if the release_handler is already upgraded to a post +%% R15 version. If not, the upgrade instructions must be backwards +%% compatible - i.e. restart_new_emulator will be the last +%% instruction, executed after all code loading, code_change etc. +fix_r15_sasl_upgrade([restart_new_emulator | RestRUs]=RUs, Ws, BaseApps) -> + case lists:keyfind(sasl,1,BaseApps) of + {sasl,Vsn} when Vsn < ?R15_SASL_VSN -> + {lists:delete(restart_emulator,RestRUs) ++ [restart_new_emulator], + [pre_R15_emulator_upgrade|Ws]}; + _ -> + {RUs,Ws} + end; +fix_r15_sasl_upgrade(RUs, Ws, _BaseApps) -> + {RUs,Ws}. + + %% collect_appup_scripts(Mode, TopApps, BaseRel, Ws, RUs) -> {NRUs, NWs} %% Mode = up | dn %% TopApps = [#application] @@ -419,13 +486,32 @@ get_script_from_appup(Mode, TopApp, BaseVsn, Ws, RUs) -> %% XXX Why is this a warning only? [{bad_vsn, {TopVsn, TopApp#application.vsn}}| Ws] end, - case lists:keysearch(BaseVsn, 1, VsnRUs) of - {value, {_, RU}} -> + case appup_search_for_version(BaseVsn, VsnRUs) of + {ok, RU} -> {RUs ++ [RU], Ws1}; - _ -> + error -> throw({error, ?MODULE, {no_relup, FName, TopApp, BaseVsn}}) end. +appup_search_for_version(BaseVsn, VsnRUs) -> + appup_search_for_version(BaseVsn, length(BaseVsn), VsnRUs). + +appup_search_for_version(BaseVsn,_,[{BaseVsn,RU}|_]) -> + {ok,RU}; +appup_search_for_version(BaseVsn,Size,[{Vsn,RU}|VsnRUs]) when is_binary(Vsn) -> + case re:run(BaseVsn,Vsn,[unicode,{capture,first,index}]) of + {match,[{0,Size}]} -> + {ok, RU}; + _ -> + appup_search_for_version(BaseVsn,Size,VsnRUs) + end; +appup_search_for_version(BaseVsn,Size,[_|VsnRUs]) -> + appup_search_for_version(BaseVsn,Size,VsnRUs); +appup_search_for_version(_,_,[]) -> + error. + + + %% Primitives for the "lists of release names" that we upgrade from %% and to. @@ -522,25 +608,36 @@ format_error({no_relup, File, App, Vsn}) -> "in file ~p~n", [App#application.name, App#application.vsn, App#application.name, Vsn, File]); - +format_error({missing_sasl,Release}) -> + io_lib:format("No sasl application in release ~p, ~p. Can not be upgraded.", + [Release#release.name, Release#release.vsn]); format_error(Error) -> io:format("~p~n", [Error]). -print_warnings(Ws) when is_list(Ws) -> - lists:foreach(fun(W) -> print_warning(W) end, Ws); -print_warnings(W) -> - print_warning(W). +print_warnings(Ws, Opts) when is_list(Ws) -> + lists:foreach(fun(W) -> print_warning(W, Opts) end, Ws); +print_warnings(W, Opts) -> + print_warning(W, Opts). -print_warning(W) -> - S = format_warning(W), +print_warning(W, Opts) -> + Prefix = case lists:member(warnings_as_errors, Opts) of + true -> + ""; + false -> + "*WARNING* " + end, + S = format_warning(Prefix, W), io:format("~s", [S]). -format_warning({erts_vsn_changed, {Rel1, Rel2}}) -> - io_lib:format("*WARNING* The ERTS version changed between ~p and ~p~n", - [Rel1, Rel2]); -format_warning(What) -> - io_lib:format("*WARNING* ~p~n",[What]). +format_warning(W) -> + format_warning("*WARNING* ", W). + +format_warning(Prefix, {erts_vsn_changed, {Rel1, Rel2}}) -> + io_lib:format("~sThe ERTS version changed between ~p and ~p~n", + [Prefix, Rel1, Rel2]); +format_warning(Prefix, What) -> + io_lib:format("~s~p~n",[Prefix, What]). get_reason({error, {open, _, _}}) -> open; |