aboutsummaryrefslogtreecommitdiffstats
path: root/lib/sasl/src/release_handler.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sasl/src/release_handler.erl')
-rw-r--r--lib/sasl/src/release_handler.erl420
1 files changed, 334 insertions, 86 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]).