📄 release_handler.erl
字号:
lists:foreach(fun({_Lib, _LVsn, LDir}) -> remove_file(LDir) end, RemoveThese), remove_file(filename:join([RelDir, Vsn])), case lists:keysearch(EVsn, #release.erts_vsn, NewReleases) of {value, _} -> ok; false -> % Remove erts library, no more references to it remove_file(filename:join(Root, "erts-" ++ EVsn)) end, write_releases(RelDir, NewReleases, false), {ok, NewReleases}; false -> {error, {no_such_release, Vsn}} end.do_set_unpacked(Root, RelDir, RelFile, LibDirs, Releases, Masters) -> Release = check_rel(Root, RelFile, LibDirs, Masters), #release{vsn = Vsn} = Release, case lists:keysearch(Vsn, #release.vsn, Releases) of {value, _} -> throw({error, {existing_release, Vsn}}); false -> ok end, NewReleases = [Release#release{status = unpacked} | Releases], VsnDir = filename:join([RelDir, Vsn]), make_dir(VsnDir, Masters), write_releases(RelDir, NewReleases, Masters), {ok, NewReleases, Vsn}.do_set_removed(RelDir, Vsn, Releases, Masters) -> case lists:keysearch(Vsn, #release.vsn, Releases) of {value, #release{status = permanent}} -> {error, {permanent, Vsn}}; {value, _} -> NewReleases = lists:keydelete(Vsn, #release.vsn, Releases), write_releases(RelDir, NewReleases, Masters), {ok, NewReleases}; false -> {error, {no_such_release, Vsn}} end.%%-----------------------------------------------------------------%% A relup file consists of:%% {Vsn, [{FromVsn, Descr, RhScript}], [{ToVsn, Descr, RhScript}]}.%% It describes how to get to this release from previous releases,%% and how to get from this release to previous releases.%% We can get from a FromVsn that's a substring of CurrentVsn (e.g.%% 1.1 is a substring of 1.1.1, but not 1.2), but when we get to%% ToVsn, we must have an exact match.%%%% We do not put any semantics into the version strings, i.e. we%% don't know if going from Vsn1 to Vsn2 represents a upgrade or%% a downgrade. For both upgrades and downgrades, the relup file%% is located in the directory of the latest version. Since we%% do not which version is latest, we first suppose that ToVsn > %% CurrentVsn, i.e. we perform an upgrade. If we don't find the%% corresponding relup instructions, we check if it's possible to%% downgrade from CurrentVsn to ToVsn.%%-----------------------------------------------------------------get_rh_script(#release{vsn = CurrentVsn}, #release{vsn = Vsn}, RelDir, Masters) -> Relup = filename:join([RelDir, Vsn, "relup"]), case try_upgrade(Vsn, CurrentVsn, Relup, Masters) of {ok, RhScript} -> {ok, RhScript}; _ -> Relup2 = filename:join([RelDir, CurrentVsn,"relup"]), case try_downgrade(Vsn, CurrentVsn, Relup2, Masters) of {ok, RhScript} -> {ok, RhScript}; _ -> throw({error, {no_matching_relup, Vsn, CurrentVsn}}) end end.try_upgrade(ToVsn, CurrentVsn, Relup, Masters) -> case consult(Relup, Masters) of {ok, [{ToVsn, ListOfRhScripts, _}]} -> case lists:keysearch(CurrentVsn, 1, ListOfRhScripts) of {value, RhScript} -> {ok, RhScript}; _ -> error end; {ok, _} -> throw({error, {bad_relup_file, Relup}}); {error, Reason} when is_tuple(Reason) -> throw({error, {bad_relup_file, Relup}}); {error, enoent} -> error; {error, FileError} -> % FileError is posix atom | no_master throw({error, {FileError, Relup}}) end.try_downgrade(ToVsn, CurrentVsn, Relup, Masters) -> case consult(Relup, Masters) of {ok, [{CurrentVsn, _, ListOfRhScripts}]} -> case lists:keysearch(ToVsn, 1, ListOfRhScripts) of {value, RhScript} -> {ok, RhScript}; _ -> error end; {ok, _} -> throw({error, {bad_relup_file, Relup}}); {error, Reason} when is_tuple(Reason) -> throw({error, {bad_relup_file, Relup}}); {error, FileError} -> % FileError is posix atom | no_master throw({error, {FileError, Relup}}) end.%% Status = current | tmp_current | permanentset_status(Vsn, Status, Releases) -> lists:zf(fun(Release) when Release#release.vsn == Vsn, Release#release.status == permanent -> %% If a permanent rel is installed, it keeps its %% permanent status (not changed to current). %% The current becomes old though. true; (Release) when Release#release.vsn == Vsn -> {true, Release#release{status = Status}}; (Release) when Release#release.status == Status -> {true, Release#release{status = old}}; (_) -> true end, Releases).get_latest_release(Releases) -> case lists:keysearch(current, #release.status, Releases) of {value, Release} -> Release; false -> {value, Release} = lists:keysearch(permanent, #release.status, Releases), Release end.%% Returns: [{Lib, Vsn, Dir}] to be removeddiff_dir([H | T], L) -> case memlib(H, L) of true -> diff_dir(T, L); false -> [H | diff_dir(T, L)] end;diff_dir([], _) -> [].memlib({Lib, Vsn, _Dir}, [{Lib, Vsn, _Dir2} | _T]) -> true;memlib(Lib, [_H | T]) -> memlib(Lib, T);memlib(_Lib, []) -> false. %% recursively remove file or directoryremove_file(File) -> case file:read_file_info(File) of {ok, Info} when Info#file_info.type==directory -> case file:list_dir(File) of {ok, Files} -> lists:foreach(fun(File2) -> remove_file(filename:join(File,File2)) end, Files), case file:del_dir(File) of ok -> ok; {error, Reason} -> throw({error, Reason}) end; {error, Reason} -> throw({error, Reason}) end; {ok, _Info} -> case file:delete(File) of ok -> ok; {error, Reason} -> throw({error, Reason}) end; {error, _Reason} -> throw({error, {no_such_file, File}}) end.do_write_file(File, Str) -> case file:open(File, write) of {ok, Fd} -> io:put_chars(Fd, Str), file:close(Fd), ok; {error, Reason} -> {error, {Reason, File}} end.%%-----------------------------------------------------------------%% Change current applications (specifically, update their version,%% description and env.)%%-----------------------------------------------------------------change_appl_data(RelDir, #release{vsn = Vsn}, Masters) -> Dir = filename:join([RelDir, Vsn]), BootFile = filename:join(Dir, "start.boot"), case read_file(BootFile, Masters) of {ok, Bin} -> Config = case consult(filename:join(Dir, "sys.config"), Masters) of {ok, [Conf]} -> Conf; _ -> [] end, Appls = get_appls(binary_to_term(Bin)), case application_controller:change_application_data(Appls,Config) of ok -> Appls; {error, Reason} -> exit({change_appl_data, Reason}) end; {error, _Reason} -> throw({error, {no_such_file, BootFile}}) end.%%-----------------------------------------------------------------%% This function is dependent on the application functions and%% the start script syntax.%%-----------------------------------------------------------------get_appls({script, _, Script}) -> get_appls(Script, []).%% kernel is taken care of separatelyget_appls([{kernelProcess, application_controller, {application_controller, start, [App]}} |T], Res) -> get_appls(T, [App | Res]);%% other applications but kernelget_appls([{apply, {application, load, [App]}} |T], Res) -> get_appls(T, [App | Res]);get_appls([_ | T], Res) -> get_appls(T, Res);get_appls([], Res) -> Res.mon_nodes(true) -> net_kernel:monitor_nodes(true);mon_nodes(false) -> net_kernel:monitor_nodes(false), flush().flush() -> receive {nodedown, _} -> flush(); {nodeup, _} -> flush() after 0 -> ok end.prepare_restart_nt(#release{erts_vsn = EVsn, vsn = Vsn}, #release{erts_vsn = PermEVsn, vsn = PermVsn}, DataFileName) -> CurrentServiceName = hd(string:tokens(atom_to_list(node()),"@")) ++ "_" ++ PermVsn, FutureServiceName = hd(string:tokens(atom_to_list(node()),"@")) ++ "_" ++ Vsn, CurrentService = case erlsrv:get_service(PermEVsn,CurrentServiceName) of {error, Reason} -> throw({error, Reason}); CS -> CS end, FutureService = erlsrv:new_service(FutureServiceName, CurrentService, filename:nativename(DataFileName), %% This is rather icky... On a %% non permanent service, the %% ERLSRV_SERVICE_NAME is %% actually that of an old service, %% to make heart commands work... CurrentServiceName), case erlsrv:store_service(EVsn, FutureService) of {error, Rison} -> throw({error,Rison}); _ -> erlsrv:disable_service(EVsn, FutureServiceName), ErlSrv = filename:nativename(erlsrv:erlsrv(EVsn)), case heart:set_cmd(ErlSrv ++ " enable " ++ FutureServiceName ++ " & " ++ ErlSrv ++ " start " ++ FutureServiceName ++ " & " ++ ErlSrv ++ " disable " ++ FutureServiceName) of ok -> ok; Error -> throw({error, {'heart:set_cmd() error', Error}}) end end. %%-----------------------------------------------------------------%% Set things up for restarting the new emulator. The actual%% restart is performed by calling init:reboot() higher up.%%-----------------------------------------------------------------prepare_restart_new_emulator(StartPrg, RelDir, Release, PRelease, Masters) -> #release{erts_vsn = EVsn, vsn = Vsn} = Release, Data = EVsn ++ " " ++ Vsn, DataFile = write_new_start_erl(Data, RelDir, Masters), %% Tell heart to use DataFile instead of start_erl.data case os:type() of {win32,nt} -> prepare_restart_nt(Release,PRelease,DataFile); {unix,_} -> StartP = check_start_prg(StartPrg, Masters), case heart:set_cmd(StartP ++ " " ++ DataFile) of ok -> ok; Error -> throw({error, {'heart:set_cmd() error', Error}}) end end.check_start_prg({do_check, StartPrg}, Masters) -> check_file(StartPrg, regular, Masters), StartPrg;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.%%-----------------------------------------------------------------%% When a new emulator shall be restarted, the current release%% is written with status tmp_current. When the new emulator%% is started, this function is called. The tmp_current release%% gets status unpacked on disk, and current in memory. If a reboot%% is made (due to a crash), the release is just unpacked. If a crash%% occurs before a call to transform_release is made, the old emulator%% is started, and transform_release is called for it. The tmp_current%% release is changed to unpacked.%% 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 -> 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) end.%%-----------------------------------------------------------------%% Functions handling files, RELEASES, start_erl.data etc.%% This functions consider if the release_handler is a client and%% in that case performs the operations at all master nodes or at%% none (in case of failure).%%-----------------------------------------------------------------check_opt_file(FileName, Type, Masters) -> case catch check_file(FileName, Type, Masters) of ok -> true; _Error -> io:format("Warning: ~p missing (optional)~n", [FileName]), false end.check_file(FileName, Type, false) -> do_check_file(FileName, Type);check_file(FileName, Type, Masters) -> check_file_masters(FileName, Type, Masters).
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -