diff options
Diffstat (limited to 'lib/sasl/src')
-rw-r--r-- | lib/sasl/src/release_handler.erl | 420 | ||||
-rw-r--r-- | lib/sasl/src/release_handler_1.erl | 120 | ||||
-rw-r--r-- | lib/sasl/src/sasl.appup.src | 26 | ||||
-rw-r--r-- | lib/sasl/src/systools_make.erl | 144 | ||||
-rw-r--r-- | lib/sasl/src/systools_rc.erl | 40 | ||||
-rw-r--r-- | lib/sasl/src/systools_relup.erl | 153 |
6 files changed, 719 insertions, 184 deletions
diff --git a/lib/sasl/src/release_handler.erl b/lib/sasl/src/release_handler.erl index 5650f1433d..4e8cb4628c 100644 --- a/lib/sasl/src/release_handler.erl +++ b/lib/sasl/src/release_handler.erl @@ -26,9 +26,9 @@ create_RELEASES/1, create_RELEASES/2, create_RELEASES/4, unpack_release/1, check_install_release/1, check_install_release/2, - install_release/1, install_release/2, remove_release/1, - which_releases/0, which_releases/1, make_permanent/1, - reboot_old_release/1, + 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, @@ -41,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, @@ -62,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 - %%----------------------------------------------------------------- @@ -75,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 @@ -184,11 +193,15 @@ check_check_install_options([],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() %%----------------------------------------------------------------- @@ -231,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). @@ -322,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 @@ -359,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() @@ -379,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() @@ -587,7 +615,7 @@ handle_call({check_install_release, Vsn, Purge}, _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 = @@ -602,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, " @@ -950,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), @@ -958,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), @@ -995,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 @@ -1011,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; @@ -1269,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. @@ -1496,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, @@ -1521,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 @@ -1547,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 @@ -1626,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). @@ -1853,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]). diff --git a/lib/sasl/src/release_handler_1.erl b/lib/sasl/src/release_handler_1.erl index 8d0baf3ab1..b4b288646f 100644 --- a/lib/sasl/src/release_handler_1.erl +++ b/lib/sasl/src/release_handler_1.erl @@ -47,26 +47,38 @@ %%%----------------------------------------------------------------- %%% 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,soft_purge) of {ok, 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) -> - {ok,PurgeMods}; - {error, Error} -> - {error, Error}; - Other -> - {error, Other} - end; + 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, [], [], [], []). @@ -83,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. @@ -174,6 +194,42 @@ do_check_old_code(Mod,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. + + %%----------------------------------------------------------------- %% An unpurged module is a module for which there exist an old %% version of the code. This should only be the case if there are @@ -243,7 +299,7 @@ do_check_old_code(Mod,Procs) -> %% 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..} @@ -402,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 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_make.erl b/lib/sasl/src/systools_make.erl index 7f400f5cce..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]). @@ -162,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 | @@ -250,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}) @@ -271,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 -> @@ -286,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}. @@ -340,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. @@ -352,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. @@ -2198,5 +2318,9 @@ form_warn(Prefix, {exref_undef, Undef}) -> [Prefix,M,F,A]) end, map(F, Undef); +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 6d9e922900..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 @@ -200,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), @@ -247,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 @@ -257,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; @@ -294,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; @@ -343,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] @@ -440,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. @@ -543,7 +608,9 @@ 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]). |