PageRenderTime 75ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/src/lib/reltool_util/src/reltool_util.erl

http://github.com/okeuday/CloudI
Erlang | 1287 lines | 910 code | 116 blank | 261 comment | 1 complexity | f0bbaf331b717d9056d4f279f81acdc4 MD5 | raw file
Possible License(s): BSD-3-Clause, 0BSD, MIT, Apache-2.0, Unlicense, GPL-3.0, LGPL-3.0
  1. %-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
  2. % ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et:
  3. %%%
  4. %%%------------------------------------------------------------------------
  5. %%% @doc
  6. %%% ==reltool Utility Functions==
  7. %%% All the functions here are probably considered unorthodox, but
  8. %%% are useful for runtime usage of applications and releases.
  9. %%% @end
  10. %%%
  11. %%% BSD LICENSE
  12. %%%
  13. %%% Copyright (c) 2013, Michael Truog <mjtruog at gmail dot com>
  14. %%% All rights reserved.
  15. %%%
  16. %%% Redistribution and use in source and binary forms, with or without
  17. %%% modification, are permitted provided that the following conditions are met:
  18. %%%
  19. %%% * Redistributions of source code must retain the above copyright
  20. %%% notice, this list of conditions and the following disclaimer.
  21. %%% * Redistributions in binary form must reproduce the above copyright
  22. %%% notice, this list of conditions and the following disclaimer in
  23. %%% the documentation and/or other materials provided with the
  24. %%% distribution.
  25. %%% * All advertising materials mentioning features or use of this
  26. %%% software must display the following acknowledgment:
  27. %%% This product includes software developed by Michael Truog
  28. %%% * The name of the author may not be used to endorse or promote
  29. %%% products derived from this software without specific prior
  30. %%% written permission
  31. %%%
  32. %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  33. %%% CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  34. %%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  35. %%% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  36. %%% DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  37. %%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  38. %%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  39. %%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  40. %%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  41. %%% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  42. %%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  43. %%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  44. %%% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  45. %%% DAMAGE.
  46. %%%
  47. %%% @author Michael Truog <mjtruog [at] gmail (dot) com>
  48. %%% @copyright 2013 Michael Truog
  49. %%% @version 0.8.0 {@date} {@time}
  50. %%%------------------------------------------------------------------------
  51. -module(reltool_util).
  52. -author('mjtruog [at] gmail (dot) com').
  53. -export([application_start/1,
  54. application_start/2,
  55. application_start/3,
  56. applications_start/1,
  57. applications_start/2,
  58. application_stop/1,
  59. application_remove/1,
  60. application_remove/2,
  61. application_purged/1,
  62. application_purged/2,
  63. application_running/1,
  64. application_running/2,
  65. application_loaded/1,
  66. application_modules/1,
  67. application_modules/2,
  68. ensure_application_loaded/1,
  69. ensure_application_started/1,
  70. ensure_application_stopped/1,
  71. module_loaded/1,
  72. is_module_loaded/1,
  73. is_module_loaded/2,
  74. module_purged/1,
  75. module_purged/2,
  76. module_exports/1,
  77. script_start/1,
  78. script_remove/1,
  79. script_remove/2]).
  80. -define(IS_MODULE_LOADED_DELTA, 100).
  81. -define(MODULES_PURGED_DELTA, 100).
  82. -compile({no_auto_import, [{module_loaded, 1}]}).
  83. %%%------------------------------------------------------------------------
  84. %%% External interface functions
  85. %%%------------------------------------------------------------------------
  86. %%-------------------------------------------------------------------------
  87. %% @doc
  88. %% ===Start all the dependent applications manually.===
  89. %% @end
  90. %%-------------------------------------------------------------------------
  91. -spec application_start(Application :: atom()) ->
  92. ok |
  93. {error, any()}.
  94. application_start(Application)
  95. when is_atom(Application) ->
  96. application_start(Application, [], 5000).
  97. %%-------------------------------------------------------------------------
  98. %% @doc
  99. %% ===Start all the dependent applications manually with a specific configuration.===
  100. %% @end
  101. %%-------------------------------------------------------------------------
  102. -spec application_start(Application :: atom(),
  103. Env :: list({atom(), any()})) ->
  104. ok |
  105. {error, any()}.
  106. application_start(Application, Env)
  107. when is_atom(Application), is_list(Env) ->
  108. application_start(Application, Env, 5000).
  109. %%-------------------------------------------------------------------------
  110. %% @doc
  111. %% ===Start all the dependent applications manually with a specific configuration and timeout.===
  112. %% @end
  113. %%-------------------------------------------------------------------------
  114. -spec application_start(Application :: atom(),
  115. Env :: list({atom(), any()}),
  116. Timeout :: pos_integer() | infinity) ->
  117. ok |
  118. {error, any()}.
  119. application_start(Application, Env, Timeout)
  120. when is_atom(Application), is_list(Env) ->
  121. case ensure_application_loaded(Application) of
  122. ok ->
  123. case application_start_set_env(Env, Application, Timeout) of
  124. ok ->
  125. case applications_dependencies(Application) of
  126. {ok, As} ->
  127. case application_start_dependencies(As) of
  128. ok ->
  129. ensure_application_started(Application);
  130. {error, _} = Error ->
  131. Error
  132. end;
  133. {error, _} = Error ->
  134. Error
  135. end;
  136. {error, _} = Error ->
  137. Error
  138. end;
  139. {error, _} = Error ->
  140. Error
  141. end.
  142. %%-------------------------------------------------------------------------
  143. %% @doc
  144. %% ===Start all the dependent applications manually.===
  145. %% @end
  146. %%-------------------------------------------------------------------------
  147. -spec applications_start(Applications :: list(atom() | {atom(), list()})) ->
  148. ok |
  149. {error, any()}.
  150. applications_start([_ | _] = Applications) ->
  151. applications_start(Applications, 5000).
  152. %%-------------------------------------------------------------------------
  153. %% @doc
  154. %% ===Start all the dependent applications manually.===
  155. %% @end
  156. %%-------------------------------------------------------------------------
  157. -spec applications_start(Applications :: list(atom() | {atom(), list()}),
  158. Timeout :: pos_integer() | infinity) ->
  159. ok |
  160. {error, any()}.
  161. applications_start([_ | _] = Applications, Timeout) ->
  162. applications_start_element(Applications, Timeout).
  163. %%-------------------------------------------------------------------------
  164. %% @doc
  165. %% ===Stop an application and its dependencies.===
  166. %% Only stop dependencies that are not required for other applications.
  167. %% @end
  168. %%-------------------------------------------------------------------------
  169. -spec application_stop(Application :: atom()) ->
  170. ok |
  171. {error, any()}.
  172. application_stop(Application) ->
  173. case application_stop_dependencies(Application) of
  174. {ok, _} ->
  175. ok;
  176. {error, _} = Error ->
  177. Error
  178. end.
  179. %%-------------------------------------------------------------------------
  180. %% @doc
  181. %% ===Stop and purge the modules of an application and all of its dependencies.===
  182. %% Only application dependencies that are not required for other
  183. %% applications are removed.
  184. %% @end
  185. %%-------------------------------------------------------------------------
  186. -spec application_remove(Application :: atom()) ->
  187. ok |
  188. {error, any()}.
  189. application_remove(Application) ->
  190. application_remove(Application, 5000).
  191. %%-------------------------------------------------------------------------
  192. %% @doc
  193. %% ===Stop and purge the modules of an application and all of its dependencies with a timeout.===
  194. %% Only application dependencies that are not required for other
  195. %% applications are removed.
  196. %% @end
  197. %%-------------------------------------------------------------------------
  198. -spec application_remove(Application :: atom(),
  199. Timeout :: pos_integer() | infinity) ->
  200. ok |
  201. {error, any()}.
  202. application_remove(Application, infinity)
  203. when is_atom(Application) ->
  204. case application_stop_dependencies(Application) of
  205. {ok, Applications} ->
  206. applications_purged(Applications, infinity);
  207. {error, _} = Error ->
  208. Error
  209. end;
  210. application_remove(Application, Timeout)
  211. when is_atom(Application), is_integer(Timeout), Timeout > 0 ->
  212. case application_stop_dependencies(Application) of
  213. {ok, Applications} ->
  214. TimeoutSlice = erlang:round(
  215. 0.5 + Timeout / erlang:length(Applications)),
  216. applications_purged(Applications, TimeoutSlice);
  217. {error, _} = Error ->
  218. Error
  219. end.
  220. %%-------------------------------------------------------------------------
  221. %% @doc
  222. %% ===Purge a loaded application's modules and unload the application.===
  223. %% The application is stopped if it is running, but its dependencies are
  224. %% ignored.
  225. %% @end
  226. %%-------------------------------------------------------------------------
  227. -spec application_purged(Application :: atom()) ->
  228. ok |
  229. {error, any()}.
  230. application_purged(Application) ->
  231. application_purged(Application, 5000).
  232. %%-------------------------------------------------------------------------
  233. %% @doc
  234. %% ===Purge a loaded application's modules and unload the application with a specific timeout.===
  235. %% The application is stopped if it is running, but its dependencies are
  236. %% ignored.
  237. %% @end
  238. %%-------------------------------------------------------------------------
  239. -spec application_purged(Application :: atom(),
  240. Timeout :: pos_integer() | infinity) ->
  241. ok |
  242. {error, any()}.
  243. application_purged(Application, Timeout)
  244. when is_atom(Application) ->
  245. case application_loaded(Application) of
  246. {ok, _} ->
  247. case ensure_application_stopped(Application) of
  248. ok ->
  249. case application_modules(Application) of
  250. {ok, Modules} ->
  251. case modules_purged(Modules, Timeout) of
  252. ok ->
  253. application:unload(Application);
  254. {error, _} = Error ->
  255. Error
  256. end;
  257. {error, _} = Error ->
  258. Error
  259. end;
  260. {error, _} = Error ->
  261. Error
  262. end;
  263. {error, _} = Error ->
  264. Error
  265. end.
  266. %%-------------------------------------------------------------------------
  267. %% @doc
  268. %% ===Check if an application is currently running.===
  269. %% @end
  270. %%-------------------------------------------------------------------------
  271. -spec application_running(Application :: atom()) ->
  272. {ok, {atom(), string()}} |
  273. {error, any()}.
  274. application_running(Application)
  275. when is_atom(Application) ->
  276. application_running(Application, 5000).
  277. %%-------------------------------------------------------------------------
  278. %% @doc
  279. %% ===Check if an application is currently running with a timeout.===
  280. %% @end
  281. %%-------------------------------------------------------------------------
  282. -spec application_running(Application :: atom(),
  283. Timeout :: pos_integer() | infinity) ->
  284. {ok, {atom(), string()}} |
  285. {error, any()}.
  286. application_running(Application, Timeout)
  287. when is_atom(Application) ->
  288. try application:which_applications(Timeout) of
  289. Apps ->
  290. case lists:keyfind(Application, 1, Apps) of
  291. {Application, _, VSN} ->
  292. {ok, {Application, VSN}};
  293. false ->
  294. {error, {not_found, Application}}
  295. end
  296. catch exit:{timeout, _} ->
  297. {error, application_controller_timeout}
  298. end.
  299. %%-------------------------------------------------------------------------
  300. %% @doc
  301. %% ===Check if an application is currently loaded.===
  302. %% @end
  303. %%-------------------------------------------------------------------------
  304. -spec application_loaded(Application :: atom()) ->
  305. {ok, {atom(), string()}} |
  306. {error, any()}.
  307. application_loaded(Application)
  308. when is_atom(Application) ->
  309. Apps = application:loaded_applications(),
  310. case lists:keyfind(Application, 1, Apps) of
  311. {Application, _, VSN} ->
  312. {ok, {Application, VSN}};
  313. false ->
  314. {error, {not_found, Application}}
  315. end.
  316. %%-------------------------------------------------------------------------
  317. %% @doc
  318. %% ===Retrieve a list of application modules.===
  319. %% @end
  320. %%-------------------------------------------------------------------------
  321. -spec application_modules(Application :: atom()) ->
  322. {ok, list(atom())} |
  323. {error, any()}.
  324. application_modules(Application) ->
  325. application_modules(Application, []).
  326. %%-------------------------------------------------------------------------
  327. %% @doc
  328. %% ===Retrieve a list of application modules with filter options.===
  329. %% Options can contain {behavior, ModuleName} to list all the modules
  330. %% that use a specific behaviour (the information will not be present if
  331. %% the beam file was stripped).
  332. %% @end
  333. %%-------------------------------------------------------------------------
  334. -spec application_modules(Application :: atom(),
  335. Options :: list({atom(), any()})) ->
  336. {ok, list(atom())} |
  337. {error, any()}.
  338. application_modules(Application, Options)
  339. when is_atom(Application), is_list(Options) ->
  340. case application:get_key(Application, modules) of
  341. {ok, Modules} ->
  342. {ok, modules_filter(Options, Modules, false)};
  343. undefined ->
  344. {error, {modules_missing, Application}}
  345. end.
  346. %%-------------------------------------------------------------------------
  347. %% @doc
  348. %% ===Make sure an application is loaded.===
  349. %% @end
  350. %%-------------------------------------------------------------------------
  351. -spec ensure_application_loaded(Application :: atom()) ->
  352. ok |
  353. {error, any()}.
  354. ensure_application_loaded(kernel) ->
  355. ok;
  356. ensure_application_loaded(stdlib) ->
  357. ok;
  358. ensure_application_loaded(Application) ->
  359. Loaded = case application:load(Application) of
  360. ok ->
  361. ok;
  362. {error, {already_loaded, Application}} ->
  363. ok;
  364. {error, _} = Error ->
  365. Error
  366. end,
  367. if
  368. Loaded =:= ok ->
  369. case application:get_key(Application, modules) of
  370. {ok, Modules} ->
  371. % make sure all modules are loaded, even if the
  372. % application information is already loaded, since
  373. % loading the application data does not automatically
  374. % load its modules
  375. lists:foreach(fun(M) ->
  376. % valid results, others cause a crash
  377. case module_loaded(M) of
  378. ok ->
  379. ok;
  380. {error, nofile} ->
  381. error_logger:warning_msg("broken "
  382. "application ~p "
  383. "missing ~p file~n",
  384. [Application, M]);
  385. {error, Reason} ->
  386. error_logger:error_msg("application ~p load "
  387. "error ~p on ~p file~n",
  388. [Application,
  389. Reason, M])
  390. end
  391. end, Modules);
  392. undefined ->
  393. ok
  394. end;
  395. true ->
  396. Loaded
  397. end.
  398. %%-------------------------------------------------------------------------
  399. %% @doc
  400. %% ===Make sure an application is started.===
  401. %% @end
  402. %%-------------------------------------------------------------------------
  403. -spec ensure_application_started(Application :: atom()) ->
  404. ok |
  405. {error, any()}.
  406. ensure_application_started(Application) ->
  407. case application:start(Application, temporary) of
  408. ok ->
  409. ok;
  410. {error, {already_started, Application}} ->
  411. ok;
  412. {error, _} = Error ->
  413. Error
  414. end.
  415. %%-------------------------------------------------------------------------
  416. %% @doc
  417. %% ===Make sure an application is stopped.===
  418. %% @end
  419. %%-------------------------------------------------------------------------
  420. -spec ensure_application_stopped(Application :: atom()) ->
  421. ok |
  422. {error, any()}.
  423. ensure_application_stopped(Application) ->
  424. case application:stop(Application) of
  425. ok ->
  426. ok;
  427. {error, {not_started, Application}} ->
  428. ok;
  429. {error, _} = Error ->
  430. Error
  431. end.
  432. %%-------------------------------------------------------------------------
  433. %% @doc
  434. %% ===Make sure a module is loaded.===
  435. %% If the module is not loaded, attempt to load it.
  436. %% @end
  437. %%-------------------------------------------------------------------------
  438. -spec module_loaded(Module :: atom()) ->
  439. ok |
  440. {error, any()}.
  441. module_loaded(Module)
  442. when is_atom(Module) ->
  443. case is_module_loaded_check(Module) of
  444. false ->
  445. case code:load_file(Module) of
  446. {module, Module} ->
  447. ok;
  448. {error, _} = Error ->
  449. Error
  450. end;
  451. true ->
  452. ok
  453. end.
  454. %%-------------------------------------------------------------------------
  455. %% @doc
  456. %% ===Wait to check if a module is loaded.===
  457. %% @end
  458. %%-------------------------------------------------------------------------
  459. -spec is_module_loaded(Module :: atom()) ->
  460. ok |
  461. {error, any()}.
  462. is_module_loaded(Module)
  463. when is_atom(Module) ->
  464. case is_module_loaded(Module, 5000) of
  465. {ok, _} ->
  466. ok;
  467. {error, _} = Error ->
  468. Error
  469. end.
  470. %%-------------------------------------------------------------------------
  471. %% @doc
  472. %% ===Wait to check if a module is loaded.===
  473. %% Return a new timeout value with the elapsed time subtracted.
  474. %% @end
  475. %%-------------------------------------------------------------------------
  476. -spec is_module_loaded(Module :: atom(),
  477. Timeout :: non_neg_integer()) ->
  478. {ok, non_neg_integer()} |
  479. {error, any()}.
  480. is_module_loaded(Module, Timeout)
  481. when is_atom(Module), is_integer(Timeout), Timeout >= 0 ->
  482. case is_module_loaded_check(Module) of
  483. true ->
  484. {ok, Timeout};
  485. false ->
  486. case erlang:max(Timeout - ?IS_MODULE_LOADED_DELTA, 0) of
  487. 0 ->
  488. {error, timeout};
  489. NextTimeout ->
  490. receive after ?IS_MODULE_LOADED_DELTA -> ok end,
  491. is_module_loaded(Module, NextTimeout)
  492. end
  493. end.
  494. %%-------------------------------------------------------------------------
  495. %% @doc
  496. %% ===Make sure a module is purged.===
  497. %% If the module is not loaded, ignore it.
  498. %% @end
  499. %%-------------------------------------------------------------------------
  500. -spec module_purged(Module :: atom()) ->
  501. ok |
  502. {error, any()}.
  503. module_purged(Module) ->
  504. module_purged(Module, 5000).
  505. %%-------------------------------------------------------------------------
  506. %% @doc
  507. %% ===Make sure a module is purged with a timeout.===
  508. %% If the module is not loaded, ignore it.
  509. %% @end
  510. %%-------------------------------------------------------------------------
  511. -spec module_purged(Module :: atom(),
  512. Timeout :: non_neg_integer() | infinity) ->
  513. ok |
  514. {error, any()}.
  515. module_purged(Module, Timeout)
  516. when is_atom(Module) ->
  517. modules_purged([Module], Timeout).
  518. %%-------------------------------------------------------------------------
  519. %% @doc
  520. %% ===List the exported functions of a module.===
  521. %% @end
  522. %%-------------------------------------------------------------------------
  523. -spec module_exports(Module :: atom()) ->
  524. list({atom(), pos_integer()}).
  525. module_exports(Module)
  526. when is_atom(Module) ->
  527. {exports, L0} = lists:keyfind(exports, 1, Module:module_info()),
  528. {value, _, L1} = lists:keytake(module_info, 1, L0),
  529. {value, _, L2} = lists:keytake(module_info, 1, L1),
  530. L2.
  531. %%-------------------------------------------------------------------------
  532. %% @doc
  533. %% ===Start everything specified within a script file.===
  534. %% A script file is the input used when creating a boot file, which is the
  535. %% file used when first starting the Erlang VM. This function checks
  536. %% all applications to determine if they are already running with the
  537. %% expected versions. All modules are checked to make sure they have
  538. %% been loaded, if they are expected to have been loaded. Normally,
  539. %% the script is only used in the binary boot file format and only a single
  540. %% boot file is used during the lifetime of the Erlang VM
  541. %% (so it is unclear if using this function is bad or just unorthodox).
  542. %% The script file is expected to be within a release directory created
  543. %% by reltool.
  544. %% @end
  545. %%-------------------------------------------------------------------------
  546. -spec script_start(FilePath :: string()) ->
  547. {ok, list(atom())} |
  548. {error, any()}.
  549. script_start(FilePath)
  550. when is_list(FilePath) ->
  551. true = lists:suffix(".script", FilePath),
  552. % system name and version are ignored
  553. {ok, [{script, {_Name, _Vsn}, Instructions}]} = file:consult(FilePath),
  554. Dir = filename:dirname(FilePath),
  555. % expects the typical directory structure produced by reltool
  556. DirNames = filename:split(Dir),
  557. case erlang:length(DirNames) of
  558. DirNamesLength when DirNamesLength > 2 ->
  559. Root = lists:sublist(DirNames, DirNamesLength - 2),
  560. case filelib:is_dir(filename:join(Root ++ ["lib"])) of
  561. true ->
  562. % on success, return the last application to be started
  563. % (should be the main application since all the application
  564. % dependencies are started first)
  565. script_start_instructions(Instructions, Root);
  566. false ->
  567. {error, invalid_release_structure}
  568. end;
  569. _ ->
  570. {error, invalid_release_directory}
  571. end.
  572. %%-------------------------------------------------------------------------
  573. %% @doc
  574. %% ===Stop everything specified within a script file.===
  575. %% A script file is the input used when creating a boot file, which is the
  576. %% file used when first starting the Erlang VM. This function checks
  577. %% all applications to determine applications which can be safely removed
  578. %% (assuming the application dependencies are correct). The applications
  579. %% will then be stopped and their modules will be purged. Normally,
  580. %% the script is only used in the binary boot file format and only a single
  581. %% boot file is used during the lifetime of the Erlang VM
  582. %% (so it is unclear if using this function is bad or just unorthodox).
  583. %% The script file is expected to be within a release directory created
  584. %% by reltool.
  585. %% @end
  586. %%-------------------------------------------------------------------------
  587. -spec script_remove(FilePath :: string()) ->
  588. ok |
  589. {error, any()}.
  590. script_remove(FilePath) ->
  591. script_remove(FilePath, 5000).
  592. %%-------------------------------------------------------------------------
  593. %% @doc
  594. %% ===Stop everything specified within a script file with a timeout.===
  595. %% A script file is the input used when creating a boot file, which is the
  596. %% file used when first starting the Erlang VM. This function checks
  597. %% all applications to determine applications which can be safely removed
  598. %% (assuming the application dependencies are correct). The applications
  599. %% will then be stopped and their modules will be purged. Normally,
  600. %% the script is only used in the binary boot file format and only a single
  601. %% boot file is used during the lifetime of the Erlang VM
  602. %% (so it is unclear if using this function is bad or just unorthodox).
  603. %% The script file is expected to be within a release directory created
  604. %% by reltool.
  605. %% @end
  606. %%-------------------------------------------------------------------------
  607. -spec script_remove(FilePath :: string(),
  608. Timeout :: pos_integer() | infinity) ->
  609. ok |
  610. {error, any()}.
  611. script_remove(FilePath, Timeout)
  612. when is_list(FilePath),
  613. ((is_integer(Timeout) andalso (Timeout > 0)) orelse
  614. (Timeout =:= infinity))->
  615. true = lists:suffix(".script", FilePath),
  616. % system name and version are ignored
  617. {ok, [{script, {_Name, _Vsn}, Instructions}]} = file:consult(FilePath),
  618. Dir = filename:dirname(FilePath),
  619. % expects the typical directory structure produced by reltool
  620. DirNames = filename:split(Dir),
  621. case erlang:length(DirNames) of
  622. DirNamesLength when DirNamesLength > 2 ->
  623. Root = lists:sublist(DirNames, DirNamesLength - 2),
  624. case filelib:is_dir(filename:join(Root ++ ["lib"])) of
  625. true ->
  626. case script_remove_instructions(Instructions) of
  627. {ok, Applications} ->
  628. NewTimeout = if
  629. Timeout =:= infinity ->
  630. Timeout;
  631. is_integer(Timeout) ->
  632. erlang:round(0.5 + Timeout /
  633. erlang:length(Applications))
  634. end,
  635. applications_remove(Applications, NewTimeout);
  636. {error, _} = Error ->
  637. Error
  638. end;
  639. false ->
  640. {error, invalid_release_structure}
  641. end;
  642. _ ->
  643. {error, invalid_release_directory}
  644. end.
  645. %%%------------------------------------------------------------------------
  646. %%% Private functions
  647. %%%------------------------------------------------------------------------
  648. application_start_set_env([], _, _) ->
  649. ok;
  650. application_start_set_env([{K, V} | L], Application, Timeout) ->
  651. try application:set_env(Application, K, V, Timeout) of
  652. ok ->
  653. application_start_set_env(L, Application, Timeout)
  654. catch
  655. exit:{timeout, _} ->
  656. {error, application_controller_timeout}
  657. end;
  658. application_start_set_env(_, _, _) ->
  659. {error, invalid_application_env}.
  660. application_start_dependencies([]) ->
  661. ok;
  662. application_start_dependencies([A | As]) ->
  663. case ensure_application_started(A) of
  664. ok ->
  665. application_start_dependencies(As);
  666. {error, _} = Error ->
  667. Error
  668. end.
  669. applications_start_element([], _) ->
  670. ok;
  671. applications_start_element([Application | Applications], Timeout)
  672. when is_atom(Application) ->
  673. case application_start(Application, [], Timeout) of
  674. ok ->
  675. applications_start_element(Applications, Timeout);
  676. {error, _} = Error ->
  677. Error
  678. end;
  679. applications_start_element([{Application, Env} | Applications], Timeout)
  680. when is_atom(Application), is_list(Env) ->
  681. case application_start(Application, Env, Timeout) of
  682. ok ->
  683. applications_start_element(Applications, Timeout);
  684. {error, _} = Error ->
  685. Error
  686. end.
  687. application_stop_external([], Apps) ->
  688. {ok, Apps};
  689. application_stop_external([A | As], Apps) ->
  690. case lists:keytake(A, 1, Apps) of
  691. false ->
  692. {error, {not_started, A}};
  693. {value, _, NextApps} ->
  694. application_stop_external(As, NextApps)
  695. end.
  696. application_stop_ignore(L) ->
  697. application_stop_ignore(sets:new(), L).
  698. application_stop_ignore(Required, []) ->
  699. sets:to_list(Required);
  700. application_stop_ignore(Required, [{A, _, _} | L]) ->
  701. case applications_dependencies(A) of
  702. {ok, As} ->
  703. application_stop_ignore(
  704. sets:union(Required, sets:from_list(As)), L);
  705. {error, _} = Error ->
  706. Error
  707. end.
  708. application_stop_all([]) ->
  709. ok;
  710. application_stop_all([A | As]) ->
  711. case ensure_application_stopped(A) of
  712. ok ->
  713. application_stop_all(As);
  714. {error, _} = Error ->
  715. Error
  716. end.
  717. application_stop_dependencies(Application)
  718. when is_atom(Application) ->
  719. case applications_dependencies(Application) of
  720. {ok, StopAs0} ->
  721. case ensure_application_stopped(Application) of
  722. ok ->
  723. StopAs1 = delete_all(kernel, StopAs0),
  724. StopAs2 = delete_all(stdlib, StopAs1),
  725. Apps = application:loaded_applications(),
  726. {value, _, OtherApps0} = lists:keytake(Application,
  727. 1, Apps),
  728. % determine applications which are not dependencies
  729. case application_stop_external(StopAs2, OtherApps0) of
  730. {ok, OtherAppsN} ->
  731. % check to see the required applications
  732. % separate from the application dependencies
  733. RequiredAs = application_stop_ignore(OtherAppsN),
  734. % ignore all applications that are requirements
  735. % of other applications
  736. StopAsN = lists:reverse(lists:foldl(fun(A, As) ->
  737. delete_all(A, As)
  738. end, StopAs2, RequiredAs)),
  739. % stop all the application dependencies
  740. % that are no longer required
  741. case application_stop_all(StopAsN) of
  742. ok ->
  743. {ok, [Application | StopAsN]};
  744. {error, _} = Error ->
  745. Error
  746. end;
  747. {error, _} = Error ->
  748. Error
  749. end;
  750. {error, _} = Error ->
  751. Error
  752. end;
  753. {error, _} = Error ->
  754. Error
  755. end.
  756. applications_remove([], _) ->
  757. ok;
  758. applications_remove([Application | Applications], Timeout) ->
  759. case application_remove(Application, Timeout) of
  760. ok ->
  761. applications_remove(Applications, Timeout);
  762. {error, _} = Error ->
  763. Error
  764. end.
  765. applications_purged([], _) ->
  766. ok;
  767. applications_purged([Application | Applications], Timeout) ->
  768. case application_purged(Application, Timeout) of
  769. ok ->
  770. applications_purged(Applications, Timeout);
  771. {error, _} = Error ->
  772. Error
  773. end.
  774. applications_dependencies(A) ->
  775. Included = case application:get_key(A, included_applications) of
  776. undefined ->
  777. ok;
  778. {ok, LoadAs} ->
  779. applications_dependencies_load(LoadAs)
  780. end,
  781. if
  782. Included =:= ok ->
  783. case application:get_key(A, applications) of
  784. undefined ->
  785. {error, {undefined_dependencies, A}};
  786. {ok, As} ->
  787. applications_dependencies(As, As)
  788. end;
  789. true ->
  790. Included
  791. end.
  792. applications_dependencies_load([]) ->
  793. ok;
  794. applications_dependencies_load([A | Rest]) ->
  795. case ensure_application_loaded(A) of
  796. ok ->
  797. As1 = case application:get_key(A, included_applications) of
  798. undefined ->
  799. [];
  800. {ok, As0} ->
  801. As0
  802. end,
  803. LoadAs = case application:get_key(A, applications) of
  804. undefined ->
  805. As1;
  806. {ok, As2} ->
  807. As1 ++ As2
  808. end,
  809. case applications_dependencies_load(LoadAs) of
  810. ok ->
  811. applications_dependencies_load(Rest);
  812. {error, _} = Error ->
  813. Error
  814. end;
  815. {error, _} = Error ->
  816. Error
  817. end.
  818. applications_dependencies([], As) ->
  819. {ok, As};
  820. applications_dependencies([A | Rest], As) ->
  821. case ensure_application_loaded(A) of
  822. ok ->
  823. Included = case application:get_key(A, included_applications) of
  824. undefined ->
  825. ok;
  826. {ok, LoadAs} ->
  827. applications_dependencies_load(LoadAs)
  828. end,
  829. if
  830. Included =:= ok ->
  831. case application:get_key(A, applications) of
  832. undefined ->
  833. {error, {undefined_dependencies, A}};
  834. {ok, []} ->
  835. applications_dependencies(Rest, As);
  836. {ok, NextAs} ->
  837. OtherAs = lists:foldl(fun(NextA, L) ->
  838. lists:delete(NextA, L)
  839. end, As, NextAs),
  840. case applications_dependencies(NextAs,
  841. NextAs ++ OtherAs) of
  842. {ok, NewAs} ->
  843. applications_dependencies(Rest, NewAs);
  844. {error, _} = Error ->
  845. Error
  846. end
  847. end;
  848. true ->
  849. Included
  850. end;
  851. {error, _} = Error ->
  852. Error
  853. end.
  854. applications_top_level(Applications) ->
  855. true = erlang:length(lists:usort(Applications)) ==
  856. erlang:length(Applications), % no duplicates
  857. case applications_top_level(Applications, sets:new()) of
  858. {ok, Dependencies} ->
  859. TopLevelApplications = lists:foldl(fun(A, L) ->
  860. lists:delete(A, L)
  861. end, Applications, sets:to_list(Dependencies)),
  862. {ok, TopLevelApplications};
  863. {error, _} = Error ->
  864. Error
  865. end.
  866. applications_top_level([], Dependencies) ->
  867. {ok, Dependencies};
  868. applications_top_level([Application | Applications], Dependencies) ->
  869. case applications_dependencies(Application) of
  870. {ok, As} ->
  871. applications_top_level(Applications,
  872. sets:union(sets:from_list(As),
  873. Dependencies));
  874. {error, _} = Error ->
  875. Error
  876. end.
  877. script_start_instructions(L, Root) ->
  878. Apps = application:loaded_applications(),
  879. script_start_instructions(L, preload, [], Root, Apps).
  880. script_start_instructions([], started, Applications, _, _) ->
  881. applications_top_level(Applications);
  882. script_start_instructions([{progress, Progress} | L],
  883. _, Applications, Root, Apps) ->
  884. script_start_instructions(L, Progress, Applications, Root, Apps);
  885. script_start_instructions([{preLoaded, _} | L],
  886. preload, Applications, Root, Apps) ->
  887. script_start_instructions(L, preload, Applications, Root, Apps);
  888. script_start_instructions([{kernel_load_completed} | L],
  889. preloaded, Applications, Root, Apps) ->
  890. script_start_instructions(L, kernel_load_completed,
  891. Applications, Root, Apps);
  892. script_start_instructions([{path, Paths} | L],
  893. preloaded, Applications, Root, Apps) ->
  894. case ensure_code_paths(Paths, Apps) of
  895. ok ->
  896. script_start_instructions(L, preloaded, Applications, Root, Apps);
  897. {error, _} = Error ->
  898. Error
  899. end;
  900. script_start_instructions([{primLoad, Modules} | L],
  901. preloaded, Applications, Root, Apps) ->
  902. Loaded = lists:all(fun(M) ->
  903. is_module_loaded_check(M) =:= true
  904. end, Modules),
  905. if
  906. Loaded ->
  907. script_start_instructions(L, preloaded, Applications, Root, Apps);
  908. true ->
  909. {error, modules_not_preloaded}
  910. end;
  911. script_start_instructions([{kernel_load_completed} | L],
  912. kernel_load_completed, Applications, Root, Apps) ->
  913. script_start_instructions(L, kernel_load_completed,
  914. Applications, Root, Apps);
  915. script_start_instructions([{primLoad, Modules} | L],
  916. kernel_load_completed, Applications, Root, Apps) ->
  917. case ensure_all_loaded(Modules) of
  918. ok ->
  919. script_start_instructions(L, kernel_load_completed,
  920. Applications, Root, Apps);
  921. {error, _} = Error ->
  922. Error
  923. end;
  924. script_start_instructions([{path, Paths} | L],
  925. kernel_load_completed, Applications, Root, Apps) ->
  926. case load_all_paths(Paths, Root) of
  927. ok ->
  928. script_start_instructions(L, kernel_load_completed,
  929. Applications, Root, Apps);
  930. {error, _} = Error ->
  931. Error
  932. end;
  933. script_start_instructions([{path, _} | L],
  934. modules_loaded, Applications, Root, Apps) ->
  935. script_start_instructions(L, modules_loaded, Applications, Root, Apps);
  936. script_start_instructions([{kernelProcess, _, _} | L],
  937. modules_loaded, Applications, Root, Apps) ->
  938. script_start_instructions(L, modules_loaded, Applications, Root, Apps);
  939. script_start_instructions([{apply, {application, load, [AppDescr]}} | L],
  940. init_kernel_started, Applications, Root, Apps) ->
  941. {application, A, [_ | _] = AppSpecKeys} = AppDescr,
  942. case lists:keyfind(A, 1, Apps) of
  943. {A, _, VSN} ->
  944. {vsn, RequestedVSN} = lists:keyfind(vsn, 1, AppSpecKeys),
  945. if
  946. VSN == RequestedVSN ->
  947. script_start_instructions(L, init_kernel_started,
  948. [A | Applications], Root, Apps);
  949. true ->
  950. {error, {version_mismatch, A, RequestedVSN, VSN}}
  951. end;
  952. false ->
  953. case application:load(AppDescr) of
  954. ok ->
  955. script_start_instructions(L, init_kernel_started,
  956. [A | Applications], Root, Apps);
  957. {error, _} = Error ->
  958. Error
  959. end
  960. end;
  961. script_start_instructions([{apply,
  962. {application, start_boot, [A | _]}} | L],
  963. applications_loaded, Applications, Root, Apps)
  964. when A =:= kernel; A =:= stdlib ->
  965. % if this code is being used, kernel and stdlib should have already
  966. % been started with the boot file that was used to start the Erlang VM
  967. script_start_instructions(L, applications_loaded, Applications, Root, Apps);
  968. script_start_instructions([{apply,
  969. {application, start_boot, [A | _]}} | L],
  970. applications_loaded, Applications, Root, Apps) ->
  971. case ensure_application_started(A) of
  972. ok ->
  973. script_start_instructions(L, applications_loaded,
  974. Applications, Root, Apps);
  975. {error, _} = Error ->
  976. Error
  977. end;
  978. script_start_instructions([{apply, {c, erlangrc, _}} | L],
  979. applications_loaded, Applications, Root, Apps) ->
  980. script_start_instructions(L, applications_loaded,
  981. Applications, Root, Apps).
  982. script_remove_instructions(L) ->
  983. script_remove_instructions(L, preload, []).
  984. script_remove_instructions([], started, Applications) ->
  985. applications_top_level(Applications);
  986. script_remove_instructions([{progress, Progress} | L], _, Applications) ->
  987. script_remove_instructions(L, Progress, Applications);
  988. script_remove_instructions([{preLoaded, _} | L], preload, Applications) ->
  989. script_remove_instructions(L, preload, Applications);
  990. script_remove_instructions([{kernel_load_completed} | L],
  991. preloaded, Applications) ->
  992. script_remove_instructions(L, kernel_load_completed, Applications);
  993. script_remove_instructions([{path, _} | L],
  994. preloaded, Applications) ->
  995. script_remove_instructions(L, preloaded, Applications);
  996. script_remove_instructions([{primLoad, _} | L],
  997. preloaded, Applications) ->
  998. script_remove_instructions(L, preloaded, Applications);
  999. script_remove_instructions([{kernel_load_completed} | L],
  1000. kernel_load_completed, Applications) ->
  1001. script_remove_instructions(L, kernel_load_completed, Applications);
  1002. script_remove_instructions([{primLoad, _} | L],
  1003. kernel_load_completed, Applications) ->
  1004. script_remove_instructions(L, kernel_load_completed, Applications);
  1005. script_remove_instructions([{path, _} | L],
  1006. kernel_load_completed, Applications) ->
  1007. script_remove_instructions(L, kernel_load_completed, Applications);
  1008. script_remove_instructions([{path, _} | L],
  1009. modules_loaded, Applications) ->
  1010. script_remove_instructions(L, modules_loaded, Applications);
  1011. script_remove_instructions([{kernelProcess, _, _} | L],
  1012. modules_loaded, Applications) ->
  1013. script_remove_instructions(L, modules_loaded, Applications);
  1014. script_remove_instructions([{apply, {application, load, [AppDescr]}} | L],
  1015. init_kernel_started, Applications) ->
  1016. {application, A, [_ | _]} = AppDescr,
  1017. script_remove_instructions(L, init_kernel_started, [A | Applications]);
  1018. script_remove_instructions([{apply, {application, start_boot, _}} | L],
  1019. applications_loaded, Applications) ->
  1020. script_remove_instructions(L, applications_loaded, Applications);
  1021. script_remove_instructions([{apply, {c, erlangrc, _}} | L],
  1022. applications_loaded, Applications) ->
  1023. script_remove_instructions(L, applications_loaded, Applications).
  1024. ensure_all_loaded([]) ->
  1025. ok;
  1026. ensure_all_loaded([Module | Modules]) ->
  1027. case is_module_loaded_check(Module) of
  1028. true ->
  1029. Loaded = lists:all(fun(M) ->
  1030. is_module_loaded_check(M) =:= true
  1031. end, Modules),
  1032. if
  1033. Loaded ->
  1034. ok;
  1035. true ->
  1036. {error, modules_partially_loaded}
  1037. end;
  1038. false ->
  1039. NotLoaded = lists:all(fun(M) ->
  1040. is_module_loaded_check(M) =:= false
  1041. end, Modules),
  1042. if
  1043. NotLoaded ->
  1044. load_all_modules([Module | Modules]);
  1045. true ->
  1046. {error, modules_partially_loaded}
  1047. end
  1048. end.
  1049. ensure_code_paths([], _) ->
  1050. ok;
  1051. ensure_code_paths([P | Paths], Apps) ->
  1052. ["$ROOT", "lib", NameVSN, "ebin"] = filename:split(P),
  1053. {Name, VSN} = split_name_vsn(NameVSN),
  1054. Application = erlang:list_to_existing_atom(Name),
  1055. case lists:keyfind(Application, 1, Apps) of
  1056. {Application, _, VSN} ->
  1057. ensure_code_paths(Paths, Apps);
  1058. {Application, _, InvalidVSN} ->
  1059. {error, {version_mismatch, Application, VSN, InvalidVSN}};
  1060. false ->
  1061. {error, {not_loaded, Application, VSN}}
  1062. end.
  1063. is_module_loaded_check(Module) when is_atom(Module) ->
  1064. case code:is_loaded(Module) of
  1065. {file, _} ->
  1066. true;
  1067. false ->
  1068. false
  1069. end.
  1070. modules_purged(Modules, infinity) ->
  1071. modules_purged(Modules, [], 5000);
  1072. modules_purged(Modules, Timeout)
  1073. when is_list(Modules), is_integer(Timeout), Timeout >= 0 ->
  1074. modules_purged(Modules, [], Timeout).
  1075. modules_purged([], [], _) ->
  1076. ok;
  1077. modules_purged([], BusyModules, Timeout) ->
  1078. case erlang:max(Timeout - ?MODULES_PURGED_DELTA, 0) of
  1079. 0 ->
  1080. % attempt to force the purge, killing any processes that remain
  1081. % executing the code
  1082. case lists:dropwhile(fun code:purge/1, BusyModules) of
  1083. [] ->
  1084. ok;
  1085. _ ->
  1086. {error, timeout}
  1087. end;
  1088. NextTimeout ->
  1089. receive after ?MODULES_PURGED_DELTA -> ok end,
  1090. modules_purged(lists:reverse(BusyModules), [], NextTimeout)
  1091. end;
  1092. modules_purged([Module | Modules], BusyModules, Timeout) ->
  1093. % purge the Module if no processes remain executing the code
  1094. case code:soft_purge(Module) of
  1095. true ->
  1096. modules_purged(Modules, BusyModules, Timeout);
  1097. false ->
  1098. modules_purged(Modules, [Module | BusyModules], Timeout)
  1099. end.
  1100. modules_filter([], Modules, _) ->
  1101. Modules;
  1102. modules_filter([{Behaviour, Name} | Options], Modules, Loaded)
  1103. when Behaviour =:= behaviour; Behaviour =:= behavior ->
  1104. NewModules = lists:filter(fun(Module) ->
  1105. Attributes = if
  1106. Loaded =:= true ->
  1107. lists:keyfind(attributes, 1, Module:module_info());
  1108. Loaded =:= false ->
  1109. case code:is_loaded(Module) of
  1110. {file, _} ->
  1111. lists:keyfind(attributes, 1, Module:module_info());
  1112. false ->
  1113. false
  1114. end
  1115. end,
  1116. case Attributes of
  1117. false ->
  1118. false;
  1119. {attributes, []} ->
  1120. false;
  1121. {attributes, AttributesL} ->
  1122. case lists:keyfind(behaviour, 1, AttributesL) of
  1123. false ->
  1124. false;
  1125. {behaviour, []} ->
  1126. false;
  1127. {behaviour, Names} ->
  1128. lists:member(Name, Names)
  1129. end
  1130. end
  1131. end, Modules),
  1132. modules_filter(Options, NewModules, true);
  1133. modules_filter([_ | _], _, _) ->
  1134. erlang:exit(badarg).
  1135. load_all_modules([]) ->
  1136. ok;
  1137. load_all_modules([Module | Modules]) ->
  1138. case code:load_file(Module) of
  1139. {module, Module} ->
  1140. load_all_modules(Modules);
  1141. {error, Reason} ->
  1142. {error, {Reason, Module}}
  1143. end.
  1144. load_all_paths([], _) ->
  1145. ok;
  1146. load_all_paths([P | Paths], Root) ->
  1147. ["$ROOT", "lib", NameVSN, "ebin"] = filename:split(P),
  1148. CodePath = filename:join(Root ++ ["lib", NameVSN, "ebin"]),
  1149. case code:add_pathz(CodePath) of
  1150. true ->
  1151. load_all_paths(Paths, Root);
  1152. {error, Reason} ->
  1153. {error, {Reason, CodePath}}
  1154. end.
  1155. split_name_vsn(NameVSN) ->
  1156. split_name_vsn([], [], NameVSN).
  1157. split_name_vsn([_ | _] = Name, VSN, []) ->
  1158. [$- | FinalVSN] = lists:reverse(VSN),
  1159. {Name, FinalVSN};
  1160. split_name_vsn(Name, NameSegment, [$- | L]) ->
  1161. split_name_vsn(lists:reverse(NameSegment) ++ Name, [$-], L);
  1162. split_name_vsn(Name, VSN, [C | L]) ->
  1163. split_name_vsn(Name, [C | VSN], L).
  1164. delete_all(Elem, List) when is_list(List) ->
  1165. delete_all(Elem, [], List).
  1166. delete_all(Elem, L, [Elem | T]) ->
  1167. delete_all(Elem, L, T);
  1168. delete_all(Elem, L, [H | T]) ->
  1169. delete_all(Elem, [H | L], T);
  1170. delete_all(_, L, []) ->
  1171. lists:reverse(L).