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