PageRenderTime 52ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/sasl/src/release_handler.erl

https://github.com/cobusc/otp
Erlang | 2301 lines | 1600 code | 188 blank | 513 comment | 18 complexity | 0a680c04422a6b5be62be39428a201c6 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0
  1. %%
  2. %% %CopyrightBegin%
  3. %%
  4. %% Copyright Ericsson AB 1996-2018. All Rights Reserved.
  5. %%
  6. %% Licensed under the Apache License, Version 2.0 (the "License");
  7. %% you may not use this file except in compliance with the License.
  8. %% You may obtain a copy of the License at
  9. %%
  10. %% http://www.apache.org/licenses/LICENSE-2.0
  11. %%
  12. %% Unless required by applicable law or agreed to in writing, software
  13. %% distributed under the License is distributed on an "AS IS" BASIS,
  14. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. %% See the License for the specific language governing permissions and
  16. %% limitations under the License.
  17. %%
  18. %% %CopyrightEnd%
  19. %%
  20. -module(release_handler).
  21. -behaviour(gen_server).
  22. -include_lib("kernel/include/file.hrl").
  23. %% External exports
  24. -export([start_link/0,
  25. create_RELEASES/1, create_RELEASES/2, create_RELEASES/4,
  26. unpack_release/1,
  27. check_install_release/1, check_install_release/2,
  28. install_release/1, install_release/2, new_emulator_upgrade/2,
  29. remove_release/1, which_releases/0, which_releases/1,
  30. make_permanent/1, reboot_old_release/1,
  31. set_unpacked/2, set_removed/1, install_file/2]).
  32. -export([upgrade_app/2, downgrade_app/2, downgrade_app/3,
  33. upgrade_script/2, downgrade_script/3,
  34. eval_appup_script/4]).
  35. %% Internal exports
  36. -export([init/1, handle_call/3, handle_info/2, terminate/2,
  37. handle_cast/2, code_change/3]).
  38. %% Internal exports, a client release_handler may call this functions.
  39. -export([do_write_release/3, do_copy_file/2, do_copy_files/2,
  40. do_copy_files/1, do_rename_files/1, do_remove_files/1,
  41. remove_file/1, do_write_file/2, do_write_file/3,
  42. do_ensure_RELEASES/1]).
  43. -record(state, {unpurged = [],
  44. root,
  45. rel_dir,
  46. releases,
  47. timer,
  48. start_prg,
  49. masters = false,
  50. client_dir = false,
  51. static_emulator = false,
  52. pre_sync_nodes = []}).
  53. %%-----------------------------------------------------------------
  54. %% status action next_status
  55. %% =============================================
  56. %% - unpack unpacked
  57. %% unpacked install current
  58. %% remove -
  59. %% current make_permanent permanent
  60. %% install other old
  61. %% restart node unpacked
  62. %% remove -
  63. %% permanent make other permanent old
  64. %% install permanent
  65. %% old reboot_old permanent
  66. %% install current
  67. %% remove -
  68. %%-----------------------------------------------------------------
  69. %% libs = [{Lib, Vsn, Dir}]
  70. -record(release, {name, vsn, erts_vsn, libs = [], status}).
  71. -define(timeout, 10000).
  72. %%-----------------------------------------------------------------
  73. %% The version set on the temporary release that will be used when the
  74. %% emulator is upgraded.
  75. -define(tmp_vsn(__BaseVsn__), "__new_emulator__"++__BaseVsn__).
  76. %%-----------------------------------------------------------------
  77. %% Assumes the following file structure:
  78. %% root --- lib --- Appl-Vsn1 --- <src>
  79. %% | | |- ebin
  80. %% | | |_ priv
  81. %% | |_ Appl-Vsn2
  82. %% |
  83. %% |- bin --- start (default; {sasl, start_prg} overrides
  84. %% | |- run_erl
  85. %% | |- start_erl (reads start_erl.data)
  86. %% | |_ <to_erl>
  87. %% |
  88. %% |- erts-EVsn1 --- bin --- <jam44>
  89. %% | |- <epmd>
  90. %% | |_ erl
  91. %% |- erts-EVsn2
  92. %% |
  93. %% |- clients --- ClientName1 --- bin -- start
  94. %% <clients use same lib and erts as master>
  95. %% | | |_ releases --- start_erl.data
  96. %% | | |_ Vsn1 -- start.boot
  97. %% | |_ ClientName2
  98. %% |
  99. %% |- clients --- Type1 --- lib
  100. %% <clients use own lib and erts>
  101. %% | | |- erts-EVsn
  102. %% | | |- bin -- start
  103. %% | | |_ ClientName1 -- releases -- start_erl.data
  104. %% | | |_ start.boot (static)
  105. %% | | |_ Vsn1
  106. %% | |_ Type2
  107. %% |
  108. %% |- releases --- RELEASES
  109. %% | |_ <Vsn1.tar.Z>
  110. %% | |
  111. %% | |- start_erl.data (generated by rh)
  112. %% | |
  113. %% | |_ Vsn1 --- start.boot
  114. %% | | |- <sys.config>
  115. %% | | |_ relup
  116. %% | |_ Vsn2
  117. %% |
  118. %% |- log --- erlang.log.N (1 .. 5)
  119. %%
  120. %% where <Name> means 'for example Name', and root is
  121. %% init:get_argument(root)
  122. %%
  123. %% It is configurable where the start file is located, and what it
  124. %% is called.
  125. %% The paramater is {sasl, start_prg} = File
  126. %% It is also configurable where the releases directory is located.
  127. %% Default is $ROOT/releases. $RELDIR overrids, and
  128. %% {sasl, releases_dir} overrides both.
  129. %%-----------------------------------------------------------------
  130. start_link() ->
  131. gen_server:start_link({local, release_handler}, ?MODULE, [], []).
  132. %%-----------------------------------------------------------------
  133. %% Args: ReleaseName is the name of the package file
  134. %% (without .tar.Z (.tar on non unix systems))
  135. %% Purpose: Copies all files in the release package to their
  136. %% directories. Checks that all required libs and erts
  137. %% files are present.
  138. %% Returns: {ok, Vsn} | {error, Reason}
  139. %% Reason = {existing_release, Vsn} |
  140. %% {no_such_file, File} |
  141. %% {bad_rel_file, RelFile} |
  142. %% {file_missing, FileName} | (in the tar package)
  143. %% exit_reason()
  144. %%-----------------------------------------------------------------
  145. unpack_release(ReleaseName) ->
  146. call({unpack_release, ReleaseName}).
  147. %%-----------------------------------------------------------------
  148. %% Purpose: Checks the relup script for the specified version.
  149. %% The release must be unpacked.
  150. %% Options = [purge] - all old code that can be soft purged
  151. %% will be purged if all checks succeeds. This can be usefull
  152. %% in order to reduce time needed in the following call to
  153. %% install_release.
  154. %% Returns: {ok, FromVsn, Descr} | {error, Reason}
  155. %% Reason = {illegal_option, IllegalOpt} |
  156. %% {already_installed, Vsn} |
  157. %% {bad_relup_file, RelFile} |
  158. %% {no_such_release, Vsn} |
  159. %% {no_such_from_vsn, Vsn} |
  160. %% exit_reason()
  161. %%-----------------------------------------------------------------
  162. check_install_release(Vsn) ->
  163. check_install_release(Vsn, []).
  164. check_install_release(Vsn, Opts) ->
  165. case check_check_install_options(Opts, false) of
  166. {ok,Purge} ->
  167. call({check_install_release, Vsn, Purge});
  168. Error ->
  169. Error
  170. end.
  171. check_check_install_options([purge|Opts], _) ->
  172. check_check_install_options(Opts, true);
  173. check_check_install_options([Illegal|_],_Purge) ->
  174. {error,{illegal_option,Illegal}};
  175. check_check_install_options([],Purge) ->
  176. {ok,Purge}.
  177. %%-----------------------------------------------------------------
  178. %% Purpose: Executes the relup script for the specified version.
  179. %% The release must be unpacked.
  180. %% Returns: {ok, FromVsn, Descr} |
  181. %% {continue_after_restart, FromVsn, Descr} |
  182. %% {error, Reason}
  183. %% Reason = {already_installed, Vsn} |
  184. %% {bad_relup_file, RelFile} |
  185. %% {no_such_release, Vsn} |
  186. %% {no_such_from_vsn, Vsn} |
  187. %% {could_not_create_hybrid_boot,Why} |
  188. %% {missing_base_app,Vsn,App} |
  189. %% {illegal_option, Opt}} |
  190. %% exit_reason()
  191. %%-----------------------------------------------------------------
  192. install_release(Vsn) ->
  193. call({install_release, Vsn, restart, []}).
  194. install_release(Vsn, Opt) ->
  195. case check_install_options(Opt, restart, []) of
  196. {ok, ErrorAction, InstallOpt} ->
  197. call({install_release, Vsn, ErrorAction, InstallOpt});
  198. Error ->
  199. Error
  200. end.
  201. check_install_options([Opt | Opts], ErrAct, InstOpts) ->
  202. case install_option(Opt) of
  203. {error_action, EAct} ->
  204. check_install_options(Opts, EAct, InstOpts);
  205. true ->
  206. check_install_options(Opts, ErrAct, [Opt | InstOpts]);
  207. false ->
  208. {error, {illegal_option, Opt}}
  209. end;
  210. check_install_options([], ErrAct, InstOpts) ->
  211. {ok, ErrAct, InstOpts}.
  212. install_option(Opt = {error_action, reboot}) -> Opt;
  213. install_option(Opt = {error_action, restart}) -> Opt;
  214. install_option({code_change_timeout, TimeOut}) ->
  215. check_timeout(TimeOut);
  216. install_option({suspend_timeout, TimeOut}) ->
  217. check_timeout(TimeOut);
  218. install_option({update_paths, Bool}) when Bool==true; Bool==false ->
  219. true;
  220. install_option(_Opt) -> false.
  221. check_timeout(infinity) -> true;
  222. check_timeout(Int) when is_integer(Int), Int > 0 -> true;
  223. check_timeout(_Else) -> false.
  224. %%-----------------------------------------------------------------
  225. %% Purpose: Called by boot script after emulator is restarted due to
  226. %% new erts version.
  227. %% Returns: Same as install_release/2
  228. %% If this crashes, the emulator restart will fail
  229. %% (since the function is called from the boot script)
  230. %% and there will be a rollback.
  231. %%-----------------------------------------------------------------
  232. new_emulator_upgrade(Vsn, Opts) ->
  233. Result = call({install_release, Vsn, reboot, Opts}),
  234. error_logger:info_msg(
  235. "~w:install_release(~p,~p) completed after node restart "
  236. "with new emulator version~nResult: ~p~n",[?MODULE,Vsn,Opts,Result]),
  237. Result.
  238. %%-----------------------------------------------------------------
  239. %% Purpose: Makes the specified release version be the one that is
  240. %% used when the system starts (or restarts).
  241. %% The release must be installed (not unpacked).
  242. %% Returns: ok | {error, Reason}
  243. %% Reason = {bad_status, Status} |
  244. %% {no_such_release, Vsn} |
  245. %% exit_reason()
  246. %%-----------------------------------------------------------------
  247. make_permanent(Vsn) ->
  248. call({make_permanent, Vsn}).
  249. %%-----------------------------------------------------------------
  250. %% Purpose: Reboots the system from an old release.
  251. %%-----------------------------------------------------------------
  252. reboot_old_release(Vsn) ->
  253. call({reboot_old_release, Vsn}).
  254. %%-----------------------------------------------------------------
  255. %% Purpose: Deletes all files and directories used by the release
  256. %% version, that are not used by any other release.
  257. %% The release must not be permanent.
  258. %% Returns: ok | {error, Reason}
  259. %% Reason = {permanent, Vsn} |
  260. %%-----------------------------------------------------------------
  261. remove_release(Vsn) ->
  262. call({remove_release, Vsn}).
  263. %%-----------------------------------------------------------------
  264. %% Args: RelFile = string()
  265. %% Libs = [{Lib, LibVsn, Dir}]
  266. %% Lib = LibVsn = Dir = string()
  267. %% Purpose: Tells the release handler that a release has been
  268. %% unpacked, without using the function unpack_release/1.
  269. %% RelFile is an absolute file name including the extension
  270. %% .rel.
  271. %% The release dir will be created. The necessary files can
  272. %% be installed by calling install_file/2.
  273. %% The release_handler remebers where all libs are located.
  274. %% If remove_release is called later,
  275. %% those libs are removed as well (if no other releases uses
  276. %% them).
  277. %% Returns: ok | {error, Reason}
  278. %%-----------------------------------------------------------------
  279. set_unpacked(RelFile, LibDirs) ->
  280. call({set_unpacked, RelFile, LibDirs}).
  281. %%-----------------------------------------------------------------
  282. %% Args: Vsn = string()
  283. %% Purpose: Makes it possible to handle removal of releases
  284. %% outside the release_handler.
  285. %% This function won't delete any files at all.
  286. %% Returns: ok | {error, Reason}
  287. %%-----------------------------------------------------------------
  288. set_removed(Vsn) ->
  289. call({set_removed, Vsn}).
  290. %%-----------------------------------------------------------------
  291. %% Purpose: Makes it possible to install the start.boot,
  292. %% sys.config and relup files if they are not part of a
  293. %% standard release package. May be used to
  294. %% install files that are generated, before install_release
  295. %% is called.
  296. %% Returns: ok | {error, {no_such_release, Vsn}}
  297. %%-----------------------------------------------------------------
  298. install_file(Vsn, File) when is_list(File) ->
  299. call({install_file, File, Vsn}).
  300. %%-----------------------------------------------------------------
  301. %% Returns: [{Name, Vsn, [LibName], Status}]
  302. %% Status = unpacked | current | permanent | old
  303. %%-----------------------------------------------------------------
  304. which_releases() ->
  305. call(which_releases).
  306. %%-----------------------------------------------------------------
  307. %% Returns: [{Name, Vsn, [LibName], Status}]
  308. %% Status = unpacked | current | permanent | old
  309. %%-----------------------------------------------------------------
  310. which_releases(Status) ->
  311. Releases = which_releases(),
  312. get_releases_with_status(Releases, Status, []).
  313. %%-----------------------------------------------------------------
  314. %% check_script(Script, LibDirs) -> ok | {error, Reason}
  315. %%-----------------------------------------------------------------
  316. check_script(Script, LibDirs) ->
  317. release_handler_1:check_script(Script, LibDirs).
  318. %%-----------------------------------------------------------------
  319. %% eval_script(Script, Apps, LibDirs, NewLibs, Opts) ->
  320. %% {ok, UnPurged} |
  321. %% restart_emulator |
  322. %% {error, Error}
  323. %% {'EXIT', Reason}
  324. %% If sync_nodes is present, the calling process must have called
  325. %% net_kernel:monitor_nodes(true) before calling this function.
  326. %% No! No other process than the release_handler can ever call this
  327. %% function, if sync_nodes is used.
  328. %%
  329. %% LibDirs is a list of all applications, while NewLibs is a list of
  330. %% applications that have changed version between the current and the
  331. %% new release.
  332. %% -----------------------------------------------------------------
  333. eval_script(Script, Apps, LibDirs, NewLibs, Opts) ->
  334. catch release_handler_1:eval_script(Script, Apps, LibDirs, NewLibs, Opts).
  335. %%-----------------------------------------------------------------
  336. %% Func: create_RELEASES(Root, RelFile, LibDirs) -> ok | {error, Reason}
  337. %% Types: Root = RelFile = string()
  338. %% Purpose: Creates an initial RELEASES file.
  339. %%-----------------------------------------------------------------
  340. create_RELEASES([Root, RelFile | LibDirs]) ->
  341. create_RELEASES(Root, filename:join(Root, "releases"), RelFile, LibDirs).
  342. create_RELEASES(Root, RelFile) ->
  343. create_RELEASES(Root, filename:join(Root, "releases"), RelFile, []).
  344. create_RELEASES(Root, RelDir, RelFile, LibDirs) ->
  345. case catch check_rel(Root, RelFile, LibDirs, false) of
  346. {error, Reason } ->
  347. {error, Reason};
  348. Rel ->
  349. Rel2 = Rel#release{status = permanent},
  350. catch write_releases(RelDir, [Rel2], false)
  351. end.
  352. %%-----------------------------------------------------------------
  353. %% Func: upgrade_app(App, Dir) -> {ok, Unpurged}
  354. %% | restart_emulator
  355. %% | {error, Error}
  356. %% Types:
  357. %% App = atom()
  358. %% Dir = string() assumed to be application directory, the code
  359. %% located under Dir/ebin
  360. %% Purpose: Upgrade to the version in Dir according to an appup file
  361. %%-----------------------------------------------------------------
  362. upgrade_app(App, NewDir) ->
  363. try upgrade_script(App, NewDir) of
  364. {ok, NewVsn, Script} ->
  365. eval_appup_script(App, NewVsn, NewDir, Script)
  366. catch
  367. throw:Reason ->
  368. {error, Reason}
  369. end.
  370. %%-----------------------------------------------------------------
  371. %% Func: downgrade_app(App, Dir)
  372. %% downgrade_app(App, Vsn, Dir) -> {ok, Unpurged}
  373. %% | restart_emulator
  374. %% | {error, Error}
  375. %% Types:
  376. %% App = atom()
  377. %% Vsn = string(), may be omitted if Dir == App-Vsn
  378. %% Dir = string() assumed to be application directory, the code
  379. %% located under Dir/ebin
  380. %% Purpose: Downgrade from the version in Dir according to an appup file
  381. %% located in the ebin dir of the _current_ version
  382. %%-----------------------------------------------------------------
  383. downgrade_app(App, OldDir) ->
  384. case string:lexemes(filename:basename(OldDir), "-") of
  385. [_AppS, OldVsn] ->
  386. downgrade_app(App, OldVsn, OldDir);
  387. _ ->
  388. {error, {unknown_version, App}}
  389. end.
  390. downgrade_app(App, OldVsn, OldDir) ->
  391. try downgrade_script(App, OldVsn, OldDir) of
  392. {ok, Script} ->
  393. eval_appup_script(App, OldVsn, OldDir, Script)
  394. catch
  395. throw:Reason ->
  396. {error, Reason}
  397. end.
  398. upgrade_script(App, NewDir) ->
  399. OldVsn = ensure_running(App),
  400. OldDir = code:lib_dir(App),
  401. {NewVsn, Script} = find_script(App, NewDir, OldVsn, up),
  402. OldAppl = read_app(App, OldVsn, OldDir),
  403. NewAppl = read_app(App, NewVsn, NewDir),
  404. case systools_rc:translate_scripts(up,
  405. [Script],[NewAppl],[OldAppl]) of
  406. {ok, LowLevelScript} ->
  407. {ok, NewVsn, LowLevelScript};
  408. {error, _SystoolsRC, Reason} ->
  409. throw(Reason)
  410. end.
  411. downgrade_script(App, OldVsn, OldDir) ->
  412. NewVsn = ensure_running(App),
  413. NewDir = code:lib_dir(App),
  414. {NewVsn, Script} = find_script(App, NewDir, OldVsn, down),
  415. OldAppl = read_app(App, OldVsn, OldDir),
  416. NewAppl = read_app(App, NewVsn, NewDir),
  417. case systools_rc:translate_scripts(dn,
  418. [Script],[OldAppl],[NewAppl]) of
  419. {ok, LowLevelScript} ->
  420. {ok, LowLevelScript};
  421. {error, _SystoolsRC, Reason} ->
  422. throw(Reason)
  423. end.
  424. eval_appup_script(App, ToVsn, ToDir, Script) ->
  425. EnvBefore = application_controller:prep_config_change(),
  426. AppSpecL = read_appspec(App, ToDir),
  427. Res = release_handler_1:eval_script(Script,
  428. [], % [AppSpec]
  429. [{App, ToVsn, ToDir}],
  430. [{App, ToVsn, ToDir}],
  431. []), % [Opt]
  432. case Res of
  433. {ok, _Unpurged} ->
  434. application_controller:change_application_data(AppSpecL,[]),
  435. application_controller:config_change(EnvBefore);
  436. _Res ->
  437. ignore
  438. end,
  439. Res.
  440. ensure_running(App) ->
  441. case lists:keysearch(App, 1, application:which_applications()) of
  442. {value, {_App, _Descr, Vsn}} ->
  443. Vsn;
  444. false ->
  445. throw({app_not_running, App})
  446. end.
  447. find_script(App, Dir, OldVsn, UpOrDown) ->
  448. Appup = filename:join([Dir, "ebin", atom_to_list(App)++".appup"]),
  449. case file:consult(Appup) of
  450. {ok, [{NewVsn, UpFromScripts, DownToScripts}]} ->
  451. Scripts = case UpOrDown of
  452. up -> UpFromScripts;
  453. down -> DownToScripts
  454. end,
  455. case systools_relup:appup_search_for_version(OldVsn,Scripts) of
  456. {ok,Script} ->
  457. {NewVsn,Script};
  458. error ->
  459. throw({version_not_in_appup, OldVsn})
  460. end;
  461. {error, enoent} ->
  462. throw(no_appup_found);
  463. {error, Reason} ->
  464. throw(Reason)
  465. end.
  466. read_app(App, Vsn, Dir) ->
  467. AppS = atom_to_list(App),
  468. Path = [filename:join(Dir, "ebin")],
  469. case systools_make:read_application(AppS, Vsn, Path, []) of
  470. {ok, Appl} ->
  471. Appl;
  472. {error, {not_found, _AppFile}} ->
  473. throw({no_app_found, Vsn, Dir});
  474. {error, Reason} ->
  475. throw(Reason)
  476. end.
  477. read_appspec(App, Dir) ->
  478. AppS = atom_to_list(App),
  479. Path = [filename:join(Dir, "ebin")],
  480. case file:path_consult(Path, AppS++".app") of
  481. {ok, AppSpecL, _File} ->
  482. AppSpecL;
  483. {error, Reason} ->
  484. throw(Reason)
  485. end.
  486. %%-----------------------------------------------------------------
  487. %% call(Request) -> Term
  488. %%-----------------------------------------------------------------
  489. call(Req) ->
  490. gen_server:call(release_handler, Req, infinity).
  491. %%-----------------------------------------------------------------
  492. %% Call-back functions from gen_server
  493. %%-----------------------------------------------------------------
  494. init([]) ->
  495. {ok, [[Root]]} = init:get_argument(root),
  496. {CliDir, Masters} = is_client(),
  497. ReleaseDir =
  498. case application:get_env(sasl, releases_dir) of
  499. undefined ->
  500. case os:getenv("RELDIR") of
  501. false ->
  502. if
  503. CliDir == false ->
  504. filename:join([Root, "releases"]);
  505. true ->
  506. filename:join([CliDir, "releases"])
  507. end;
  508. RELDIR ->
  509. RELDIR
  510. end;
  511. {ok, Dir} ->
  512. Dir
  513. end,
  514. Releases =
  515. case consult(filename:join(ReleaseDir, "RELEASES"), Masters) of
  516. {ok, [Term]} ->
  517. transform_release(ReleaseDir, Term, Masters);
  518. _ ->
  519. {Name, Vsn} = init:script_id(),
  520. [#release{name = Name, vsn = Vsn, status = permanent}]
  521. end,
  522. StartPrg =
  523. case application:get_env(start_prg) of
  524. {ok, Found2} when is_list(Found2) ->
  525. {do_check, Found2};
  526. _ ->
  527. {no_check, filename:join([Root, "bin", "start"])}
  528. end,
  529. Static =
  530. case application:get_env(static_emulator) of
  531. {ok, SFlag} when is_atom(SFlag) -> SFlag;
  532. _ -> false
  533. end,
  534. {ok, #state{root = Root, rel_dir = ReleaseDir, releases = Releases,
  535. start_prg = StartPrg, masters = Masters,
  536. client_dir = CliDir, static_emulator = Static}}.
  537. handle_call({unpack_release, ReleaseName}, _From, S)
  538. when S#state.masters == false ->
  539. case catch do_unpack_release(S#state.root, S#state.rel_dir,
  540. ReleaseName, S#state.releases) of
  541. {ok, NewReleases, Vsn} ->
  542. {reply, {ok, Vsn}, S#state{releases = NewReleases}};
  543. {error, Reason} ->
  544. {reply, {error, Reason}, S};
  545. {'EXIT', Reason} ->
  546. {reply, {error, Reason}, S}
  547. end;
  548. handle_call({unpack_release, _ReleaseName}, _From, S) ->
  549. {reply, {error, client_node}, S};
  550. handle_call({check_install_release, Vsn, Purge}, _From, S) ->
  551. case catch do_check_install_release(S#state.rel_dir,
  552. Vsn,
  553. S#state.releases,
  554. S#state.masters,
  555. Purge) of
  556. {ok, CurrentVsn, Descr} ->
  557. {reply, {ok, CurrentVsn, Descr}, S};
  558. {error, Reason} ->
  559. {reply, {error, Reason}, S};
  560. {'EXIT', Reason} ->
  561. {reply, {error, Reason}, S}
  562. end;
  563. handle_call({install_release, Vsn, ErrorAction, Opts}, From, S) ->
  564. NS = resend_sync_nodes(S),
  565. case catch do_install_release(S, Vsn, Opts) of
  566. {ok, NewReleases, [], CurrentVsn, Descr} ->
  567. {reply, {ok, CurrentVsn, Descr}, NS#state{releases=NewReleases}};
  568. {ok, NewReleases, Unpurged, CurrentVsn, Descr} ->
  569. Timer =
  570. case S#state.timer of
  571. undefined ->
  572. {ok, Ref} = timer:send_interval(?timeout, timeout),
  573. Ref;
  574. Ref -> Ref
  575. end,
  576. NewS = NS#state{releases = NewReleases, unpurged = Unpurged,
  577. timer = Timer},
  578. {reply, {ok, CurrentVsn, Descr}, NewS};
  579. {error, Reason} ->
  580. {reply, {error, Reason}, NS};
  581. {restart_emulator, CurrentVsn, Descr} ->
  582. gen_server:reply(From, {ok, CurrentVsn, Descr}),
  583. init:reboot(),
  584. {noreply, NS};
  585. {restart_new_emulator, CurrentVsn, Descr} ->
  586. gen_server:reply(From, {continue_after_restart, CurrentVsn, Descr}),
  587. init:reboot(),
  588. {noreply, NS};
  589. {'EXIT', Reason} ->
  590. io:format("release_handler:"
  591. "install_release(Vsn=~tp Opts=~tp) failed, "
  592. "Reason=~tp~n", [Vsn, Opts, Reason]),
  593. gen_server:reply(From, {error, Reason}),
  594. case ErrorAction of
  595. restart ->
  596. init:restart();
  597. reboot ->
  598. init:reboot()
  599. end,
  600. {noreply, NS}
  601. end;
  602. handle_call({make_permanent, Vsn}, _From, S) ->
  603. case catch do_make_permanent(S, Vsn) of
  604. {ok, Releases, Unpurged} ->
  605. {reply, ok, S#state{releases = Releases, unpurged = Unpurged}};
  606. {error, Reason} ->
  607. {reply, {error, Reason}, S};
  608. {'EXIT', Reason} ->
  609. {reply, {error, Reason}, S}
  610. end;
  611. handle_call({reboot_old_release, Vsn}, From, S) ->
  612. case catch do_reboot_old_release(S, Vsn) of
  613. ok ->
  614. gen_server:reply(From, ok),
  615. init:reboot(),
  616. {noreply, S};
  617. {error, Reason} ->
  618. {reply, {error, Reason}, S};
  619. {'EXIT', Reason} ->
  620. {reply, {error, Reason}, S}
  621. end;
  622. handle_call({remove_release, Vsn}, _From, S)
  623. when S#state.masters == false ->
  624. case catch do_remove_release(S#state.root, S#state.rel_dir,
  625. Vsn, S#state.releases) of
  626. {ok, NewReleases} ->
  627. {reply, ok, S#state{releases = NewReleases}};
  628. {error, Reason} ->
  629. {reply, {error, Reason}, S};
  630. {'EXIT', Reason} ->
  631. {reply, {error, Reason}, S}
  632. end;
  633. handle_call({remove_release, _Vsn}, _From, S) ->
  634. {reply, {error, client_node}, S};
  635. handle_call({set_unpacked, RelFile, LibDirs}, _From, S) ->
  636. Root = S#state.root,
  637. case catch do_set_unpacked(Root, S#state.rel_dir, RelFile,
  638. LibDirs, S#state.releases,
  639. S#state.masters) of
  640. {ok, NewReleases, Vsn} ->
  641. {reply, {ok, Vsn}, S#state{releases = NewReleases}};
  642. {error, Reason} ->
  643. {reply, {error, Reason}, S};
  644. {'EXIT', Reason} ->
  645. {reply, {error, Reason}, S}
  646. end;
  647. handle_call({set_removed, Vsn}, _From, S) ->
  648. case catch do_set_removed(S#state.rel_dir, Vsn,
  649. S#state.releases,
  650. S#state.masters) of
  651. {ok, NewReleases} ->
  652. {reply, ok, S#state{releases = NewReleases}};
  653. {error, Reason} ->
  654. {reply, {error, Reason}, S};
  655. {'EXIT', Reason} ->
  656. {reply, {error, Reason}, S}
  657. end;
  658. handle_call({install_file, File, Vsn}, _From, S) ->
  659. Reply =
  660. case lists:keysearch(Vsn, #release.vsn, S#state.releases) of
  661. {value, _} ->
  662. Dir = filename:join([S#state.rel_dir, Vsn]),
  663. catch copy_file(File, Dir, S#state.masters);
  664. _ ->
  665. {error, {no_such_release, Vsn}}
  666. end,
  667. {reply, Reply, S};
  668. handle_call(which_releases, _From, S) ->
  669. Reply = lists:map(fun(#release{name = Name, vsn = Vsn, libs = Libs,
  670. status = Status}) ->
  671. {Name, Vsn, mk_lib_name(Libs), Status}
  672. end, S#state.releases),
  673. {reply, Reply, S}.
  674. mk_lib_name([{LibName, Vsn, _Dir} | T]) ->
  675. [lists:concat([LibName, "-", Vsn]) | mk_lib_name(T)];
  676. mk_lib_name([]) -> [].
  677. handle_info(timeout, S) ->
  678. case soft_purge(S#state.unpurged) of
  679. [] ->
  680. _ = timer:cancel(S#state.timer),
  681. {noreply, S#state{unpurged = [], timer = undefined}};
  682. Unpurged ->
  683. {noreply, S#state{unpurged = Unpurged}}
  684. end;
  685. handle_info({sync_nodes, Id, Node}, S) ->
  686. PSN = S#state.pre_sync_nodes,
  687. {noreply, S#state{pre_sync_nodes = [{sync_nodes, Id, Node} | PSN]}};
  688. handle_info(Msg, State) ->
  689. error_logger:info_msg("release_handler: got unknown message: ~p~n", [Msg]),
  690. {noreply, State}.
  691. terminate(_Reason, _State) ->
  692. ok.
  693. handle_cast(_Msg, State) ->
  694. {noreply, State}.
  695. code_change(_OldVsn, State, _Extra) ->
  696. {ok, State}.
  697. %%%-----------------------------------------------------------------
  698. %%% Internal functions
  699. %%%-----------------------------------------------------------------
  700. is_client() ->
  701. case application:get_env(masters) of
  702. {ok, Masters} ->
  703. Alive = is_alive(),
  704. case atom_list(Masters) of
  705. true when Alive == true ->
  706. case application:get_env(client_directory) of
  707. {ok, ClientDir} ->
  708. case int_list(ClientDir) of
  709. true ->
  710. {ClientDir, Masters};
  711. _ ->
  712. exit({bad_parameter, client_directory,
  713. ClientDir})
  714. end;
  715. _ ->
  716. {false, false}
  717. end;
  718. _ ->
  719. exit({bad_parameter, masters, Masters})
  720. end;
  721. _ ->
  722. {false, false}
  723. end.
  724. atom_list([A|T]) when is_atom(A) -> atom_list(T);
  725. atom_list([]) -> true;
  726. atom_list(_) -> false.
  727. int_list([I|T]) when is_integer(I) -> int_list(T);
  728. int_list([]) -> true;
  729. int_list(_) -> false.
  730. resend_sync_nodes(S) ->
  731. lists:foreach(fun(Msg) -> self() ! Msg end, S#state.pre_sync_nodes),
  732. S#state{pre_sync_nodes = []}.
  733. soft_purge(Unpurged) ->
  734. lists:filter(fun({Mod, _PostPurgeMethod}) ->
  735. case code:soft_purge(Mod) of
  736. true -> false; % No proc left, don't remember Mod
  737. false -> true % Still proc left, remember it
  738. end
  739. end,
  740. Unpurged).
  741. brutal_purge(Unpurged) ->
  742. lists:filter(fun({Mod, brutal_purge}) -> code:purge(Mod), false;
  743. (_) -> true
  744. end,
  745. Unpurged).
  746. %%-----------------------------------------------------------------
  747. %% The release package is a RelName.tar.Z (.tar on non unix) file
  748. %% with the following contents:
  749. %% - RelName.rel == {release, {Name, Vsn}, {erts, EVsn}, [lib()]}
  750. %% - <files> according to [lib()]
  751. %% - lib() = {LibName, LibVsn}
  752. %% In the Dir, there exists a file called RELEASES, which contains
  753. %% a [{Vsn, {erts, EVsn}, {libs, [{LibName, LibVsn, LibDir}]}}].
  754. %% Note that RelDir is an absolute directory name !
  755. %% Note that this function is not executed by a client
  756. %% release_handler.
  757. %%-----------------------------------------------------------------
  758. do_unpack_release(Root, RelDir, ReleaseName, Releases) ->
  759. Tar = filename:join(RelDir, ReleaseName ++ ".tar.gz"),
  760. do_check_file(Tar, regular),
  761. Rel = ReleaseName ++ ".rel",
  762. _ = extract_rel_file(filename:join("releases", Rel), Tar, Root),
  763. RelFile = filename:join(RelDir, Rel),
  764. Release = check_rel(Root, RelFile, false),
  765. #release{vsn = Vsn} = Release,
  766. case lists:keysearch(Vsn, #release.vsn, Releases) of
  767. {value, _} -> throw({error, {existing_release, Vsn}});
  768. _ -> ok
  769. end,
  770. extract_tar(Root, Tar),
  771. NewReleases = [Release#release{status = unpacked} | Releases],
  772. write_releases(RelDir, NewReleases, false),
  773. %% Keeping this for backwards compatibility reasons with older
  774. %% systools:make_tar, where there is no copy of the .rel file in
  775. %% the releases/<vsn> dir. See OTP-9746.
  776. Dir = filename:join([RelDir, Vsn]),
  777. copy_file(RelFile, Dir, false),
  778. %% Clean release
  779. _ = file:delete(Tar),
  780. _ = file:delete(RelFile),
  781. {ok, NewReleases, Vsn}.
  782. check_rel(Root, RelFile, Masters) ->
  783. check_rel(Root, RelFile, [], Masters).
  784. check_rel(Root, RelFile, LibDirs, Masters) ->
  785. case consult(RelFile, Masters) of
  786. {ok, [RelData]} ->
  787. check_rel_data(RelData, Root, LibDirs, Masters);
  788. {ok, _} ->
  789. throw({error, {bad_rel_file, RelFile}});
  790. {error, Reason} when is_tuple(Reason) ->
  791. throw({error, {bad_rel_file, RelFile}});
  792. {error, FileError} -> % FileError is posix atom | no_master
  793. throw({error, {FileError, RelFile}})
  794. end.
  795. check_rel_data({release, {Name, Vsn}, {erts, EVsn}, Libs}, Root, LibDirs,
  796. Masters) ->
  797. Libs2 =
  798. lists:map(fun(LibSpec) ->
  799. Lib = element(1, LibSpec),
  800. LibVsn = element(2, LibSpec),
  801. LibName = lists:concat([Lib, "-", LibVsn]),
  802. LibDir =
  803. case lists:keysearch(Lib, 1, LibDirs) of
  804. {value, {_Lib, _Vsn, Dir}} ->
  805. Path = filename:join(Dir,LibName),
  806. check_path(Path, Masters),
  807. Path;
  808. _ ->
  809. filename:join([Root, "lib", LibName])
  810. end,
  811. {Lib, LibVsn, LibDir}
  812. end,
  813. Libs),
  814. #release{name = Name, vsn = Vsn, erts_vsn = EVsn,
  815. libs = Libs2, status = unpacking};
  816. check_rel_data(RelData, _Root, _LibDirs, _Masters) ->
  817. throw({error, {bad_rel_data, RelData}}).
  818. check_path(Path) ->
  819. check_path_response(Path, file:read_file_info(Path)).
  820. check_path(Path, false) -> check_path(Path);
  821. check_path(Path, Masters) -> check_path_master(Masters, Path).
  822. %%-----------------------------------------------------------------
  823. %% check_path at any master node.
  824. %% If the path does not exist or is not a directory
  825. %% at one node it should not exist at any other node either.
  826. %%-----------------------------------------------------------------
  827. check_path_master([Master|Ms], Path) ->
  828. case rpc:call(Master, file, read_file_info, [Path]) of
  829. {badrpc, _} -> consult_master(Ms, Path);
  830. Res -> check_path_response(Path, Res)
  831. end;
  832. check_path_master([], _Path) ->
  833. {error, no_master}.
  834. check_path_response(_Path, {ok, Info}) when Info#file_info.type==directory ->
  835. ok;
  836. check_path_response(Path, {ok, _Info}) ->
  837. throw({error, {not_a_directory, Path}});
  838. check_path_response(Path, {error, _Reason}) ->
  839. throw({error, {no_such_directory, Path}}).
  840. do_check_install_release(RelDir, Vsn, Releases, Masters, Purge) ->
  841. case lists:keysearch(Vsn, #release.vsn, Releases) of
  842. {value, #release{status = current}} ->
  843. {error, {already_installed, Vsn}};
  844. {value, Release} ->
  845. LatestRelease = get_latest_release(Releases),
  846. VsnDir = filename:join([RelDir, Vsn]),
  847. check_file(filename:join(VsnDir, "start.boot"), regular, Masters),
  848. IsRelup = check_opt_file(filename:join(VsnDir, "relup"), regular, Masters),
  849. check_opt_file(filename:join(VsnDir, "sys.config"), regular, Masters),
  850. %% Check that all required libs are present
  851. Libs = Release#release.libs,
  852. lists:foreach(fun({_Lib, _LibVsn, LibDir}) ->
  853. check_file(LibDir, directory, Masters),
  854. Ebin = filename:join(LibDir, "ebin"),
  855. check_file(Ebin, directory, Masters)
  856. end,
  857. Libs),
  858. if
  859. IsRelup ->
  860. case get_rh_script(LatestRelease, Release, RelDir, Masters) of
  861. {ok, {CurrentVsn, Descr, Script}} ->
  862. case catch check_script(Script, Libs) of
  863. {ok,SoftPurgeMods} when Purge=:=true ->
  864. %% Get modules with brutal_purge
  865. %% instructions, but that can be
  866. %% soft purged
  867. {ok,BrutalPurgeMods} =
  868. release_handler_1:check_old_processes(
  869. Script,brutal_purge),
  870. lists:foreach(
  871. fun(Mod) ->
  872. catch erlang:purge_module(Mod)
  873. end,
  874. SoftPurgeMods ++ BrutalPurgeMods),
  875. {ok, CurrentVsn, Descr};
  876. {ok,_} ->
  877. {ok, CurrentVsn, Descr};
  878. Else ->
  879. Else
  880. end;
  881. Error ->
  882. Error
  883. end;
  884. true ->
  885. {ok, Vsn, ""}
  886. end;
  887. _ ->
  888. {error, {no_such_release, Vsn}}
  889. end.
  890. do_install_release(#state{start_prg = StartPrg,
  891. root = RootDir,
  892. rel_dir = RelDir, releases = Releases,
  893. masters = Masters,
  894. static_emulator = Static},
  895. Vsn, Opts) ->
  896. case lists:keysearch(Vsn, #release.vsn, Releases) of
  897. {value, #release{status = current}} ->
  898. {error, {already_installed, Vsn}};
  899. {value, Release} ->
  900. LatestRelease = get_latest_release(Releases),
  901. case get_rh_script(LatestRelease, Release, RelDir, Masters) of
  902. {ok, {_CurrentVsn, _Descr, [restart_new_emulator|_Script]}}
  903. when Static == true ->
  904. throw(static_emulator);
  905. {ok, {CurrentVsn, Descr, [restart_new_emulator|_Script]}} ->
  906. %% This will only happen if the upgrade includes
  907. %% an emulator upgrade (and it is not a downgrade)
  908. %% - then the new emulator must be started before
  909. %% new code can be loaded.
  910. %% Create a temporary release which includes new
  911. %% emulator, kernel, stdlib and sasl - and old
  912. %% versions of other applications.
  913. {TmpVsn,TmpRelease} =
  914. new_emulator_make_tmp_release(LatestRelease,Release,
  915. RelDir,Opts,Masters),
  916. NReleases = [TmpRelease|Releases],
  917. %% Then uppgrade to the temporary release.
  918. %% The rest of the upgrade will continue after the restart
  919. prepare_restart_new_emulator(StartPrg, RootDir,
  920. RelDir, TmpVsn, TmpRelease,
  921. NReleases, Masters),
  922. {restart_new_emulator, CurrentVsn, Descr};
  923. {ok, {CurrentVsn, Descr, Script}} ->
  924. %% In case there has been an emulator upgrade,
  925. %% remove the temporary release
  926. NReleases =
  927. new_emulator_rm_tmp_release(
  928. LatestRelease#release.vsn,
  929. LatestRelease#release.erts_vsn,
  930. Vsn,RelDir,Releases,Masters),
  931. %% Then execute the relup script
  932. mon_nodes(true),
  933. EnvBefore = application_controller:prep_config_change(),
  934. Apps = change_appl_data(RelDir, Release, Masters),
  935. LibDirs = Release#release.libs,
  936. NewLibs = get_new_libs(LatestRelease#release.libs,
  937. Release#release.libs),
  938. case eval_script(Script, Apps, LibDirs, NewLibs, Opts) of
  939. {ok, Unpurged} ->
  940. application_controller:config_change(EnvBefore),
  941. mon_nodes(false),
  942. NReleases1 = set_status(Vsn, current, NReleases),
  943. {ok, NReleases1, Unpurged, CurrentVsn, Descr};
  944. restart_emulator when Static == true ->
  945. throw(static_emulator);
  946. restart_emulator ->
  947. mon_nodes(false),
  948. prepare_restart_new_emulator(StartPrg, RootDir,
  949. RelDir, Vsn, Release,
  950. NReleases, Masters),
  951. {restart_emulator, CurrentVsn, Descr};
  952. Else ->
  953. application_controller:config_change(EnvBefore),
  954. mon_nodes(false),
  955. Else
  956. end;
  957. Error ->
  958. Error
  959. end;
  960. _ ->
  961. {error, {no_such_release, Vsn}}
  962. end.
  963. new_emulator_make_tmp_release(CurrentRelease,ToRelease,RelDir,Opts,Masters) ->
  964. CurrentVsn = CurrentRelease#release.vsn,
  965. ToVsn = ToRelease#release.vsn,
  966. TmpVsn = ?tmp_vsn(CurrentVsn),
  967. case get_base_libs(ToRelease#release.libs) of
  968. {ok,{Kernel,Stdlib,Sasl},_} ->
  969. case get_base_libs(CurrentRelease#release.libs) of
  970. {ok,_,RestLibs} ->
  971. TmpErtsVsn = ToRelease#release.erts_vsn,
  972. TmpLibs = [Kernel,Stdlib,Sasl|RestLibs],
  973. TmpRelease = CurrentRelease#release{vsn=TmpVsn,
  974. erts_vsn=TmpErtsVsn,
  975. libs = TmpLibs,
  976. status = unpacked},
  977. new_emulator_make_hybrid_boot(CurrentVsn,ToVsn,TmpVsn,
  978. RelDir,Opts,Masters),
  979. new_emulator_make_hybrid_config(CurrentVsn,ToVsn,TmpVsn,
  980. RelDir,Masters),
  981. {TmpVsn,TmpRelease};
  982. {error,{missing,Missing}} ->
  983. throw({error,{missing_base_app,CurrentVsn,Missing}})
  984. end;
  985. {error,{missing,Missing}} ->
  986. throw({error,{missing_base_app,ToVsn,Missing}})
  987. end.
  988. %% Get kernel, stdlib and sasl libs,
  989. %% and also return the rest of the libs as a list.
  990. %% Return error if any of kernel, stdlib or sasl does not exist.
  991. get_base_libs(Libs) ->
  992. get_base_libs(Libs,undefined,undefined,undefined,[]).
  993. get_base_libs([{kernel,_,_}=Kernel|Libs],undefined,Stdlib,Sasl,Rest) ->
  994. get_base_libs(Libs,Kernel,Stdlib,Sasl,Rest);
  995. get_base_libs([{stdlib,_,_}=Stdlib|Libs],Kernel,undefined,Sasl,Rest) ->
  996. get_base_libs(Libs,Kernel,Stdlib,Sasl,Rest);
  997. get_base_libs([{sasl,_,_}=Sasl|Libs],Kernel,Stdlib,undefined,Rest) ->
  998. get_base_libs(Libs,Kernel,Stdlib,Sasl,Rest);
  999. get_base_libs([Lib|Libs],Kernel,Stdlib,Sasl,Rest) ->
  1000. get_base_libs(Libs,Kernel,Stdlib,Sasl,[Lib|Rest]);
  1001. get_base_libs([],undefined,_Stdlib,_Sasl,_Rest) ->
  1002. {error,{missing,kernel}};
  1003. get_base_libs([],_Kernel,undefined,_Sasl,_Rest) ->
  1004. {error,{missing,stdlib}};
  1005. get_base_libs([],_Kernel,_Stdlib,undefined,_Rest) ->
  1006. {error,{missing,sasl}};
  1007. get_base_libs([],Kernel,Stdlib,Sasl,Rest) ->
  1008. {ok,{Kernel,Stdlib,Sasl},lists:reverse(Rest)}.
  1009. new_emulator_make_hybrid_boot(CurrentVsn,ToVsn,TmpVsn,RelDir,Opts,Masters) ->
  1010. FromBootFile = filename:join([RelDir,CurrentVsn,"start.boot"]),
  1011. ToBootFile = filename:join([RelDir,ToVsn,"start.boot"]),
  1012. TmpBootFile = filename:join([RelDir,TmpVsn,"start.boot"]),
  1013. ensure_dir(TmpBootFile,Masters),
  1014. Args = [ToVsn,Opts],
  1015. {ok,FromBoot} = read_file(FromBootFile,Masters),
  1016. {ok,ToBoot} = read_file(ToBootFile,Masters),
  1017. case systools_make:make_hybrid_boot(TmpVsn,FromBoot,ToBoot,Args) of
  1018. {ok,TmpBoot} ->
  1019. write_file(TmpBootFile,TmpBoot,Masters);
  1020. {error,Reason} ->
  1021. throw({error,{could_not_create_hybrid_boot,Reason}})
  1022. end.
  1023. new_emulator_make_hybrid_config(CurrentVsn,ToVsn,TmpVsn,RelDir,Masters) ->
  1024. FromFile = filename:join([RelDir,CurrentVsn,"sys.config"]),
  1025. ToFile = filename:join([RelDir,ToVsn,"sys.config"]),
  1026. TmpFile = filename:join([RelDir,TmpVsn,"sys.config"]),
  1027. FromConfig =
  1028. case consult(FromFile,Masters) of
  1029. {ok,[FC]} ->
  1030. FC;
  1031. {error,Error1} ->
  1032. io:format("Warning: ~w can not read ~tp: ~tp~n",
  1033. [?MODULE,FromFile,Error1]),
  1034. []
  1035. end,
  1036. [Kernel,Stdlib,Sasl] =
  1037. case consult(ToFile,Masters) of
  1038. {ok,[ToConfig]} ->
  1039. [lists:keyfind(App,1,ToConfig) || App <- [kernel,stdlib,sasl]];
  1040. {error,Error2} ->
  1041. io:format("Warning: ~w can not read ~tp: ~tp~n",
  1042. [?MODULE,ToFile,Error2]),
  1043. [false,false,false]
  1044. end,
  1045. Config1 = replace_config(kernel,FromConfig,Kernel),
  1046. Config2 = replace_config(stdlib,Config1,Stdlib),
  1047. Config3 = replace_config(sasl,Config2,Sasl),
  1048. ConfigStr = io_lib:format("%% ~s~n~tp.~n",
  1049. [epp:encoding_to_string(utf8),Config3]),
  1050. write_file(TmpFile,unicode:characters_to_binary(ConfigStr),Masters).
  1051. %% Take the configuration for application App from the new config and
  1052. %% insert in the old config.
  1053. %% If no entry exists in the new config, then delete the entry (if it exists)
  1054. %% from the old config.
  1055. %% If entry exists in the new config, but not in the old config, then
  1056. %% add the entry.
  1057. replace_config(App,Config,false) ->
  1058. lists:keydelete(App,1,Config);
  1059. replace_config(App,Config,AppConfig) ->
  1060. lists:keystore(App,1,Config,AppConfig).
  1061. %% Remove all files related to the temporary release
  1062. new_emulator_rm_tmp_release(?tmp_vsn(_)=TmpVsn,EVsn,NewVsn,
  1063. RelDir,Releases,Masters) ->
  1064. case os:type() of
  1065. {win32, nt} ->
  1066. rename_tmp_service(EVsn,TmpVsn,NewVsn);
  1067. _ ->
  1068. ok
  1069. end,
  1070. remove_dir(filename:join(RelDir,TmpVsn),Masters),
  1071. lists:keydelete(TmpVsn,#release.vsn,Releases);
  1072. new_emulator_rm_tmp_release(_,_,_,_,Releases,_) ->
  1073. Releases.
  1074. %% Rename the tempoarary service (for erts ugprade) to the real ToVsn
  1075. rename_tmp_service(EVsn,TmpVsn,NewVsn) ->
  1076. FromName = hd(string:lexemes(atom_to_list(node()),"@")) ++ "_" ++ TmpVsn,
  1077. ToName = hd(string:lexemes(atom_to_list(node()),"@")) ++ "_" ++ NewVsn,
  1078. case erlsrv:get_service(EVsn,ToName) of
  1079. {error, _Error} ->
  1080. ok;
  1081. _Data ->
  1082. {ok,_} = erlsrv:remove_service(ToName),
  1083. ok
  1084. end,
  1085. rename_service(EVsn,FromName,ToName).
  1086. %% Rename a service and check that it succeeded
  1087. rename_service(EVsn,FromName,ToName) ->
  1088. case erlsrv:rename_service(EVsn,FromName,ToName) of
  1089. {ok,_} ->
  1090. case erlsrv:get_service(EVsn,ToName) of
  1091. {error,Error1} ->
  1092. throw({error,Error1});
  1093. _Data2 ->
  1094. ok
  1095. end;
  1096. Error2 ->
  1097. throw({error,{service_rename_failed, Error2}})
  1098. end.
  1099. %%% This code chunk updates the services in one of two ways,
  1100. %%% Either the emulator is restarted, in which case the old service
  1101. %%% is to be removed and the new enabled, or the emulator is NOT restarted
  1102. %%% in which case we try to rename the old service to the new name and try
  1103. %%% to update heart's view of what service we are really running.
  1104. do_make_services_permanent(PermanentVsn,Vsn, PermanentEVsn, EVsn) ->
  1105. PermName = hd(string:lexemes(atom_to_list(node()),"@"))
  1106. ++ "_" ++ PermanentVsn,
  1107. Name = hd(string:lexemes(atom_to_list(node()),"@"))
  1108. ++ "_" ++ Vsn,
  1109. case erlsrv:get_service(EVsn,Name) of
  1110. {error, _Error} ->
  1111. %% We probably do not need to replace services, just
  1112. %% rename.
  1113. case os:getenv("ERLSRV_SERVICE_NAME") == PermName of
  1114. true ->
  1115. rename_service(EVsn,PermName,Name),
  1116. %% The interfaces for doing this are
  1117. %% NOT published and may be subject to
  1118. %% change. Do NOT do this anywhere else!
  1119. os:putenv("ERLSRV_SERVICE_NAME", Name),
  1120. %% Restart heart port program, this
  1121. %% function is only to be used here.
  1122. heart:cycle();
  1123. false ->
  1124. throw({error,service_name_missmatch})
  1125. end;
  1126. Data ->
  1127. UpdData = erlsrv:new_service(Name, Data, []),
  1128. case erlsrv:store_service(EVsn,UpdData) of
  1129. ok ->
  1130. {ok,_} = erlsrv:disable_service(PermanentEVsn, PermName),
  1131. {ok,_} = erlsrv:enable_service(EVsn, Name),
  1132. {ok,_} = erlsrv:remove_service(PermName),
  1133. %%% Read comments about these above...
  1134. os:putenv("ERLSRV_SERVICE_NAME", Name),
  1135. ok = heart:cycle();
  1136. Error4 ->
  1137. throw(Error4)
  1138. end
  1139. end.
  1140. do_make_permanent(#state{releases = Releases,
  1141. rel_dir = RelDir, unpurged = Unpurged,
  1142. masters = Masters,
  1143. static_emulator = Static},
  1144. Vsn) ->
  1145. case lists:keysearch(Vsn, #release.vsn, Releases) of
  1146. {value, #release{erts_vsn = EVsn, status = Status}}
  1147. when Status /= unpacked, Status /= old, Status /= permanent ->
  1148. Dir = filename:join([RelDir, Vsn]),
  1149. Sys =
  1150. case catch check_file(filename:join(Dir, "sys.config"),
  1151. regular, Masters) of
  1152. ok -> filename:join(Dir, "sys");
  1153. _ -> false
  1154. end,
  1155. Boot = filename:join(Dir, "start.boot"),
  1156. check_file(Boot, regular, Masters),
  1157. set_permanent_files(RelDir, EVsn, Vsn, Masters, Static),
  1158. NewReleases = set_status(Vsn, permanent, Releases),
  1159. write_releases(RelDir, NewReleases, Masters),
  1160. case os:type() of
  1161. {win32, nt} ->
  1162. {value, PermanentRelease} =
  1163. lists:keysearch(permanent, #release.status,
  1164. Releases),
  1165. PermanentVsn = PermanentRelease#release.vsn,
  1166. PermanentEVsn = PermanentRelease#release.erts_vsn,
  1167. case catch do_make_services_permanent(PermanentVsn,
  1168. Vsn,
  1169. PermanentEVsn,
  1170. EVsn) of
  1171. {error,Reason} ->
  1172. throw({error,{service_update_failed, Reason}});
  1173. _ ->
  1174. ok
  1175. end;
  1176. _ ->
  1177. ok
  1178. end,
  1179. ok = init:make_permanent(filename:join(Dir, "start"), Sys),
  1180. {ok, NewReleases, brutal_purge(Unpurged)};
  1181. {value, #release{status = permanent}} ->
  1182. {ok, Releases, Unpurged};
  1183. {value, #release{status = Status}} ->
  1184. {error, {bad_status, Status}};
  1185. false ->
  1186. {error, {no_such_release, Vsn}}
  1187. end.
  1188. do_back_service(OldVersion, CurrentVersion,OldEVsn,CurrentEVsn) ->
  1189. NN = hd(string:lexemes(atom_to_list(node()),"@")),
  1190. OldName = NN ++ "_" ++ OldVersion,
  1191. CurrentName = NN ++ "_" ++ CurrentVersion,
  1192. UpdData = case erlsrv:get_service(CurrentEVsn,CurrentName) of
  1193. {error, Error} ->
  1194. throw({error,Error});
  1195. Data ->
  1196. erlsrv:new_service(OldName, Data, [])
  1197. end,
  1198. _ = case erlsrv:store_service(OldEVsn,UpdData) of
  1199. ok ->
  1200. {ok,_} = erlsrv:disable_service(CurrentEVsn,CurrentName),
  1201. {ok,_} = erlsrv:enable_service(OldEVsn,OldName);
  1202. Error2 ->
  1203. throw(Error2)
  1204. end,
  1205. OldErlSrv = filename:nativename(erlsrv:erlsrv(OldEVsn)),
  1206. CurrentErlSrv = filename:nativename(erlsrv:erlsrv(CurrentEVsn)),
  1207. case heart:set_cmd(CurrentErlSrv ++ " remove " ++ CurrentName ++
  1208. " & " ++ OldErlSrv ++ " start " ++ OldName) of
  1209. ok ->
  1210. ok;
  1211. Error3 ->
  1212. throw({error, {'heart:set_cmd() error', Error3}})
  1213. end.
  1214. do_reboot_old_release(#state{releases = Releases,
  1215. rel_dir = RelDir, masters = Masters,
  1216. static_emulator = Static},
  1217. Vsn) ->
  1218. case lists:keysearch(Vsn, #release.vsn, Releases) of
  1219. {value, #release{erts_vsn = EVsn, status = old}} ->
  1220. CurrentRunning = case os:type() of
  1221. {win32,nt} ->
  1222. %% Get the current release on NT
  1223. case lists:keysearch(permanent,
  1224. #release.status,
  1225. Releases) of
  1226. false ->
  1227. lists:keysearch(current,
  1228. #release.status,
  1229. Releases);
  1230. {value,CR} ->
  1231. CR
  1232. end;
  1233. _ ->
  1234. false
  1235. end,
  1236. set_permanent_files(RelDir, EVsn, Vsn, Masters, Static),
  1237. NewReleases = set_status(Vsn, permanent, Releases),
  1238. write_releases(RelDir, NewReleases, Masters),
  1239. case os:type() of
  1240. {win32,nt} ->
  1241. %% Edit up the services and set a reasonable heart
  1242. %% command
  1243. do_back_service(Vsn,CurrentRunning#release.vsn,EVsn,
  1244. CurrentRunning#release.erts_vsn);
  1245. _ ->
  1246. ok
  1247. end,
  1248. ok;
  1249. {value, #release{status = Status}} ->
  1250. {error, {bad_status, Status}};
  1251. false ->
  1252. {error, {no_such_release, Vsn}}
  1253. end.
  1254. %%-----------------------------------------------------------------
  1255. %% Depending of if the release_handler is running in normal, client or
  1256. %% client with static emulator the new system version is made permanent
  1257. %% in different ways.
  1258. %%-----------------------------------------------------------------
  1259. set_permanent_files(RelDir, EVsn, Vsn, false, _) ->
  1260. write_start(filename:join([RelDir, "start_erl.data"]),
  1261. EVsn ++ " " ++ Vsn,
  1262. false);
  1263. set_permanent_files(RelDir, EVsn, Vsn, Masters, false) ->
  1264. write_start(filename:join([RelDir, "start_erl.data"]),
  1265. EVsn ++ " " ++ Vsn,
  1266. Masters);
  1267. set_permanent_files(RelDir, _EVsn, Vsn, Masters, _Static) ->
  1268. VsnDir = filename:join([RelDir, Vsn]),
  1269. set_static_files(VsnDir, RelDir, Masters).
  1270. do_remove_service(Vsn) ->
  1271. %% Very unconditionally remove the service.
  1272. %% Note that the service could already have been removed when
  1273. %% making another release permanent.
  1274. ServiceName = hd(string:lexemes(atom_to_list(node()),"@"))
  1275. ++ "_" ++ Vsn,
  1276. case erlsrv:get_service(ServiceName) of
  1277. {error, _Error} ->
  1278. ok;
  1279. _Data ->
  1280. {ok,_} = erlsrv:remove_service(ServiceName),
  1281. ok
  1282. end.
  1283. do_remove_release(Root, RelDir, Vsn, Releases) ->
  1284. % Decide which libs should be removed
  1285. case lists:keysearch(Vsn, #release.vsn, Releases) of
  1286. {value, #release{status = permanent}} ->
  1287. {error, {permanent, Vsn}};
  1288. {value, #release{libs = RemoveLibs, vsn = Vsn, erts_vsn = EVsn}} ->
  1289. case os:type() of
  1290. {win32, nt} ->
  1291. do_remove_service(Vsn);
  1292. _ ->
  1293. ok
  1294. end,
  1295. NewReleases = lists:keydelete(Vsn, #release.vsn, Releases),
  1296. RemoveThese =
  1297. lists:foldl(fun(#release{libs = Libs}, Remove) ->
  1298. diff_dir(Remove, Libs)
  1299. end, RemoveLibs, NewReleases),
  1300. lists:foreach(fun({_Lib, _LVsn, LDir}) ->
  1301. remove_file(LDir)
  1302. end, RemoveThese),
  1303. remove_file(filename:join([RelDir, Vsn])),
  1304. case lists:keysearch(EVsn, #release.erts_vsn, NewReleases) of
  1305. {value, _} -> ok;
  1306. false -> % Remove erts library, no more references to it
  1307. remove_file(filename:join(Root, "erts-" ++ EVsn))
  1308. end,
  1309. write_releases(RelDir, NewReleases, false),
  1310. {ok, NewReleases};
  1311. false ->
  1312. {error, {no_such_release, Vsn}}
  1313. end.
  1314. do_set_unpacked(Root, RelDir, RelFile, LibDirs, Releases, Masters) ->
  1315. Release = check_rel(Root, RelFile, LibDirs, Masters),
  1316. #release{vsn = Vsn} = Release,
  1317. case lists:keysearch(Vsn, #release.vsn, Releases) of
  1318. {value, _} -> throw({error, {existing_release, Vsn}});
  1319. false -> ok
  1320. end,
  1321. NewReleases = [Release#release{status = unpacked} | Releases],
  1322. VsnDir = filename:join([RelDir, Vsn]),
  1323. make_dir(VsnDir, Masters),
  1324. write_releases(RelDir, NewReleases, Masters),
  1325. {ok, NewReleases, Vsn}.
  1326. do_set_removed(RelDir, Vsn, Releases, Masters) ->
  1327. case lists:keysearch(Vsn, #release.vsn, Releases) of
  1328. {value, #release{status = permanent}} ->
  1329. {error, {permanent, Vsn}};
  1330. {value, _} ->
  1331. NewReleases = lists:keydelete(Vsn, #release.vsn, Releases),
  1332. write_releases(RelDir, NewReleases, Masters),
  1333. {ok, NewReleases};
  1334. false ->
  1335. {error, {no_such_release, Vsn}}
  1336. end.
  1337. %%-----------------------------------------------------------------
  1338. %% A relup file consists of:
  1339. %% {Vsn, [{FromVsn, Descr, RhScript}], [{ToVsn, Descr, RhScript}]}.
  1340. %% It describes how to get to this release from previous releases,
  1341. %% and how to get from this release to previous releases.
  1342. %% We can get from a FromVsn that's a substring of CurrentVsn (e.g.
  1343. %% 1.1 is a substring of 1.1.1, but not 1.2), but when we get to
  1344. %% ToVsn, we must have an exact match.
  1345. %%
  1346. %% We do not put any semantics into the version strings, i.e. we
  1347. %% don't know if going from Vsn1 to Vsn2 represents a upgrade or
  1348. %% a downgrade. For both upgrades and downgrades, the relup file
  1349. %% is located in the directory of the latest version. Since we
  1350. %% do not which version is latest, we first suppose that ToVsn >
  1351. %% CurrentVsn, i.e. we perform an upgrade. If we don't find the
  1352. %% corresponding relup instructions, we check if it's possible to
  1353. %% downgrade from CurrentVsn to ToVsn.
  1354. %%-----------------------------------------------------------------
  1355. get_rh_script(#release{vsn = ?tmp_vsn(CurrentVsn)},
  1356. #release{vsn = ToVsn},
  1357. RelDir,
  1358. Masters) ->
  1359. {ok,{Vsn,Descr,[restart_new_emulator|Script]}} =
  1360. do_get_rh_script(CurrentVsn,ToVsn,RelDir,Masters),
  1361. {ok,{Vsn,Descr,Script}};
  1362. get_rh_script(#release{vsn = CurrentVsn},
  1363. #release{vsn = ToVsn},
  1364. RelDir,
  1365. Masters) ->
  1366. do_get_rh_script(CurrentVsn,ToVsn,RelDir,Masters).
  1367. do_get_rh_script(CurrentVsn, ToVsn, RelDir, Masters) ->
  1368. Relup = filename:join([RelDir, ToVsn, "relup"]),
  1369. case try_upgrade(ToVsn, CurrentVsn, Relup, Masters) of
  1370. {ok, RhScript} ->
  1371. {ok, RhScript};
  1372. _ ->
  1373. Relup2 = filename:join([RelDir, CurrentVsn,"relup"]),
  1374. case try_downgrade(ToVsn, CurrentVsn, Relup2, Masters) of
  1375. {ok, RhScript} ->
  1376. {ok, RhScript};
  1377. _ ->
  1378. throw({error, {no_matching_relup, ToVsn, CurrentVsn}})
  1379. end
  1380. end.
  1381. try_upgrade(ToVsn, CurrentVsn, Relup, Masters) ->
  1382. case consult(Relup, Masters) of
  1383. {ok, [{ToVsn, ListOfRhScripts, _}]} ->
  1384. case lists:keysearch(CurrentVsn, 1, ListOfRhScripts) of
  1385. {value, RhScript} ->
  1386. {ok, RhScript};
  1387. _ ->
  1388. error
  1389. end;
  1390. {ok, _} ->
  1391. throw({error, {bad_relup_file, Relup}});
  1392. {error, Reason} when is_tuple(Reason) ->
  1393. throw({error, {bad_relup_file, Relup}});
  1394. {error, enoent} ->
  1395. error;
  1396. {error, FileError} -> % FileError is posix atom | no_master
  1397. throw({error, {FileError, Relup}})
  1398. end.
  1399. try_downgrade(ToVsn, CurrentVsn, Relup, Masters) ->
  1400. case consult(Relup, Masters) of
  1401. {ok, [{CurrentVsn, _, ListOfRhScripts}]} ->
  1402. case lists:keysearch(ToVsn, 1, ListOfRhScripts) of
  1403. {value, RhScript} ->
  1404. {ok, RhScript};
  1405. _ ->
  1406. error
  1407. end;
  1408. {ok, _} ->
  1409. throw({error, {bad_relup_file, Relup}});
  1410. {error, Reason} when is_tuple(Reason) ->
  1411. throw({error, {bad_relup_file, Relup}});
  1412. {error, FileError} -> % FileError is posix atom | no_master
  1413. throw({error, {FileError, Relup}})
  1414. end.
  1415. %% Status = current | tmp_current | permanent
  1416. set_status(Vsn, Status, Releases) ->
  1417. lists:zf(fun(Release) when Release#release.vsn == Vsn,
  1418. Release#release.status == permanent ->
  1419. %% If a permanent rel is installed, it keeps its
  1420. %% permanent status (not changed to current).
  1421. %% The current becomes old though.
  1422. true;
  1423. (Release) when Release#release.vsn == Vsn ->
  1424. {true, Release#release{status = Status}};
  1425. (Release) when Release#release.status == Status ->
  1426. {true, Release#release{status = old}};
  1427. (_) ->
  1428. true
  1429. end, Releases).
  1430. get_latest_release(Releases) ->
  1431. case lists:keysearch(current, #release.status, Releases) of
  1432. {value, Release} ->
  1433. Release;
  1434. false ->
  1435. {value, Release} =
  1436. lists:keysearch(permanent, #release.status, Releases),
  1437. Release
  1438. end.
  1439. %% Returns: [{Lib, Vsn, Dir}] to be removed
  1440. diff_dir([H | T], L) ->
  1441. case memlib(H, L) of
  1442. true -> diff_dir(T, L);
  1443. false -> [H | diff_dir(T, L)]
  1444. end;
  1445. diff_dir([], _) -> [].
  1446. memlib({Lib, Vsn, _Dir}, [{Lib, Vsn, _Dir2} | _T]) -> true;
  1447. memlib(Lib, [_H | T]) -> memlib(Lib, T);
  1448. memlib(_Lib, []) -> false.
  1449. %% recursively remove file or directory
  1450. remove_file(File) ->
  1451. case file:read_link_info(File) of
  1452. {ok, Info} when Info#file_info.type==directory ->
  1453. case file:list_dir(File) of
  1454. {ok, Files} ->
  1455. lists:foreach(fun(File2) ->
  1456. remove_file(filename:join(File,File2))
  1457. end, Files),
  1458. case file:del_dir(File) of
  1459. ok -> ok;
  1460. {error, Reason} -> throw({error, Reason})
  1461. end;
  1462. {error, Reason} ->
  1463. throw({error, Reason})
  1464. end;
  1465. {ok, _Info} ->
  1466. case file:delete(File) of
  1467. ok -> ok;
  1468. {error, Reason} -> throw({error, Reason})
  1469. end;
  1470. {error, _Reason} ->
  1471. throw({error, {no_such_file, File}})
  1472. end.
  1473. do_write_file(File, Str) ->
  1474. do_write_file(File, Str, []).
  1475. do_write_file(File, Str, FileOpts) ->
  1476. case file:open(File, [write | FileOpts]) of
  1477. {ok, Fd} ->
  1478. io:put_chars(Fd, Str),
  1479. ok = file:close(Fd);
  1480. {error, Reason} ->
  1481. {error, {Reason, File}}
  1482. end.
  1483. %%-----------------------------------------------------------------
  1484. %% Change current applications (specifically, update their version,
  1485. %% description and env.)
  1486. %%-----------------------------------------------------------------
  1487. change_appl_data(RelDir, #release{vsn = Vsn}, Masters) ->
  1488. Dir = filename:join([RelDir, Vsn]),
  1489. BootFile = filename:join(Dir, "start.boot"),
  1490. case read_file(BootFile, Masters) of
  1491. {ok, Bin} ->
  1492. Config = case consult(filename:join(Dir, "sys.config"), Masters) of
  1493. {ok, [Conf]} -> Conf;
  1494. _ -> []
  1495. end,
  1496. Appls = get_appls(binary_to_term(Bin)),
  1497. case application_controller:change_application_data(Appls,Config) of
  1498. ok -> Appls;
  1499. {error, Reason} -> exit({change_appl_data, Reason})
  1500. end;
  1501. {error, _Reason} ->
  1502. throw({error, {no_such_file, BootFile}})
  1503. end.
  1504. %%-----------------------------------------------------------------
  1505. %% This function is dependent on the application functions and
  1506. %% the start script syntax.
  1507. %%-----------------------------------------------------------------
  1508. get_appls({script, _, Script}) -> get_appls(Script, []).
  1509. %% kernel is taken care of separately
  1510. get_appls([{kernelProcess, application_controller,
  1511. {application_controller, start, [App]}} |T], Res) ->
  1512. get_appls(T, [App | Res]);
  1513. %% other applications but kernel
  1514. get_appls([{apply, {application, load, [App]}} |T], Res) ->
  1515. get_appls(T, [App | Res]);
  1516. get_appls([_ | T], Res) ->
  1517. get_appls(T, Res);
  1518. get_appls([], Res) ->
  1519. Res.
  1520. mon_nodes(true) ->
  1521. ok = net_kernel:monitor_nodes(true);
  1522. mon_nodes(false) ->
  1523. ok = net_kernel:monitor_nodes(false),
  1524. flush().
  1525. flush() ->
  1526. receive
  1527. {nodedown, _} -> flush();
  1528. {nodeup, _} -> flush()
  1529. after
  1530. 0 -> ok
  1531. end.
  1532. prepare_restart_nt(#release{erts_vsn = EVsn, vsn = Vsn},
  1533. #release{erts_vsn = PermEVsn, vsn = PermVsn},
  1534. DataFileName) ->
  1535. CurrentServiceName = hd(string:lexemes(atom_to_list(node()),"@"))
  1536. ++ "_" ++ PermVsn,
  1537. FutureServiceName = hd(string:lexemes(atom_to_list(node()),"@"))
  1538. ++ "_" ++ Vsn,
  1539. CurrentService = case erlsrv:get_service(PermEVsn,CurrentServiceName) of
  1540. {error, _} = Error1 ->
  1541. throw(Error1);
  1542. CS ->
  1543. CS
  1544. end,
  1545. FutureService = erlsrv:new_service(FutureServiceName,
  1546. CurrentService,
  1547. filename:nativename(DataFileName),
  1548. %% This is rather icky... On a
  1549. %% non permanent service, the
  1550. %% ERLSRV_SERVICE_NAME is
  1551. %% actually that of an old service,
  1552. %% to make heart commands work...
  1553. CurrentServiceName),
  1554. case erlsrv:store_service(EVsn, FutureService) of
  1555. {error, _} = Error2 ->
  1556. throw(Error2);
  1557. _X ->
  1558. {ok,_} = erlsrv:disable_service(EVsn, FutureServiceName),
  1559. ErlSrv = filename:nativename(erlsrv:erlsrv(EVsn)),
  1560. StartDisabled = ErlSrv ++ " start_disabled " ++ FutureServiceName,
  1561. case heart:set_cmd(StartDisabled) of
  1562. ok ->
  1563. ok;
  1564. Error3 ->
  1565. throw({error, {'heart:set_cmd() error', Error3}})
  1566. end
  1567. end.
  1568. %%-----------------------------------------------------------------
  1569. %% Set things up for restarting the new emulator. The actual
  1570. %% restart is performed by calling init:reboot() higher up.
  1571. %%-----------------------------------------------------------------
  1572. prepare_restart_new_emulator(StartPrg, RootDir, RelDir,
  1573. Vsn, Release, Releases, Masters) ->
  1574. {value, PRelease} = lists:keysearch(permanent, #release.status,Releases),
  1575. NReleases1 = set_status(Vsn, current, Releases),
  1576. NReleases2 = set_status(Vsn,tmp_current,NReleases1),
  1577. write_releases(RelDir, NReleases2, Masters),
  1578. prepare_restart_new_emulator(StartPrg, RootDir, RelDir,
  1579. Release, PRelease, Masters).
  1580. prepare_restart_new_emulator(StartPrg, RootDir, RelDir,
  1581. Release, PRelease, Masters) ->
  1582. #release{erts_vsn = EVsn, vsn = Vsn} = Release,
  1583. Data = EVsn ++ " " ++ Vsn,
  1584. DataFile = write_new_start_erl(Data, RelDir, Masters),
  1585. %% Tell heart to use DataFile instead of start_erl.data
  1586. case os:type() of
  1587. {win32,nt} ->
  1588. write_ini_file(RootDir,EVsn,Masters),
  1589. prepare_restart_nt(Release,PRelease,DataFile);
  1590. {unix,_} ->
  1591. StartP = check_start_prg(StartPrg, Masters),
  1592. case heart:set_cmd(StartP ++ " " ++ DataFile) of
  1593. ok ->
  1594. ok;
  1595. Error ->
  1596. throw({error, {'heart:set_cmd() error', Error}})
  1597. end
  1598. end.
  1599. check_start_prg({do_check, StartPrg}, Masters) ->
  1600. check_file(StartPrg, regular, Masters),
  1601. StartPrg;
  1602. check_start_prg({_, StartPrg}, _) ->
  1603. StartPrg.
  1604. write_new_start_erl(Data, RelDir, Masters) ->
  1605. DataFile = filename:join([RelDir, "new_start_erl.data"]),
  1606. write_file(DataFile, Data, Masters),
  1607. DataFile.
  1608. %%-----------------------------------------------------------------
  1609. %% When a new emulator shall be restarted, the current release
  1610. %% is written with status tmp_current. When the new emulator
  1611. %% is started, this function is called. The tmp_current release
  1612. %% gets status unpacked on disk, and current in memory. If a reboot
  1613. %% is made (due to a crash), the release is just unpacked. If a crash
  1614. %% occurs before a call to transform_release is made, the old emulator
  1615. %% is started, and transform_release is called for it. The tmp_current
  1616. %% release is changed to unpacked.
  1617. %% If the release is made permanent, this is written to disk.
  1618. %%-----------------------------------------------------------------
  1619. transform_release(ReleaseDir, Releases, Masters) ->
  1620. case init:script_id() of
  1621. {Name, ?tmp_vsn(_)=TmpVsn} ->
  1622. %% This is was a reboot due to a new emulator version. The
  1623. %% current release is a temporary internal release, which
  1624. %% must be removed. It is the "real new release" that is
  1625. %% set to unpacked on disk and current in memory.
  1626. DReleases = lists:keydelete(TmpVsn,#release.vsn,Releases),
  1627. write_releases(ReleaseDir, DReleases, Masters),
  1628. set_current({Name,TmpVsn},Releases);
  1629. ScriptId ->
  1630. F = fun(Release) when Release#release.status == tmp_current ->
  1631. Release#release{status = unpacked};
  1632. (Release) -> Release
  1633. end,
  1634. case lists:map(F, Releases) of
  1635. Releases ->
  1636. Releases;
  1637. DReleases ->
  1638. write_releases(ReleaseDir, DReleases, Masters),
  1639. set_current(ScriptId, Releases)
  1640. end
  1641. end.
  1642. set_current(ScriptId, Releases) ->
  1643. F1 = fun(Release) when Release#release.status == tmp_current ->
  1644. case ScriptId of
  1645. {_Name,Vsn} when Release#release.vsn == Vsn ->
  1646. Release#release{status = current};
  1647. _ ->
  1648. Release#release{status = unpacked}
  1649. end;
  1650. (Release) -> Release
  1651. end,
  1652. lists:map(F1, Releases).
  1653. %%-----------------------------------------------------------------
  1654. %% Functions handling files, RELEASES, start_erl.data etc.
  1655. %% This functions consider if the release_handler is a client and
  1656. %% in that case performs the operations at all master nodes or at
  1657. %% none (in case of failure).
  1658. %%-----------------------------------------------------------------
  1659. check_opt_file(FileName, Type, Masters) ->
  1660. case catch check_file(FileName, Type, Masters) of
  1661. ok ->
  1662. true;
  1663. _Error ->
  1664. io:format("Warning: ~tp missing (optional)~n", [FileName]),
  1665. false
  1666. end.
  1667. check_file(FileName, Type, false) ->
  1668. do_check_file(FileName, Type);
  1669. check_file(FileName, Type, Masters) ->
  1670. check_file_masters(FileName, Type, Masters).
  1671. %% Check that file exists at all masters.
  1672. check_file_masters(FileName, Type, [Master|Masters]) ->
  1673. do_check_file(Master, FileName, Type),
  1674. check_file_masters(FileName, Type, Masters);
  1675. check_file_masters(_FileName, _Type, []) ->
  1676. ok.
  1677. %% Type == regular | directory
  1678. do_check_file(FileName, Type) ->
  1679. case file:read_file_info(FileName) of
  1680. {ok, Info} when Info#file_info.type==Type -> ok;
  1681. {error, _Reason} -> throw({error, {no_such_file, FileName}})
  1682. end.
  1683. do_check_file(Master, FileName, Type) ->
  1684. case rpc:call(Master, file, read_file_info, [FileName]) of
  1685. {ok, Info} when Info#file_info.type==Type -> ok;
  1686. _ -> throw({error, {no_such_file, {Master, FileName}}})
  1687. end.
  1688. %%-----------------------------------------------------------------
  1689. %% If Rel doesn't exists in tar it could have been created
  1690. %% by the user in another way, i.e. ignore this here.
  1691. %%-----------------------------------------------------------------
  1692. extract_rel_file(Rel, Tar, Root) ->
  1693. _ = erl_tar:extract(Tar, [{files, [Rel]}, {cwd, Root}, compressed]).
  1694. extract_tar(Root, Tar) ->
  1695. case erl_tar:extract(Tar, [keep_old_files, {cwd, Root}, compressed]) of
  1696. ok ->
  1697. ok;
  1698. {error, {Name, Reason}} -> % New erl_tar (R3A).
  1699. throw({error, {cannot_extract_file, Name, Reason}})
  1700. end.
  1701. write_releases(Dir, Releases, Masters) ->
  1702. %% We must never write 'current' to disk, since this will confuse
  1703. %% us after a node restart - since we would then have a permanent
  1704. %% release running, but state set to current for a non-running
  1705. %% release.
  1706. NewReleases = lists:zf(fun(Release) when Release#release.status == current ->
  1707. {true, Release#release{status = unpacked}};
  1708. (_) ->
  1709. true
  1710. end, Releases),
  1711. write_releases_1(Dir, NewReleases, Masters).
  1712. write_releases_1(Dir, NewReleases, false) ->
  1713. case do_write_release(Dir, "RELEASES", NewReleases) of
  1714. ok -> ok;
  1715. Error -> throw(Error)
  1716. end;
  1717. write_releases_1(Dir, NewReleases, Masters) ->
  1718. all_masters(Masters),
  1719. write_releases_m(Dir, NewReleases, Masters).
  1720. do_write_release(Dir, RELEASES, NewReleases) ->
  1721. case file:open(filename:join(Dir, RELEASES), [write,{encoding,utf8}]) of
  1722. {ok, Fd} ->
  1723. ok = io:format(Fd, "%% ~s~n~tp.~n",
  1724. [epp:encoding_to_string(utf8),NewReleases]),
  1725. ok = file:close(Fd);
  1726. {error, Reason} ->
  1727. {error, Reason}
  1728. end.
  1729. %%-----------------------------------------------------------------
  1730. %% Write the "RELEASES" file at all master nodes.
  1731. %% 1. Save "RELEASES.backup" at all nodes.
  1732. %% 2. Save "RELEASES.change" at all nodes.
  1733. %% 3. Update the "RELEASES.change" file at all nodes.
  1734. %% 4. Move "RELEASES.change" to "RELEASES".
  1735. %% 5. Remove "RELEASES.backup" at all nodes.
  1736. %%
  1737. %% If one of the steps above fails, all steps is recovered from
  1738. %% (as long as possible), except for 5 which is allowed to fail.
  1739. %%-----------------------------------------------------------------
  1740. write_releases_m(Dir, NewReleases, Masters) ->
  1741. RelFile = filename:join(Dir, "RELEASES"),
  1742. Backup = filename:join(Dir, "RELEASES.backup"),
  1743. Change = filename:join(Dir, "RELEASES.change"),
  1744. ensure_RELEASES_exists(Masters, RelFile),
  1745. case at_all_masters(Masters, ?MODULE, do_copy_files,
  1746. [RelFile, [Backup, Change]]) of
  1747. ok ->
  1748. case at_all_masters(Masters, ?MODULE, do_write_release,
  1749. [Dir, "RELEASES.change", NewReleases]) of
  1750. ok ->
  1751. case at_all_masters(Masters, file, rename,
  1752. [Change, RelFile]) of
  1753. ok ->
  1754. remove_files(all, [Backup, Change], Masters),
  1755. ok;
  1756. {error, {Master, R}} ->
  1757. takewhile(Master, Masters, file, rename,
  1758. [Backup, RelFile]),
  1759. remove_files(all, [Backup, Change], Masters),
  1760. throw({error, {Master, R, move_releases}})
  1761. end;
  1762. {error, {Master, R}} ->
  1763. remove_files(all, [Backup, Change], Masters),
  1764. throw({error, {Master, R, update_releases}})
  1765. end;
  1766. {error, {Master, R}} ->
  1767. remove_files(Master, [Backup, Change], Masters),
  1768. throw({error, {Master, R, backup_releases}})
  1769. end.
  1770. ensure_RELEASES_exists(Masters, RelFile) ->
  1771. case at_all_masters(Masters, ?MODULE, do_ensure_RELEASES, [RelFile]) of
  1772. ok ->
  1773. ok;
  1774. {error, {Master, R}} ->
  1775. throw({error, {Master, R, ensure_RELEASES_exists}})
  1776. end.
  1777. copy_file(File, Dir, false) ->
  1778. case do_copy_file(File, Dir) of
  1779. ok -> ok;
  1780. Error -> throw(Error)
  1781. end;
  1782. copy_file(File, Dir, Masters) ->
  1783. all_masters(Masters),
  1784. copy_file_m(File, Dir, Masters).
  1785. %%-----------------------------------------------------------------
  1786. %% copy File to Dir at every master node.
  1787. %% If an error occurs at a node, the total copy failed.
  1788. %% We do not have to cleanup in case of failure as this
  1789. %% copy_file is harmless.
  1790. %%-----------------------------------------------------------------
  1791. copy_file_m(File, Dir, [Master|Masters]) ->
  1792. case rpc:call(Master, ?MODULE, do_copy_file, [File, Dir]) of
  1793. ok -> copy_file_m(File, Dir, Masters);
  1794. {error, {Reason, F}} -> throw({error, {Master, Reason, F}});
  1795. Other -> throw({error, {Master, Other, File}})
  1796. end;
  1797. copy_file_m(_File, _Dir, []) ->
  1798. ok.
  1799. do_copy_file(File, Dir) ->
  1800. File2 = filename:join(Dir, filename:basename(File)),
  1801. do_copy_file1(File, File2).
  1802. do_copy_file1(File, File2) ->
  1803. case file:read_file(File) of
  1804. {ok, Bin} ->
  1805. case file:write_file(File2, Bin) of
  1806. ok -> ok;
  1807. {error, Reason} ->
  1808. {error, {Reason, File2}}
  1809. end;
  1810. {error, Reason} ->
  1811. {error, {Reason, File}}
  1812. end.
  1813. %%-----------------------------------------------------------------
  1814. %% Copy File to a list of files.
  1815. %%-----------------------------------------------------------------
  1816. do_copy_files(File, [ToFile|ToFiles]) ->
  1817. case do_copy_file1(File, ToFile) of
  1818. ok -> do_copy_files(File, ToFiles);
  1819. Error -> Error
  1820. end;
  1821. do_copy_files(_, []) ->
  1822. ok.
  1823. %%-----------------------------------------------------------------
  1824. %% Copy each Src file to Dest file in the list of files.
  1825. %%-----------------------------------------------------------------
  1826. do_copy_files([{Src, Dest}|Files]) ->
  1827. case do_copy_file1(Src, Dest) of
  1828. ok -> do_copy_files(Files);
  1829. Error -> Error
  1830. end;
  1831. do_copy_files([]) ->
  1832. ok.
  1833. %%-----------------------------------------------------------------
  1834. %% Rename each Src file to Dest file in the list of files.
  1835. %%-----------------------------------------------------------------
  1836. do_rename_files([{Src, Dest}|Files]) ->
  1837. case file:rename(Src, Dest) of
  1838. ok -> do_rename_files(Files);
  1839. Error -> Error
  1840. end;
  1841. do_rename_files([]) ->
  1842. ok.
  1843. %%-----------------------------------------------------------------
  1844. %% Remove a list of files. Ignore failure.
  1845. %%-----------------------------------------------------------------
  1846. do_remove_files([File|Files]) ->
  1847. _ = file:delete(File),
  1848. do_remove_files(Files);
  1849. do_remove_files([]) ->
  1850. ok.
  1851. %%-----------------------------------------------------------------
  1852. %% Ensure that the RELEASES file exists.
  1853. %% If not create an empty RELEASES file.
  1854. %%-----------------------------------------------------------------
  1855. do_ensure_RELEASES(RelFile) ->
  1856. case file:read_file_info(RelFile) of
  1857. {ok, _} -> ok;
  1858. _ -> do_write_file(RelFile, "[]. ")
  1859. end.
  1860. %%-----------------------------------------------------------------
  1861. %% Make a directory, ignore failures (captured later).
  1862. %%-----------------------------------------------------------------
  1863. make_dir(Dir, false) ->
  1864. _ = file:make_dir(Dir),
  1865. ok;
  1866. make_dir(Dir, Masters) ->
  1867. lists:foreach(fun(Master) -> rpc:call(Master, file, make_dir, [Dir]) end,
  1868. Masters).
  1869. %%-----------------------------------------------------------------
  1870. %% Check that all masters are alive.
  1871. %%-----------------------------------------------------------------
  1872. all_masters(Masters) ->
  1873. case rpc:multicall(Masters, erlang, info, [version]) of
  1874. {_, []} -> ok;
  1875. {_, BadNodes} -> throw({error, {bad_masters, BadNodes}})
  1876. end.
  1877. %%-----------------------------------------------------------------
  1878. %% Evaluate {M,F,A} at all masters.
  1879. %% {M,F,A} is supposed to return ok. Otherwise at_all_masters
  1880. %% returns {error, {Master, Other}}.
  1881. %%-----------------------------------------------------------------
  1882. at_all_masters([Master|Masters], M, F, A) ->
  1883. case rpc:call(Master, M, F, A) of
  1884. ok -> at_all_masters(Masters, M, F, A);
  1885. Error -> {error, {Master, Error}}
  1886. end;
  1887. at_all_masters([], _, _, _) ->
  1888. ok.
  1889. %%-----------------------------------------------------------------
  1890. %% Evaluate {M,F,A} at all masters until Master is found.
  1891. %% Ignore {M,F,A} return value.
  1892. %%-----------------------------------------------------------------
  1893. takewhile(Master, Masters, M, F, A) ->
  1894. _ = lists:takewhile(fun(Ma) when Ma == Master ->
  1895. false;
  1896. (Ma) ->
  1897. rpc:call(Ma, M, F, A),
  1898. true
  1899. end, Masters),
  1900. ok.
  1901. consult(File, false) -> file:consult(File);
  1902. consult(File, Masters) -> consult_master(Masters, File).
  1903. %%-----------------------------------------------------------------
  1904. %% consult the File at any master node.
  1905. %% If the file does not exist at one node it should
  1906. %% not exist at any other node either.
  1907. %%-----------------------------------------------------------------
  1908. consult_master([Master|Ms], File) ->
  1909. case rpc:call(Master, file, consult, [File]) of
  1910. {badrpc, _} -> consult_master(Ms, File);
  1911. Res -> Res
  1912. end;
  1913. consult_master([], _File) ->
  1914. {error, no_master}.
  1915. read_file(File, false) ->
  1916. file:read_file(File);
  1917. read_file(File, Masters) ->
  1918. read_master(Masters, File).
  1919. write_file(File, Data, false) ->
  1920. case file:write_file(File, Data) of
  1921. ok -> ok;
  1922. Error -> throw(Error)
  1923. end;
  1924. write_file(File, Data, Masters) ->
  1925. case at_all_masters(Masters, file, write_file, [File, Data]) of
  1926. ok -> ok;
  1927. Error -> throw(Error)
  1928. end.
  1929. ensure_dir(File, false) ->
  1930. case filelib:ensure_dir(File) of
  1931. ok -> ok;
  1932. Error -> throw(Error)
  1933. end;
  1934. ensure_dir(File, Masters) ->
  1935. case at_all_masters(Masters,filelib,ensure_dir,[File]) of
  1936. ok -> ok;
  1937. Error -> throw(Error)
  1938. end.
  1939. remove_dir(Dir, false) ->
  1940. remove_file(Dir);
  1941. remove_dir(Dir, Masters) ->
  1942. case at_all_masters(Masters,?MODULE,remove_file,[Dir]) of
  1943. ok -> ok;
  1944. Error -> throw(Error)
  1945. end.
  1946. %% Ignore status of each delete !
  1947. remove_files(Master, Files, Masters) ->
  1948. takewhile(Master, Masters, ?MODULE, do_remove_files, [Files]).
  1949. %%-----------------------------------------------------------------
  1950. %% read the File at any master node.
  1951. %% If the file does not exist at one node it should
  1952. %% not exist at any other node either.
  1953. %%-----------------------------------------------------------------
  1954. read_master([Master|Ms], File) ->
  1955. case rpc:call(Master, file, read_file, [File]) of
  1956. {badrpc, _} -> read_master(Ms, File);
  1957. Res -> Res
  1958. end;
  1959. read_master([], _File) ->
  1960. {error, no_master}.
  1961. %%-----------------------------------------------------------------
  1962. %% Write start_erl.data.
  1963. %%-----------------------------------------------------------------
  1964. write_start(File, Data, false) ->
  1965. case do_write_file(File, Data) of
  1966. ok -> ok;
  1967. Error -> throw(Error)
  1968. end;
  1969. write_start(File, Data, Masters) ->
  1970. all_masters(Masters),
  1971. safe_write_file_m(File, Data, Masters).
  1972. %%-----------------------------------------------------------------
  1973. %% Copy the "start.boot" and "sys.config" from SrcDir to DestDir at all
  1974. %% master nodes.
  1975. %% 1. Save DestDir/"start.backup" and DestDir/"sys.backup" at all nodes.
  1976. %% 2. Copy files at all nodes.
  1977. %% 3. Remove backup files at all nodes.
  1978. %%
  1979. %% If one of the steps above fails, all steps is recovered from
  1980. %% (as long as possible), except for 3 which is allowed to fail.
  1981. %%-----------------------------------------------------------------
  1982. set_static_files(SrcDir, DestDir, Masters) ->
  1983. all_masters(Masters),
  1984. Boot = "start.boot",
  1985. Config = "sys.config",
  1986. SrcBoot = filename:join(SrcDir, Boot),
  1987. DestBoot = filename:join(DestDir, Boot),
  1988. BackupBoot = filename:join(DestDir, "start.backup"),
  1989. SrcConf = filename:join(SrcDir, Config),
  1990. DestConf = filename:join(DestDir, Config),
  1991. BackupConf = filename:join(DestDir, "sys.backup"),
  1992. case at_all_masters(Masters, ?MODULE, do_copy_files,
  1993. [[{DestBoot, BackupBoot},
  1994. {DestConf, BackupConf}]]) of
  1995. ok ->
  1996. case at_all_masters(Masters, ?MODULE, do_copy_files,
  1997. [[{SrcBoot, DestBoot},
  1998. {SrcConf, DestConf}]]) of
  1999. ok ->
  2000. remove_files(all, [BackupBoot, BackupConf], Masters),
  2001. ok;
  2002. {error, {Master, R}} ->
  2003. takewhile(Master, Masters, ?MODULE, do_rename_files,
  2004. [{BackupBoot, DestBoot},
  2005. {BackupConf, DestConf}]),
  2006. remove_files(all, [BackupBoot, BackupConf], Masters),
  2007. throw({error, {Master, R, copy_start_config}})
  2008. end;
  2009. {error, {Master, R}} ->
  2010. remove_files(Master, [BackupBoot, BackupConf], Masters),
  2011. throw({error, {Master, R, backup_start_config}})
  2012. end.
  2013. %%-----------------------------------------------------------------
  2014. %% Write erl.ini
  2015. %% Writes the erl.ini file used by erl.exe when (re)starting the erlang node.
  2016. %% At first installation, this is done by Install.exe, which means that if
  2017. %% the format of this file for some reason is changed, then Install.c must
  2018. %% also be updated (and probably some other c-files which read erl.ini)
  2019. %%-----------------------------------------------------------------
  2020. write_ini_file(RootDir,EVsn,Masters) ->
  2021. BinDir = filename:join([RootDir,"erts-"++EVsn,"bin"]),
  2022. Str0 = io_lib:format("[erlang]~n"
  2023. "Bindir=~ts~n"
  2024. "Progname=erl~n"
  2025. "Rootdir=~ts~n",
  2026. [filename:nativename(BinDir),
  2027. filename:nativename(RootDir)]),
  2028. Str = re:replace(Str0,"\\\\","\\\\\\\\",[{return,list},global,unicode]),
  2029. IniFile = filename:join(BinDir,"erl.ini"),
  2030. do_write_ini_file(IniFile,Str,Masters).
  2031. do_write_ini_file(File,Data,false) ->
  2032. case do_write_file(File, Data, [{encoding,utf8}]) of
  2033. ok -> ok;
  2034. Error -> throw(Error)
  2035. end;
  2036. do_write_ini_file(File,Data,Masters) ->
  2037. all_masters(Masters),
  2038. safe_write_file_m(File, Data, [{encoding,utf8}], Masters).
  2039. %%-----------------------------------------------------------------
  2040. %% Write the given file at all master nodes.
  2041. %% 1. Save <File>.backup at all nodes.
  2042. %% 2. Write <File>.change at all nodes.
  2043. %% 3. Move <File>.change to <File>
  2044. %% 4. Remove <File>.backup at all nodes.
  2045. %%
  2046. %% If one of the steps above fails, all steps are recovered from
  2047. %% (as long as possible), except for 4 which is allowed to fail.
  2048. %%-----------------------------------------------------------------
  2049. safe_write_file_m(File, Data, Masters) ->
  2050. safe_write_file_m(File, Data, [], Masters).
  2051. safe_write_file_m(File, Data, FileOpts, Masters) ->
  2052. Backup = File ++ ".backup",
  2053. Change = File ++ ".change",
  2054. case at_all_masters(Masters, ?MODULE, do_copy_files,
  2055. [File, [Backup]]) of
  2056. ok ->
  2057. case at_all_masters(Masters, ?MODULE, do_write_file,
  2058. [Change, Data, FileOpts]) of
  2059. ok ->
  2060. case at_all_masters(Masters, file, rename,
  2061. [Change, File]) of
  2062. ok ->
  2063. remove_files(all, [Backup, Change], Masters),
  2064. ok;
  2065. {error, {Master, R}} ->
  2066. takewhile(Master, Masters, file, rename,
  2067. [Backup, File]),
  2068. remove_files(all, [Backup, Change], Masters),
  2069. throw({error, {Master, R, rename,
  2070. filename:basename(Change),
  2071. filename:basename(File)}})
  2072. end;
  2073. {error, {Master, R}} ->
  2074. remove_files(all, [Backup, Change], Masters),
  2075. throw({error, {Master, R, write, filename:basename(Change)}})
  2076. end;
  2077. {error, {Master, R}} ->
  2078. remove_files(Master, [Backup], Masters),
  2079. throw({error, {Master, R, backup,
  2080. filename:basename(File),
  2081. filename:basename(Backup)}})
  2082. end.
  2083. %%-----------------------------------------------------------------
  2084. %% Figure out which applications that have changed version between the
  2085. %% two releases. The paths for these applications must always be
  2086. %% updated, even if the relup script does not load any modules. See
  2087. %% OTP-9402.
  2088. %%
  2089. %% A different situation is when the same application version is used
  2090. %% in old and new release, but the path has changed. This is not
  2091. %% handled here - instead it must be explicitely indicated by the
  2092. %% 'update_paths' option to release_handler:install_release/2 if the
  2093. %% code path shall be updated then.
  2094. %% -----------------------------------------------------------------
  2095. get_new_libs([{App,Vsn,_LibDir}|CurrentLibs], NewLibs) ->
  2096. case lists:keyfind(App,1,NewLibs) of
  2097. {App,NewVsn,_} = LibInfo when NewVsn =/= Vsn ->
  2098. [LibInfo | get_new_libs(CurrentLibs,NewLibs)];
  2099. _ ->
  2100. get_new_libs(CurrentLibs,NewLibs)
  2101. end;
  2102. get_new_libs([],_) ->
  2103. [].
  2104. %%-----------------------------------------------------------------
  2105. %% Return a list of releases witch a specific status
  2106. %%-----------------------------------------------------------------
  2107. get_releases_with_status([], _, Acc) ->
  2108. Acc;
  2109. get_releases_with_status([ {_, _, _, ReleaseStatus } = Head | Tail],
  2110. Status, Acc) when ReleaseStatus == Status ->
  2111. get_releases_with_status(Tail, Status, [Head | Acc]);
  2112. get_releases_with_status([_ | Tail], Status, Acc) ->
  2113. get_releases_with_status(Tail, Status, Acc).