1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1996-2020. 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(application_controller). 21 22%% External exports 23-export([start/1, 24 load_application/1, unload_application/1, 25 start_application/2, start_boot_application/2, stop_application/1, 26 control_application/1, 27 change_application_data/2, prep_config_change/0, config_change/1, 28 which_applications/0, which_applications/1, 29 loaded_applications/0, info/0, set_env/2, 30 get_pid_env/2, get_env/2, get_pid_all_env/1, get_all_env/1, 31 get_pid_key/2, get_key/2, get_pid_all_key/1, get_all_key/1, 32 get_master/1, get_application/1, get_application_module/1, 33 start_type/1, permit_application/2, do_config_diff/2, 34 set_env/3, set_env/4, unset_env/2, unset_env/3]). 35 36%% Internal exports 37-export([handle_call/3, handle_cast/2, handle_info/2, terminate/2, 38 code_change/3, init_starter/4, get_loaded/1]). 39 40%% Test exports, only to be used from the test suites 41-export([test_change_apps/2]). 42 43-import(lists, [zf/2, map/2, foreach/2, foldl/3, 44 keyfind/3, keydelete/3, keyreplace/4]). 45 46-include("application_master.hrl"). 47-include("logger.hrl"). 48 49-define(AC, ?MODULE). % Name of process 50 51%%%----------------------------------------------------------------- 52%%% The application_controller controls local applications only. A 53%%% local application can be loaded/started/stopped/unloaded and 54%%% changed. The control of distributed applications is taken care of 55%%% by another process (default is dist_ac). 56%%% 57%%% When an application has been started (by a call to application:start) 58%%% it can be running or not running (on this node). For example, 59%%% a distributed application must be started on all nodes, but 60%%% may be running on one node at the time. 61%%% 62%%% The external API to this module is in the module 'application'. 63%%% 64%%% The process that controls distributed applications (called dist 65%%% ac). calls application_controller:control_application(Name) to 66%%% take responsibility for an application. The interface between AC 67%%% and the dist_ac process is message-based: 68%%% 69%%% AC DIST AC 70%%% == ======= 71%%% --> {ac_load_application_req, Name} 72%%% <-- {ac_load_application_reply, Name, LoadReply} 73%%% --> {ac_start_application_req, Name} (*) 74%%% <-- {ac_start_application_reply, Name, StartReply} 75%%% --> {ac_application_run, Name, Res} 76%%% --> {ac_application_not_run, Name, Res} 77%%% --> {ac_application_stopped, Name} 78%%% --> {ac_application_unloaded, Name} 79%%% <-- {ac_change_application_req, Name, Req} (**) 80%%% 81%%% Where LoadReply = 82%%% ok - App is loaded 83%%% {error, R} - An error occurred 84%%% And StartReply = 85%%% start_it - DIST AC decided that AC should start the app 86%%% {started, Node} - The app is started distributed at Node 87%%% not_started - The app should not be running at this time 88%%% {takeover, Node}- The app should takeover from Node 89%%% {error, R} - an error occurred 90%%% And Req = 91%%% start_it - DIST AC wants AC to start the app locally 92%%% stop_it - AC should stop the app. 93%%% {takeover, Node, RestartType} 94%%% - AC should start the app as a takeover 95%%% {failover, Node, RestartType} 96%%% - AC should start the app as a failover 97%%% {started, Node} - The app is started at Node 98%%% NOTE: The app must have been started at this node 99%%% before this request is sent! 100%%% And Res = 101%%% ok - Application is started locally 102%%% {error, R} - Start of application failed 103%%% 104%%% (*) 105%%% The call to application:start() doesn't return until the 106%%% ac_start_application_reply has been received by AC. AC 107%%% itself is not blocked however. 108%%% (**) 109%%% DIST AC gets ACK to its ac_change_application_req, but not as a 110%%% separate messgage. Instead the normal messages are used as: 111%%% start_it generates an ac_application_run 112%%% stop_it generates an ac_application_not_run 113%%% takeover generates an ac_application_run 114%%% started doesn't generate anything 115%%% 116%%% There is a distinction between application:stop and stop_it 117%%% from a dist ac process. The first one stops the application, 118%%% and resets the internal structures as they were before start was 119%%% called. stop_it stops the application, but just marks it as 120%%% not being running. 121%%% 122%%% When a dist ac process has taken control of an application, no 123%%% other process can take the control. 124%%%----------------------------------------------------------------- 125 126%%----------------------------------------------------------------- 127%% Naming conventions: 128%% App = appl_descr() 129%% Appl = #appl 130%% AppName = atom() 131%% Application = App | AppName 132%%----------------------------------------------------------------- 133 134-type appname() :: atom(). 135 136-record(state, {loading = [], starting = [], start_p_false = [], running = [], 137 control = [], started = [], start_req = [], conf_data}). 138-type state() :: #state{}. 139 140%%----------------------------------------------------------------- 141%% loading = [{AppName, From}] - Load not yet finished 142%% starting = [{AppName, RestartType, Type, From}] - Start not 143%% yet finished 144%% start_p_false = [{AppName, RestartType, Type, From}] - Start not 145%% executed because permit == false 146%% running = [{AppName, Pid}] - running locally (Pid == application_master) 147%% [{AppName, {distributed, Node}}] - running on Node 148%% control = [{AppName, Controller}] 149%% started = [{AppName, RestartType}] - Names of all apps that 150%% have been started (but may not run because 151%% permission = false) 152%% conf_data = [{AppName, Env}] 153%% start_req = [{AppName, From}] - list of all start requests 154%% Id = AMPid | undefined | {distributed, Node} 155%% Env = [{Key, Value}] 156%%----------------------------------------------------------------- 157 158-record(appl, {name, appl_data, descr, id, vsn, restart_type, inc_apps, apps}). 159 160%%----------------------------------------------------------------- 161%% Func: start/1 162%% Args: KernelApp = appl_descr() 163%% appl_descr() = [{application, Name, [appl_opt()]}] 164%% appl_opt() = {description, string()} | 165%% {vsn, string()} | 166%% {id, string()}, | 167%% {modules, [Module]} | 168%% {registered, [atom()]} | 169%% {applications, [atom()]} | 170%% {included_applications, [atom()]} | 171%% {env, [{atom(), term()}]} | 172%% {start_phases, [{atom(), term()}]}| 173%% {maxT, integer()|infinity} | 174%% {maxP, integer()|infinity} | 175%% {mod, {Module, term()}} 176%% Module = atom() 177%% Purpose: Starts the application_controller. This process starts all 178%% application masters for the applications. 179%% The kernel application is the only application that is 180%% treated specially. The reason for this is that the kernel 181%% starts user. This process is special because it should 182%% be group_leader for this process. 183%% Pre: All modules are loaded, or will be loaded on demand. 184%% Returns: {ok, Pid} | ReasonStr 185%%----------------------------------------------------------------- 186start(KernelApp) -> 187 %% OTP-5811 Don't start as a gen_server to prevent crash report 188 %% when (if) the process terminates 189 Init = self(), 190 AC = spawn_link(fun() -> init(Init, KernelApp) end), 191 receive 192 {ack, AC, ok} -> 193 {ok, AC}; 194 {ack, AC, {error, Reason}} -> 195 to_string(Reason); % init doesn't want error tuple, only a reason 196 {'EXIT', _Pid, Reason} -> 197 to_string(Reason) 198 end. 199 200%%----------------------------------------------------------------- 201%% Func: load_application/1 202%% Args: Application = appl_descr() | atom() 203%% Purpose: Loads an application. Currently just inserts the 204%% application's env. 205%% Returns: ok | {error, Reason} 206%%----------------------------------------------------------------- 207load_application(Application) -> 208 gen_server:call(?AC, {load_application, Application}, infinity). 209 210unload_application(AppName) -> 211 gen_server:call(?AC, {unload_application, AppName}, infinity). 212 213%%----------------------------------------------------------------- 214%% Func: start_application/2 215%% Args: Application = atom() 216%% RestartType = permanent | transient | temporary 217%% Purpose: Starts a new application. 218%% The RestartType specifies what should happen if the 219%% application dies: 220%% If it is permanent, all other applications are terminated, 221%% and the application_controller dies. 222%% If it is transient, and the application dies normally, 223%% this is reported and no other applications are terminated. 224%% If the application dies abnormally, all other applications 225%% are terminated, and the application_controller dies. 226%% If it is temporary and the application dies this is reported 227%% and no other applications are terminated. In this way, 228%% an application can run in test mode, without disturbing 229%% the other applications. 230%% The caller of this function is suspended until the application 231%% is started, either locally or distributed. 232%% Returns: ok | {error, Reason} 233%%----------------------------------------------------------------- 234start_application(AppName, RestartType) -> 235 gen_server:call(?AC, {start_application, AppName, RestartType}, infinity). 236 237%%----------------------------------------------------------------- 238%% Func: start_boot_application/2 239%% The same as start_application/2 expect that this function is 240%% called from the boot script file. It mustnot be used by the operator. 241%% This function will cause a node crash if a permanent application 242%% fails to boot start 243%%----------------------------------------------------------------- 244start_boot_application(Application, RestartType) -> 245 case {application:load(Application), RestartType} of 246 {ok, _} -> 247 AppName = get_appl_name(Application), 248 gen_server:call(?AC, {start_application, AppName, RestartType}, infinity); 249 {{error, {already_loaded, AppName}}, _} -> 250 gen_server:call(?AC, {start_application, AppName, RestartType}, infinity); 251 {{error,{bad_environment_value,Env}}, permanent} -> 252 Txt = io_lib:format("Bad environment variable: ~tp Application: ~p", 253 [Env, Application]), 254 exit({error, list_to_atom(lists:flatten(Txt))}); 255 {Error, _} -> 256 Error 257 end. 258 259stop_application(AppName) -> 260 gen_server:call(?AC, {stop_application, AppName}, infinity). 261 262%%----------------------------------------------------------------- 263%% Returns: [{Name, Descr, Vsn}] 264%%----------------------------------------------------------------- 265which_applications() -> 266 gen_server:call(?AC, which_applications). 267which_applications(Timeout) -> 268 gen_server:call(?AC, which_applications, Timeout). 269 270loaded_applications() -> 271 ets:select(ac_tab, 272 [{ 273 {{loaded, '$1'}, #appl{descr = '$2', vsn = '$3', _ = '_'}}, 274 [], 275 [{{'$1', '$2', '$3'}}] 276 }]). 277 278%% Returns some debug info 279info() -> 280 gen_server:call(?AC, info). 281 282control_application(AppName) -> 283 gen_server:call(?AC, {control_application, AppName}, infinity). 284 285%%----------------------------------------------------------------- 286%% Func: change_application_data/2 287%% Args: Applications = [appl_descr()] 288%% Config = [{AppName, [{Par,Val}]}] 289%% Purpose: Change all applications and their parameters on this node. 290%% This function should be used from a release handler, at 291%% the same time as the .app or start.boot file is 292%% introduced. Note that during some time the ACs may have 293%% different view of e.g. the distributed applications. 294%% This is solved by syncing the release installation. 295%% However, strange things may happen if a node crashes 296%% and two other nodes have different opinons about who's 297%% gonna start the applications. The release handler must 298%% shutdown each involved node in this case. 299%% Note that this function is used to change existing apps, 300%% adding new/deleting old isn't handled by this function. 301%% Changes an application's vsn, descr and env. 302%% Returns: ok | {error, Reason} 303%% If an error occurred, the situation may be inconsistent, 304%% so the release handler must restart the node. E.g. if 305%% some applicatation may have got new config data. 306%%----------------------------------------------------------------- 307change_application_data(Applications, Config) -> 308 gen_server:call(?AC, 309 {change_application_data, Applications, Config}, 310 infinity). 311 312prep_config_change() -> 313 gen_server:call(?AC, 314 prep_config_change, 315 infinity). 316 317 318config_change(EnvPrev) -> 319 gen_server:call(?AC, 320 {config_change, EnvPrev}, 321 infinity). 322 323 324 325get_pid_env(Master, Key) -> 326 case ets:match(ac_tab, {{application_master, '$1'}, Master}) of 327 [[AppName]] -> get_env(AppName, Key); 328 _ -> undefined 329 end. 330 331get_env(AppName, Key) -> 332 case ets:lookup(ac_tab, {env, AppName, Key}) of 333 [{_, Val}] -> {ok, Val}; 334 _ -> undefined 335 end. 336 337get_pid_all_env(Master) -> 338 case ets:match(ac_tab, {{application_master, '$1'}, Master}) of 339 [[AppName]] -> get_all_env(AppName); 340 _ -> [] 341 end. 342 343get_all_env(AppName) -> 344 map(fun([Key, Val]) -> {Key, Val} end, 345 ets:match(ac_tab, {{env, AppName, '$1'}, '$2'})). 346 347get_pid_key(Master, Key) -> 348 case ets:match(ac_tab, {{application_master, '$1'}, Master}) of 349 [[AppName]] -> get_key(AppName, Key); 350 _ -> undefined 351 end. 352 353get_key(AppName, Key) -> 354 case ets:lookup(ac_tab, {loaded, AppName}) of 355 [{_, Appl}] -> 356 case Key of 357 description -> 358 {ok, Appl#appl.descr}; 359 id -> 360 {ok, Appl#appl.id}; 361 vsn -> 362 {ok, Appl#appl.vsn}; 363 modules -> 364 {ok, (Appl#appl.appl_data)#appl_data.mods}; 365 maxP -> 366 {ok, (Appl#appl.appl_data)#appl_data.maxP}; 367 maxT -> 368 {ok, (Appl#appl.appl_data)#appl_data.maxT}; 369 registered -> 370 {ok, (Appl#appl.appl_data)#appl_data.regs}; 371 included_applications -> 372 {ok, Appl#appl.inc_apps}; 373 applications -> 374 {ok, Appl#appl.apps}; 375 env -> 376 {ok, get_all_env(AppName)}; 377 mod -> 378 {ok, (Appl#appl.appl_data)#appl_data.mod}; 379 start_phases -> 380 {ok, (Appl#appl.appl_data)#appl_data.phases}; 381 _ -> undefined 382 end; 383 _ -> 384 undefined 385 end. 386 387get_pid_all_key(Master) -> 388 case ets:match(ac_tab, {{application_master, '$1'}, Master}) of 389 [[AppName]] -> get_all_key(AppName); 390 _ -> [] 391 end. 392 393get_all_key(AppName) -> 394 case ets:lookup(ac_tab, {loaded, AppName}) of 395 [{_, Appl}] -> 396 {ok, [{description, Appl#appl.descr}, 397 {id, Appl#appl.id}, 398 {vsn, Appl#appl.vsn}, 399 {modules, (Appl#appl.appl_data)#appl_data.mods}, 400 {maxP, (Appl#appl.appl_data)#appl_data.maxP}, 401 {maxT, (Appl#appl.appl_data)#appl_data.maxT}, 402 {registered, (Appl#appl.appl_data)#appl_data.regs}, 403 {included_applications, Appl#appl.inc_apps}, 404 {applications, Appl#appl.apps}, 405 {env, get_all_env(AppName)}, 406 {mod, (Appl#appl.appl_data)#appl_data.mod}, 407 {start_phases, (Appl#appl.appl_data)#appl_data.phases} 408 ]}; 409 _ -> 410 undefined 411 end. 412 413 414start_type(Master) -> 415 case ets:match(ac_tab, {{application_master, '$1'}, Master}) of 416 [[AppName]] -> 417 gen_server:call(?AC, {start_type, AppName}, infinity); 418 _X -> 419 undefined 420 end. 421 422 423 424 425 426 427get_master(AppName) -> 428 case ets:lookup(ac_tab, {application_master, AppName}) of 429 [{_, Pid}] -> Pid; 430 _ -> undefined 431 end. 432 433get_application(Master) -> 434 case ets:match(ac_tab, {{application_master, '$1'}, Master}) of 435 [[AppName]] -> {ok, AppName}; 436 _ -> undefined 437 end. 438 439get_application_module(Module) -> 440 ApplDataPattern = #appl_data{mods='$2', _='_'}, 441 ApplPattern = #appl{appl_data=ApplDataPattern, _='_'}, 442 AppModules = ets:match(ac_tab, {{loaded, '$1'}, ApplPattern}), 443 get_application_module(Module, AppModules). 444 445get_application_module(Module, [[AppName, Modules]|AppModules]) -> 446 case lists:member(Module, Modules) of 447 true -> 448 {ok, AppName}; 449 false -> 450 get_application_module(Module, AppModules) 451 end; 452get_application_module(_Module, []) -> 453 undefined. 454 455permit_application(ApplName, Flag) -> 456 gen_server:call(?AC, 457 {permit_application, ApplName, Flag}, 458 infinity). 459 460set_env(Config, Opts) -> 461 case check_conf_data(Config) of 462 ok -> 463 Timeout = proplists:get_value(timeout, Opts, 5000), 464 gen_server:call(?AC, {set_env, Config, Opts}, Timeout); 465 466 {error, _} = Error -> 467 Error 468 end. 469 470set_env(AppName, Key, Val) -> 471 gen_server:call(?AC, {set_env, AppName, Key, Val, []}). 472set_env(AppName, Key, Val, Opts) -> 473 Timeout = proplists:get_value(timeout, Opts, 5000), 474 gen_server:call(?AC, {set_env, AppName, Key, Val, Opts}, Timeout). 475 476unset_env(AppName, Key) -> 477 gen_server:call(?AC, {unset_env, AppName, Key, []}). 478unset_env(AppName, Key, Opts) -> 479 Timeout = proplists:get_value(timeout, Opts, 5000), 480 gen_server:call(?AC, {unset_env, AppName, Key, Opts}, Timeout). 481 482%%%----------------------------------------------------------------- 483%%% call-back functions from gen_server 484%%%----------------------------------------------------------------- 485init(Init, Kernel) -> 486 register(?AC, self()), 487 process_flag(trap_exit, true), 488 put('$ancestors', [Init]), % OTP-5811, for gen_server compatibility 489 put('$initial_call', {application_controller, start, 1}), 490 491 case catch check_conf() of 492 {ok, ConfData} -> 493 %% Actually, we don't need this info in an ets table anymore. 494 %% This table was introduced because starting applications 495 %% should be able to get som info from AC (e.g. loaded_apps). 496 %% The new implementation makes sure the AC process can be 497 %% called during start-up of any app. 498 case check_conf_data(ConfData) of 499 ok -> 500 _ = ets:new(ac_tab, [set, public, named_table, 501 {read_concurrency,true}]), 502 S = #state{conf_data = ConfData}, 503 {ok, KAppl} = make_appl(Kernel), 504 case catch load(S, KAppl) of 505 {'EXIT', LoadError} -> 506 Reason = {'load error', LoadError}, 507 Init ! {ack, self(), {error, to_string(Reason)}}; 508 {error, Error} -> 509 Init ! {ack, self(), {error, to_string(Error)}}; 510 {ok, NewS} -> 511 Init ! {ack, self(), ok}, 512 gen_server:enter_loop(?MODULE, [], NewS, 513 {local, ?AC}) 514 end; 515 {error, ErrorStr} -> 516 Str = lists:flatten(io_lib:format("invalid config data: ~ts", [ErrorStr])), 517 Init ! {ack, self(), {error, to_string(Str)}} 518 end; 519 {error, {File, Line, Str}} -> 520 ReasonStr = 521 lists:flatten(io_lib:format("error in config file " 522 "~tp (~w): ~ts", 523 [File, Line, Str])), 524 Init ! {ack, self(), {error, to_string(ReasonStr)}} 525 end. 526 527 528%% Check the syntax of the .config file 529%% [{ApplicationName, [{Parameter, Value}]}]. 530 531check_conf_data([]) -> 532 ok; 533check_conf_data(ConfData) when is_list(ConfData) -> 534 [Application | ConfDataRem] = ConfData, 535 case Application of 536 {AppName, List} when is_atom(AppName), is_list(List) -> 537 case lists:keymember(AppName, 1, ConfDataRem) of 538 true -> 539 {error, "duplicate application config: " ++ atom_to_list(AppName)}; 540 false -> 541 case check_para(List, AppName) of 542 ok -> check_conf_data(ConfDataRem); 543 Error -> Error 544 end 545 end; 546 {AppName, List} when is_list(List) -> 547 ErrMsg = "application: " 548 ++ lists:flatten(io_lib:format("~tp",[AppName])) 549 ++ "; application name must be an atom", 550 {error, ErrMsg}; 551 {AppName, _List} -> 552 ErrMsg = "application: " 553 ++ lists:flatten(io_lib:format("~tp",[AppName])) 554 ++ "; parameters must be a list", 555 {error, ErrMsg}; 556 Else -> 557 ErrMsg = "invalid application config: " 558 ++ lists:flatten(io_lib:format("~tp",[Else])), 559 {error, ErrMsg} 560 end; 561check_conf_data(_ConfData) -> 562 {error, "configuration must be a list ended by <dot><whitespace>"}. 563 564 565check_para([], _AppName) -> 566 ok; 567check_para([{Para, Val} | ParaList], AppName) when is_atom(Para) -> 568 case lists:keymember(Para, 1, ParaList) of 569 true -> 570 ErrMsg = "application: " ++ atom_to_list(AppName) 571 ++ "; duplicate parameter: " ++ atom_to_list(Para), 572 {error, ErrMsg}; 573 false -> 574 case check_para_value(Para, Val, AppName) of 575 ok -> check_para(ParaList, AppName); 576 {error, _} = Error -> Error 577 end 578 end; 579check_para([{Para, _Val} | _ParaList], AppName) -> 580 {error, "application: " ++ atom_to_list(AppName) ++ "; invalid parameter name: " ++ 581 lists:flatten(io_lib:format("~tp",[Para]))}; 582check_para([Else | _ParaList], AppName) -> 583 {error, "application: " ++ atom_to_list(AppName) ++ "; invalid parameter: " ++ 584 lists:flatten(io_lib:format("~tp",[Else]))}. 585 586check_para_value(distributed, Apps, kernel) -> check_distributed(Apps); 587check_para_value(_Para, _Val, _AppName) -> ok. 588 589%% Special check of distributed parameter for kernel 590check_distributed([]) -> 591 ok; 592check_distributed([{App, List} | Apps]) when is_atom(App), is_list(List) -> 593 check_distributed(Apps); 594check_distributed([{App, infinity, List} | Apps]) when is_atom(App), is_list(List) -> 595 check_distributed(Apps); 596check_distributed([{App, Time, List} | Apps]) when is_atom(App), is_integer(Time), is_list(List) -> 597 check_distributed(Apps); 598check_distributed(_Else) -> 599 {error, "application: kernel; erroneous parameter: distributed"}. 600 601 602-type calls() :: 'info' | 'prep_config_change' | 'which_applications' 603 | {'config_change' | 'control_application' | 604 'load_application' | 'start_type' | 'stop_application' | 605 'unload_application', term()} 606 | {'change_application_data', _, _} 607 | {'permit_application', atom() | {'application',atom(),_},_} 608 | {'start_application', _, _} 609 | {'unset_env', _, _, _} 610 | {'set_env', _, _, _, _}. 611 612-spec handle_call(calls(), {pid(), term()}, state()) -> 613 {'noreply', state()} | {'reply', term(), state()}. 614 615handle_call({load_application, Application}, From, S) -> 616 case catch do_load_application(Application, S) of 617 {ok, NewS} -> 618 AppName = get_appl_name(Application), 619 case cntrl(AppName, S, {ac_load_application_req, AppName}) of 620 true -> 621 {noreply, S#state{loading = [{AppName, From} | 622 S#state.loading]}}; 623 false -> 624 {reply, ok, NewS} 625 end; 626 {error, _} = Error -> 627 {reply, Error, S}; 628 {'EXIT', R} -> 629 {reply, {error, R}, S} 630 end; 631 632handle_call({unload_application, AppName}, _From, S) -> 633 case lists:keymember(AppName, 1, S#state.running) of 634 true -> {reply, {error, {running, AppName}}, S}; 635 false -> 636 case get_loaded(AppName) of 637 {true, _} -> 638 NewS = unload(AppName, S), 639 cntrl(AppName, S, {ac_application_unloaded, AppName}), 640 {reply, ok, NewS}; 641 false -> 642 {reply, {error, {not_loaded, AppName}}, S} 643 end 644 end; 645 646handle_call({start_application, AppName, RestartType}, From, S) -> 647 #state{running = Running, starting = Starting, start_p_false = SPF, 648 started = Started, start_req = Start_req} = S, 649 %% Check if the commandline environment variables are OK. 650 %% Incase of erroneous variables do not start the application, 651 %% if the application is permanent crash the node. 652 %% Check if the application is already starting. 653 case lists:keyfind(AppName, 1, Start_req) of 654 false -> 655 case catch check_start_cond(AppName, RestartType, Started, Running) of 656 {ok, Appl} -> 657 Cntrl = cntrl(AppName, S, {ac_start_application_req, AppName}), 658 Perm = application:get_env(kernel, permissions), 659 case {Cntrl, Perm} of 660 {true, _} -> 661 {noreply, S#state{starting = [{AppName, RestartType, normal, From} | 662 Starting], 663 start_req = [{AppName, From} | Start_req]}}; 664 {false, undefined} -> 665 spawn_starter(From, Appl, S, normal), 666 {noreply, S#state{starting = [{AppName, RestartType, normal, From} | 667 Starting], 668 start_req = [{AppName, From} | Start_req]}}; 669 {false, {ok, Perms}} -> 670 case lists:member({AppName, false}, Perms) of 671 false -> 672 spawn_starter(From, Appl, S, normal), 673 {noreply, S#state{starting = [{AppName, RestartType, normal, From} | 674 Starting], 675 start_req = [{AppName, From} | Start_req]}}; 676 true -> 677 SS = S#state{start_p_false = [{AppName, RestartType, normal, From} | 678 SPF]}, 679 {reply, ok, SS} 680 end 681 end; 682 {error, _R} = Error -> 683 {reply, Error, S} 684 end; 685 {AppName, _FromX} -> 686 SS = S#state{start_req = [{AppName, From} | Start_req]}, 687 {noreply, SS} 688 end; 689 690handle_call({permit_application, AppName, Bool}, From, S) -> 691 Control = S#state.control, 692 Starting = S#state.starting, 693 SPF = S#state.start_p_false, 694 Started = S#state.started, 695 Running = S#state.running, 696 Start_req = S#state.start_req, 697 IsLoaded = get_loaded(AppName), 698 IsStarting = lists:keysearch(AppName, 1, Starting), 699 IsSPF = lists:keysearch(AppName, 1, SPF), 700 IsStarted = lists:keysearch(AppName, 1, Started), 701 IsRunning = lists:keysearch(AppName, 1, Running), 702 703 case lists:keymember(AppName, 1, Control) of 704 %%======================== 705 %% distributed application 706 %%======================== 707 true -> 708 case {IsLoaded, IsStarting, IsStarted} of 709 %% not loaded 710 {false, _, _} -> 711 {reply, {error, {not_loaded, AppName}}, S}; 712 %% only loaded 713 {{true, _Appl}, false, false} -> 714 update_permissions(AppName, Bool), 715 {reply, {distributed_application, only_loaded}, S}; 716 _ -> 717 update_permissions(AppName, Bool), 718 {reply, distributed_application, S} 719 end; 720 %%======================== 721 %% local application 722 %%======================== 723 false -> 724 case {Bool, IsLoaded, IsStarting, IsSPF, IsStarted, IsRunning} of 725 %%------------------------ 726 %% permit the applicaition 727 %%------------------------ 728 %% already running 729 {true, _, _, _, _, {value, _Tuple}} -> 730 {reply, ok, S}; 731 %% not loaded 732 {true, false, _, _, _, _} -> 733 {reply, {error, {not_loaded, AppName}}, S}; 734 %% only loaded 735 {true, {true, _Appl}, false, false, false, false} -> 736 update_permissions(AppName, Bool), 737 {reply, ok, S}; 738 %% starting 739 {true, {true, _Appl}, {value, _Tuple}, false, false, false} -> 740 update_permissions(AppName, Bool), 741 {reply, ok, S}; %% check the permission after then app is started 742 %% start requested but not started because permit was false 743 {true, {true, Appl}, false, {value, Tuple}, false, false} -> 744 update_permissions(AppName, Bool), 745 {_AppName2, RestartType, normal, _From} = Tuple, 746 spawn_starter(From, Appl, S, normal), 747 SS = S#state{starting = [{AppName, RestartType, normal, From} | Starting], 748 start_p_false = keydelete(AppName, 1, SPF), 749 start_req = [{AppName, From} | Start_req]}, 750 {noreply, SS}; 751 %% started but not running 752 {true, {true, Appl}, _, _, {value, {AppName, RestartType}}, false} -> 753 update_permissions(AppName, Bool), 754 spawn_starter(From, Appl, S, normal), 755 SS = S#state{starting = [{AppName, RestartType, normal, From} | Starting], 756 started = keydelete(AppName, 1, Started), 757 start_req = [{AppName, From} | Start_req]}, 758 {noreply, SS}; 759 760 %%========================== 761 %% unpermit the application 762 %%========================== 763 %% running 764 {false, _, _, _, _, {value, {_AppName, Id}}} -> 765 {_AppName2, Type} = lists:keyfind(AppName, 1, Started), 766 stop_appl(AppName, Id, Type), 767 NRunning = keydelete(AppName, 1, Running), 768 {reply, ok, S#state{running = NRunning}}; 769 %% not loaded 770 {false, false, _, _, _, _} -> 771 {reply, {error, {not_loaded, AppName}}, S}; 772 %% only loaded 773 {false, {true, _Appl}, false, false, false, false} -> 774 update_permissions(AppName, Bool), 775 {reply, ok, S}; 776 %% starting 777 {false, {true, _Appl}, {value, _Tuple}, false, false, false} -> 778 update_permissions(AppName, Bool), 779 {reply, ok, S}; 780 %% start requested but not started because permit was false 781 {false, {true, _Appl}, false, {value, _Tuple}, false, false} -> 782 update_permissions(AppName, Bool), 783 SS = S#state{start_p_false = keydelete(AppName, 1, SPF)}, 784 {reply, ok, SS}; 785 %% started but not running 786 {false, {true, _Appl}, _, _, {value, _Tuple}, false} -> 787 update_permissions(AppName, Bool), 788 {reply, ok, S} 789 790 end 791 end; 792 793handle_call({stop_application, AppName}, _From, S) -> 794 #state{running = Running, started = Started} = S, 795 case lists:keyfind(AppName, 1, Running) of 796 {_AppName, Id} -> 797 {_AppName2, Type} = lists:keyfind(AppName, 1, Started), 798 stop_appl(AppName, Id, Type), 799 NRunning = keydelete(AppName, 1, Running), 800 NStarted = keydelete(AppName, 1, Started), 801 cntrl(AppName, S, {ac_application_stopped, AppName}), 802 {reply, ok, S#state{running = NRunning, started = NStarted}}; 803 false -> 804 case lists:keymember(AppName, 1, Started) of 805 true -> 806 NStarted = keydelete(AppName, 1, Started), 807 cntrl(AppName, S, {ac_application_stopped, AppName}), 808 {reply, ok, S#state{started = NStarted}}; 809 false -> 810 {reply, {error, {not_started, AppName}}, S} 811 end 812 end; 813 814handle_call({change_application_data, Applications, Config}, _From, S) -> 815 OldAppls = ets:filter(ac_tab, 816 fun([{{loaded, _AppName}, Appl}]) -> 817 {true, Appl}; 818 (_) -> 819 false 820 end, 821 []), 822 case catch do_change_apps(Applications, Config, OldAppls) of 823 {error, _} = Error -> 824 {reply, Error, S}; 825 {'EXIT', R} -> 826 {reply, {error, R}, S}; 827 {NewAppls, NewConfig} -> 828 lists:foreach(fun(Appl) -> 829 ets:insert(ac_tab, {{loaded, Appl#appl.name}, 830 Appl}) 831 end, NewAppls), 832 {reply, ok, S#state{conf_data = NewConfig}} 833 end; 834 835handle_call(prep_config_change, _From, S) -> 836 RunningApps = S#state.running, 837 EnvBefore = lists:reverse(do_prep_config_change(RunningApps)), 838 {reply, EnvBefore, S}; 839 840handle_call({config_change, EnvBefore}, _From, S) -> 841 RunningApps = S#state.running, 842 R = do_config_change(RunningApps, EnvBefore), 843 {reply, R, S}; 844 845handle_call(which_applications, _From, S) -> 846 Reply = zf(fun({Name, Id}) -> 847 case Id of 848 {distributed, _Node} -> 849 false; 850 _ -> 851 {true, #appl{descr = Descr, vsn = Vsn}} = 852 get_loaded(Name), 853 {true, {Name, Descr, Vsn}} 854 end 855 end, S#state.running), 856 {reply, Reply, S}; 857 858handle_call({set_env, Config, Opts}, _From, S) -> 859 _ = [add_env(AppName, Env) || {AppName, Env} <- Config], 860 861 case proplists:get_value(persistent, Opts, false) of 862 true -> 863 {reply, ok, S#state{conf_data = merge_env(S#state.conf_data, Config)}}; 864 false -> 865 {reply, ok, S} 866 end; 867 868handle_call({set_env, AppName, Key, Val, Opts}, _From, S) -> 869 ets:insert(ac_tab, {{env, AppName, Key}, Val}), 870 case proplists:get_value(persistent, Opts, false) of 871 true -> 872 Fun = fun(Env) -> lists:keystore(Key, 1, Env, {Key, Val}) end, 873 {reply, ok, S#state{conf_data = change_app_env(S#state.conf_data, AppName, Fun)}}; 874 false -> 875 {reply, ok, S} 876 end; 877 878handle_call({unset_env, AppName, Key, Opts}, _From, S) -> 879 ets:delete(ac_tab, {env, AppName, Key}), 880 case proplists:get_value(persistent, Opts, false) of 881 true -> 882 Fun = fun(Env) -> lists:keydelete(Key, 1, Env) end, 883 {reply, ok, S#state{conf_data = change_app_env(S#state.conf_data, AppName, Fun)}}; 884 false -> 885 {reply, ok, S} 886 end; 887 888handle_call({control_application, AppName}, {Pid, _Tag}, S) -> 889 Control = S#state.control, 890 case lists:keymember(AppName, 1, Control) of 891 false -> 892 link(Pid), 893 {reply, true, S#state{control = [{AppName, Pid} | Control]}}; 894 true -> 895 {reply, false, S} 896 end; 897 898handle_call({start_type, AppName}, _From, S) -> 899 Starting = S#state.starting, 900 StartType = case lists:keyfind(AppName, 1, Starting) of 901 false -> 902 local; 903 {_AppName, _RestartType, Type, _F} -> 904 Type 905 end, 906 {reply, StartType, S}; 907 908handle_call(info, _From, S) -> 909 Reply = [{loaded, loaded_applications()}, 910 {loading, S#state.loading}, 911 {started, S#state.started}, 912 {start_p_false, S#state.start_p_false}, 913 {running, S#state.running}, 914 {starting, S#state.starting}], 915 {reply, Reply, S}. 916 917-spec handle_cast({'application_started', appname(), _}, state()) -> 918 {'noreply', state()} | {'stop', string(), state()}. 919 920handle_cast({application_started, AppName, Res}, S) -> 921 handle_application_started(AppName, Res, S). 922 923handle_application_started(AppName, Res, S) -> 924 #state{starting = Starting, running = Running, started = Started, 925 start_req = Start_req} = S, 926 Start_reqN = reply_to_requester(AppName, Start_req, Res), 927 {_AppName, RestartType, _Type, _From} = lists:keyfind(AppName, 1, Starting), 928 case Res of 929 {ok, Id} -> 930 case AppName of 931 kernel -> check_user(); 932 _ -> ok 933 end, 934 info_started(AppName, nd(Id)), 935 notify_cntrl_started(AppName, Id, S, ok), 936 NRunning = keyreplaceadd(AppName, 1, Running,{AppName,Id}), 937 NStarted = keyreplaceadd(AppName, 1, Started,{AppName,RestartType}), 938 NewS = S#state{starting = keydelete(AppName, 1, Starting), 939 running = NRunning, 940 started = NStarted, 941 start_req = Start_reqN}, 942 %% The permission may have been changed during start 943 Perm = application:get_env(kernel, permissions), 944 case {Perm, Id} of 945 {undefined, _} -> 946 {noreply, NewS}; 947 %% Check only if the application is started on the own node 948 {{ok, Perms}, {distributed, StartNode}} when StartNode =:= node() -> 949 case lists:member({AppName, false}, Perms) of 950 true -> 951 #state{running = StopRunning, started = StopStarted} = NewS, 952 case lists:keyfind(AppName, 1, StopRunning) of 953 {_AppName, Id} -> 954 {_AppName2, Type} = 955 lists:keyfind(AppName, 1, StopStarted), 956 stop_appl(AppName, Id, Type), 957 NStopRunning = keydelete(AppName, 1, StopRunning), 958 cntrl(AppName, NewS, {ac_application_stopped, AppName}), 959 {noreply, NewS#state{running = NStopRunning, 960 started = StopStarted}}; 961 false -> 962 {noreply, NewS} 963 end; 964 false -> 965 {noreply, NewS} 966 end; 967 _ -> 968 {noreply, NewS} 969 end; 970 {error, R} = Error when RestartType =:= temporary -> 971 notify_cntrl_started(AppName, undefined, S, Error), 972 info_exited(AppName, R, RestartType), 973 {noreply, S#state{starting = keydelete(AppName, 1, Starting), 974 start_req = Start_reqN}}; 975 {info, R} when RestartType =:= temporary -> 976 notify_cntrl_started(AppName, undefined, S, {error, R}), 977 {noreply, S#state{starting = keydelete(AppName, 1, Starting), 978 start_req = Start_reqN}}; 979 {ErrInf, R} when RestartType =:= transient, ErrInf =:= error; 980 RestartType =:= transient, ErrInf =:= info -> 981 notify_cntrl_started(AppName, undefined, S, {error, R}), 982 case ErrInf of 983 error -> 984 info_exited(AppName, R, RestartType); 985 info -> 986 ok 987 end, 988 case R of 989 {{'EXIT',normal},_Call} -> 990 {noreply, S#state{starting = keydelete(AppName, 1, Starting), 991 start_req = Start_reqN}}; 992 _ -> 993 Reason = {application_start_failure, AppName, R}, 994 {stop, to_string(Reason), S} 995 end; 996 {error, R} = Error -> %% permanent 997 notify_cntrl_started(AppName, undefined, S, Error), 998 info_exited(AppName, R, RestartType), 999 Reason = {application_start_failure, AppName, R}, 1000 {stop, to_string(Reason), S}; 1001 {info, R} -> %% permanent 1002 notify_cntrl_started(AppName, undefined, S, {error, R}), 1003 Reason = {application_start_failure, AppName, R}, 1004 {stop, to_string(Reason), S} 1005 end. 1006 1007-spec handle_info(term(), state()) -> 1008 {'noreply', state()} | {'stop', string(), state()}. 1009 1010handle_info({ac_load_application_reply, AppName, Res}, S) -> 1011 case keysearchdelete(AppName, 1, S#state.loading) of 1012 {value, {_AppName, From}, Loading} -> 1013 gen_server:reply(From, Res), 1014 case Res of 1015 ok -> 1016 {noreply, S#state{loading = Loading}}; 1017 {error, _R} -> 1018 NewS = unload(AppName, S), 1019 {noreply, NewS#state{loading = Loading}} 1020 end; 1021 false -> 1022 {noreply, S} 1023 end; 1024 1025handle_info({ac_start_application_reply, AppName, Res}, S) -> 1026 Start_req = S#state.start_req, 1027 case lists:keyfind(AppName, 1, Starting = S#state.starting) of 1028 {_AppName, RestartType, Type, From} -> 1029 case Res of 1030 start_it -> 1031 {true, Appl} = get_loaded(AppName), 1032 spawn_starter(From, Appl, S, Type), 1033 {noreply, S}; 1034 {started, Node} -> 1035 handle_application_started(AppName, 1036 {ok, {distributed, Node}}, 1037 S); 1038 not_started -> 1039 Started = S#state.started, 1040 Start_reqN = 1041 reply_to_requester(AppName, Start_req, ok), 1042 {noreply, 1043 S#state{starting = keydelete(AppName, 1, Starting), 1044 started = [{AppName, RestartType} | Started], 1045 start_req = Start_reqN}}; 1046 {takeover, _Node} = Takeover -> 1047 {true, Appl} = get_loaded(AppName), 1048 spawn_starter(From, Appl, S, Takeover), 1049 NewStarting1 = keydelete(AppName, 1, Starting), 1050 NewStarting = [{AppName, RestartType, Takeover, From} | NewStarting1], 1051 {noreply, S#state{starting = NewStarting}}; 1052 {error, Reason} = Error when RestartType =:= permanent -> 1053 Start_reqN = reply_to_requester(AppName, Start_req, Error), 1054 {stop, to_string(Reason), S#state{start_req = Start_reqN}}; 1055 {error, _Reason} = Error -> 1056 Start_reqN = reply_to_requester(AppName, Start_req, Error), 1057 {noreply, S#state{starting = 1058 keydelete(AppName, 1, Starting), 1059 start_req = Start_reqN}} 1060 end; 1061 false -> 1062 {noreply, S} % someone called stop before control got that 1063 end; 1064 1065handle_info({ac_change_application_req, AppName, Msg}, S) -> 1066 Running = S#state.running, 1067 Started = S#state.started, 1068 Starting = S#state.starting, 1069 case {keyfind(AppName, 1, Running), keyfind(AppName, 1, Started)} of 1070 {{AppName, Id}, {_AppName2, Type}} -> 1071 case Msg of 1072 {started, Node} -> 1073 stop_appl(AppName, Id, Type), 1074 NRunning = [{AppName, {distributed, Node}} | 1075 keydelete(AppName, 1, Running)], 1076 {noreply, S#state{running = NRunning}}; 1077 {takeover, _Node, _RT} when is_pid(Id) -> % it is running already 1078 notify_cntrl_started(AppName, Id, S, ok), 1079 {noreply, S}; 1080 {takeover, Node, RT} -> 1081 NewS = do_start(AppName, RT, {takeover, Node}, undefined, S), 1082 {noreply, NewS}; 1083 {failover, _Node, _RT} when is_pid(Id) -> % it is running already 1084 notify_cntrl_started(AppName, Id, S, ok), 1085 {noreply, S}; 1086 {failover, Node, RT} -> 1087 case application:get_key(AppName, start_phases) of 1088 {ok, undefined} -> 1089 %% to be backwards compatible the application 1090 %% is not started as failover if start_phases 1091 %% is not defined in the .app file 1092 NewS = do_start(AppName, RT, normal, undefined, S), 1093 {noreply, NewS}; 1094 {ok, _StartPhases} -> 1095 NewS = do_start(AppName, RT, {failover, Node}, undefined, S), 1096 {noreply, NewS} 1097 end; 1098 stop_it -> 1099 stop_appl(AppName, Id, Type), 1100 cntrl(AppName, S, {ac_application_not_run, AppName}), 1101 NRunning = keyreplace(AppName, 1, Running, 1102 {AppName, {distributed, []}}), 1103 {noreply, S#state{running = NRunning}}; 1104 %% We should not try to start a running application! 1105 start_it when is_pid(Id) -> 1106 notify_cntrl_started(AppName, Id, S, ok), 1107 {noreply, S}; 1108 start_it -> 1109 NewS = do_start(AppName, undefined, normal, undefined, S), 1110 {noreply, NewS}; 1111 not_running -> 1112 NRunning = keydelete(AppName, 1, Running), 1113 {noreply, S#state{running = NRunning}}; 1114 _ -> 1115 {noreply, S} 1116 end; 1117 _ -> 1118 IsLoaded = get_loaded(AppName), 1119 IsStarting = lists:keysearch(AppName, 1, Starting), 1120 IsStarted = lists:keysearch(AppName, 1, Started), 1121 IsRunning = lists:keysearch(AppName, 1, Running), 1122 1123 case Msg of 1124 start_it -> 1125 case {IsLoaded, IsStarting, IsStarted, IsRunning} of 1126 %% already running 1127 {_, _, _, {value, _Tuple}} -> 1128 {noreply, S}; 1129 %% not loaded 1130 {false, _, _, _} -> 1131 {noreply, S}; 1132 %% only loaded 1133 {{true, _Appl}, false, false, false} -> 1134 {noreply, S}; 1135 %% starting 1136 {{true, _Appl}, {value, Tuple}, false, false} -> 1137 {_AppName, _RStype, _Type, From} = Tuple, 1138 NewS = do_start(AppName, undefined, normal, From, S), 1139 {noreply, NewS}; 1140 %% started but not running 1141 {{true, _Appl}, _, {value, {AppName, _RestartType}}, false} -> 1142 NewS = do_start(AppName, undefined, normal, undefined, S), 1143 SS = NewS#state{started = keydelete(AppName, 1, Started)}, 1144 {noreply, SS} 1145 end; 1146 {started, Node} -> 1147 NRunning = [{AppName, {distributed, Node}} | 1148 keydelete(AppName, 1, Running)], 1149 {noreply, S#state{running = NRunning}}; 1150 _ -> 1151 {noreply, S} % someone called stop before control got that 1152 end 1153 end; 1154 1155%%----------------------------------------------------------------- 1156%% An application died. Check its restart_type. Maybe terminate 1157%% all other applications. 1158%%----------------------------------------------------------------- 1159handle_info({'EXIT', Pid, Reason}, S) -> 1160 ets:match_delete(ac_tab, {{application_master, '_'}, Pid}), 1161 NRunning = keydelete(Pid, 2, S#state.running), 1162 NewS = S#state{running = NRunning}, 1163 case lists:keyfind(Pid, 2, S#state.running) of 1164 {AppName, _AmPid} -> 1165 cntrl(AppName, S, {ac_application_stopped, AppName}), 1166 case lists:keyfind(AppName, 1, S#state.started) of 1167 {_AppName, temporary} -> 1168 info_exited(AppName, Reason, temporary), 1169 {noreply, NewS}; 1170 {_AppName, transient} when Reason =:= normal -> 1171 info_exited(AppName, Reason, transient), 1172 {noreply, NewS}; 1173 {_AppName, Type} -> 1174 info_exited(AppName, Reason, Type), 1175 {stop, to_string({application_terminated, AppName, Reason}), NewS} 1176 end; 1177 false -> 1178 {noreply, S#state{control = del_cntrl(S#state.control, Pid)}} 1179 end; 1180 1181handle_info(_, S) -> 1182 {noreply, S}. 1183 1184-spec terminate(term(), state()) -> 'ok'. 1185 1186terminate(Reason, S) -> 1187 case application:get_env(kernel, shutdown_func) of 1188 {ok, {M, F}} -> 1189 catch M:F(Reason); 1190 _ -> 1191 ok 1192 end, 1193 ShutdownTimeout = 1194 case application:get_env(kernel, shutdown_timeout) of 1195 undefined -> infinity; 1196 {ok,T} -> T 1197 end, 1198 foreach(fun({_AppName, Id}) when is_pid(Id) -> 1199 Ref = erlang:monitor(process, Id), 1200 unlink(Id), 1201 exit(Id, shutdown), 1202 receive 1203 %% Proc died before link 1204 {'EXIT', Id, _} -> ok 1205 after 0 -> 1206 receive 1207 {'DOWN', Ref, process, Id, _} -> ok 1208 after ShutdownTimeout -> 1209 exit(Id, kill), 1210 receive 1211 {'DOWN', Ref, process, Id, _} -> ok 1212 end 1213 end 1214 end; 1215 (_) -> ok 1216 end, 1217 S#state.running), 1218 true = ets:delete(ac_tab), 1219 ok. 1220 1221-spec code_change(term(), state(), term()) -> {'ok', state()}. 1222 1223code_change(_OldVsn, State, _Extra) -> 1224 {ok, State}. 1225 1226 1227%%%----------------------------------------------------------------- 1228%%% Internal functions 1229%%%----------------------------------------------------------------- 1230cntrl(AppName, #state{control = Control}, Msg) -> 1231 case lists:keyfind(AppName, 1, Control) of 1232 {_AppName, Pid} -> 1233 Pid ! Msg, 1234 true; 1235 false -> 1236 false 1237 end. 1238 1239notify_cntrl_started(_AppName, {distributed, _Node}, _S, _Res) -> 1240 ok; 1241notify_cntrl_started(AppName, _Id, S, Res) -> 1242 cntrl(AppName, S, {ac_application_run, AppName, Res}). 1243 1244del_cntrl([{_, Pid}|T], Pid) -> 1245 del_cntrl(T, Pid); 1246del_cntrl([H|T], Pid) -> 1247 [H|del_cntrl(T, Pid)]; 1248del_cntrl([], _Pid) -> 1249 []. 1250 1251get_loaded(App) -> 1252 AppName = get_appl_name(App), 1253 case ets:lookup(ac_tab, {loaded, AppName}) of 1254 [{_Key, Appl}] -> {true, Appl}; 1255 _ -> false 1256 end. 1257 1258do_load_application(Application, S) -> 1259 case get_loaded(Application) of 1260 {true, _} -> 1261 throw({error, {already_loaded, Application}}); 1262 false -> 1263 case make_appl(Application) of 1264 {ok, Appl} -> load(S, Appl); 1265 Error -> Error 1266 end 1267 end. 1268 1269%% Recursively load the application and its included apps. 1270%load(S, {ApplData, ApplEnv, IncApps, Descr, Vsn, Apps}) -> 1271load(S, {ApplData, ApplEnv, IncApps, Descr, Id, Vsn, Apps}) -> 1272 Name = ApplData#appl_data.name, 1273 ConfEnv = get_env_i(Name, S), 1274 NewEnv = merge_app_env(ApplEnv, ConfEnv), 1275 CmdLineEnv = get_cmd_env(Name), 1276 NewEnv2 = merge_app_env(NewEnv, CmdLineEnv), 1277 add_env(Name, NewEnv2), 1278 Appl = #appl{name = Name, descr = Descr, id = Id, vsn = Vsn, 1279 appl_data = ApplData, inc_apps = IncApps, apps = Apps}, 1280 ets:insert(ac_tab, {{loaded, Name}, Appl}), 1281 NewS = 1282 foldl(fun(App, S1) -> 1283 case get_loaded(App) of 1284 {true, _} -> S1; 1285 false -> 1286 case do_load_application(App, S1) of 1287 {ok, S2} -> S2; 1288 Error -> throw(Error) 1289 end 1290 end 1291 end, S, IncApps), 1292 {ok, NewS}. 1293 1294unload(AppName, S) -> 1295 {ok, IncApps} = get_key(AppName, included_applications), 1296 del_env(AppName), 1297 ets:delete(ac_tab, {loaded, AppName}), 1298 foldl(fun(App, S1) -> 1299 case get_loaded(App) of 1300 false -> S1; 1301 {true, _} -> unload(App, S1) 1302 end 1303 end, S, IncApps). 1304 1305check_start_cond(AppName, RestartType, Started, Running) -> 1306 validRestartType(RestartType), 1307 case get_loaded(AppName) of 1308 {true, Appl} -> 1309 %% Check Running; not Started. An exited app is not running, 1310 %% but started. It must be possible to start an exited app! 1311 case lists:keymember(AppName, 1, Running) of 1312 true -> 1313 {error, {already_started, AppName}}; 1314 false -> 1315 foreach( 1316 fun(AppName2) -> 1317 case lists:keymember(AppName2, 1, Started) of 1318 true -> ok; 1319 false -> 1320 throw({error, {not_started, AppName2}}) 1321 end 1322 end, Appl#appl.apps), 1323 {ok, Appl} 1324 end; 1325 false -> 1326 {error, {not_loaded, AppName}} 1327 end. 1328 1329do_start(AppName, RT, Type, From, S) -> 1330 RestartType = case lists:keyfind(AppName, 1, S#state.started) of 1331 {_AppName2, OldRT} -> 1332 get_restart_type(RT, OldRT); 1333 false -> 1334 RT 1335 end, 1336 %% UW 990913: We check start_req instead of starting, because starting 1337 %% has already been checked. 1338 case lists:keymember(AppName, 1, S#state.start_req) of 1339 false -> 1340 {true, Appl} = get_loaded(AppName), 1341 Start_req = S#state.start_req, 1342 spawn_starter(undefined, Appl, S, Type), 1343 Starting = case lists:keymember(AppName, 1, S#state.starting) of 1344 false -> 1345 %% UW: don't know if this is necessary 1346 [{AppName, RestartType, Type, From} | 1347 S#state.starting]; 1348 true -> 1349 S#state.starting 1350 end, 1351 S#state{starting = Starting, 1352 start_req = [{AppName, From} | Start_req]}; 1353 true -> % otherwise we're already starting the app... 1354 S 1355 end. 1356 1357spawn_starter(From, Appl, S, Type) -> 1358 spawn_link(?MODULE, init_starter, [From, Appl, S, Type]). 1359 1360init_starter(_From, Appl, S, Type) -> 1361 process_flag(trap_exit, true), 1362 AppName = Appl#appl.name, 1363 gen_server:cast(?AC, {application_started, AppName, 1364 catch start_appl(Appl, S, Type)}). 1365 1366reply(undefined, _Reply) -> 1367 ok; 1368reply(From, Reply) -> gen_server:reply(From, Reply). 1369 1370start_appl(Appl, S, Type) -> 1371 ApplData = Appl#appl.appl_data, 1372 case ApplData#appl_data.mod of 1373 [] -> 1374 {ok, undefined}; 1375 _ -> 1376 %% Name = ApplData#appl_data.name, 1377 Running = S#state.running, 1378 foreach( 1379 fun(AppName) -> 1380 case lists:keymember(AppName, 1, Running) of 1381 true -> 1382 ok; 1383 false -> 1384 throw({info, {not_running, AppName}}) 1385 end 1386 end, Appl#appl.apps), 1387 case application_master:start_link(ApplData, Type) of 1388 {ok, _Pid} = Ok -> 1389 Ok; 1390 {error, _Reason} = Error -> 1391 throw(Error) 1392 end 1393 end. 1394 1395 1396%%----------------------------------------------------------------- 1397%% Stop application locally. 1398%%----------------------------------------------------------------- 1399stop_appl(AppName, Id, Type) when is_pid(Id) -> 1400 unlink(Id), 1401 application_master:stop(Id), 1402 info_exited(AppName, stopped, Type), 1403 ets:delete(ac_tab, {application_master, AppName}); 1404stop_appl(AppName, undefined, Type) -> 1405 %% Code-only application stopped 1406 info_exited(AppName, stopped, Type); 1407stop_appl(_AppName, _Id, _Type) -> 1408 %% Distributed application stopped 1409 ok. 1410 1411keysearchdelete(Key, Pos, List) -> 1412 ksd(Key, Pos, List, []). 1413 1414ksd(Key, Pos, [H | T], Rest) when element(Pos, H) =:= Key -> 1415 {value, H, Rest ++ T}; 1416ksd(Key, Pos, [H | T], Rest) -> 1417 ksd(Key, Pos, T, [H | Rest]); 1418ksd(_Key, _Pos, [], _Rest) -> 1419 false. 1420 1421keyreplaceadd(Key, Pos, List, New) -> 1422 %% Maintains the order! 1423 case lists:keymember(Key, Pos, List) of 1424 true -> keyreplace(Key, Pos, List, New); 1425 false -> [New | List] 1426 end. 1427 1428validRestartType(permanent) -> true; 1429validRestartType(temporary) -> true; 1430validRestartType(transient) -> true; 1431validRestartType(RestartType) -> 1432 throw({error, {invalid_restart_type, RestartType}}). 1433 1434nd({distributed, Node}) -> Node; 1435nd(_) -> node(). 1436 1437get_restart_type(undefined, OldRT) -> 1438 OldRT; 1439get_restart_type(RT, _OldRT) -> 1440 RT. 1441 1442get_appl_name(Name) when is_atom(Name) -> Name; 1443get_appl_name({application, Name, _}) when is_atom(Name) -> Name; 1444get_appl_name(Appl) -> throw({error, {bad_application, Appl}}). 1445 1446make_appl(Name) when is_atom(Name) -> 1447 FName = atom_to_list(Name) ++ ".app", 1448 case code:where_is_file(FName) of 1449 non_existing -> 1450 {error, {file:format_error(enoent), FName}}; 1451 FullName -> 1452 case prim_consult(FullName) of 1453 {ok, [Application]} -> 1454 {ok, make_appl_i(Application)}; 1455 {error, Reason} -> 1456 {error, {file:format_error(Reason), FName}}; 1457 error -> 1458 {error, "bad encoding"} 1459 end 1460 end; 1461make_appl(Application) -> 1462 {ok, make_appl_i(Application)}. 1463 1464prim_consult(FullName) -> 1465 case erl_prim_loader:get_file(FullName) of 1466 {ok, Bin, _} -> 1467 case file_binary_to_list(Bin) of 1468 {ok, String} -> 1469 case erl_scan:string(String) of 1470 {ok, Tokens, _EndLine} -> 1471 prim_parse(Tokens, []); 1472 {error, Reason, _EndLine} -> 1473 {error, Reason} 1474 end; 1475 error -> 1476 error 1477 end; 1478 error -> 1479 {error, enoent} 1480 end. 1481 1482prim_parse(Tokens, Acc) -> 1483 case lists:splitwith(fun(T) -> element(1,T) =/= dot end, Tokens) of 1484 {[], []} -> 1485 {ok, lists:reverse(Acc)}; 1486 {Tokens2, [{dot,_} = Dot | Rest]} -> 1487 case erl_parse:parse_term(Tokens2 ++ [Dot]) of 1488 {ok, Term} -> 1489 prim_parse(Rest, [Term | Acc]); 1490 {error, _R} = Error -> 1491 Error 1492 end; 1493 {Tokens2, []} -> 1494 case erl_parse:parse_term(Tokens2) of 1495 {ok, Term} -> 1496 {ok, lists:reverse([Term | Acc])}; 1497 {error, _R} = Error -> 1498 Error 1499 end 1500 end. 1501 1502make_appl_i({application, Name, Opts}) when is_atom(Name), is_list(Opts) -> 1503 Descr = get_opt(description, Opts, ""), 1504 Id = get_opt(id, Opts, ""), 1505 Vsn = get_opt(vsn, Opts, ""), 1506 Mods = get_opt(modules, Opts, []), 1507 Regs = get_opt(registered, Opts, []), 1508 Apps = get_opt(applications, Opts, []), 1509 Mod = 1510 case get_opt(mod, Opts, []) of 1511 {M,_A}=MA when is_atom(M) -> MA; 1512 [] -> []; 1513 Other -> throw({error, {badstartspec, Other}}) 1514 end, 1515 Phases = get_opt(start_phases, Opts, undefined), 1516 Env = get_opt(env, Opts, []), 1517 MaxP = get_opt(maxP, Opts, infinity), 1518 MaxT = get_opt(maxT, Opts, infinity), 1519 IncApps = get_opt(included_applications, Opts, []), 1520 {#appl_data{name = Name, regs = Regs, mod = Mod, phases = Phases, 1521 mods = Mods, inc_apps = IncApps, maxP = MaxP, maxT = MaxT}, 1522 Env, IncApps, Descr, Id, Vsn, Apps}; 1523make_appl_i({application, Name, Opts}) when is_list(Opts) -> 1524 throw({error,{invalid_name,Name}}); 1525make_appl_i({application, _Name, Opts}) -> 1526 throw({error,{invalid_options, Opts}}); 1527make_appl_i(Appl) -> throw({error, {bad_application, Appl}}). 1528 1529 1530%%----------------------------------------------------------------- 1531%% Merge current applications with changes. 1532%%----------------------------------------------------------------- 1533 1534%% do_change_apps(Applications, Config, OldAppls) -> NewAppls 1535%% Applications = [{application, AppName, [{Key,Value}]}] 1536%% Config = [{AppName,[{Par,Value}]} | File] 1537%% OldAppls = NewAppls = [#appl{}] 1538do_change_apps(Applications, Config, OldAppls) -> 1539 1540 %% OTP-4867 1541 %% Config = contents of sys.config file 1542 %% May now contain names of other .config files as well as 1543 %% configuration parameters. 1544 %% Therefore read and merge contents. 1545 {ok, SysConfig, Errors} = check_conf_sys(Config), 1546 1547 %% Report errors, but do not terminate 1548 %% (backwards compatible behaviour) 1549 lists:foreach(fun({error, {SysFName, Line, Str}}) -> 1550 ?LOG_ERROR("~tp: ~w: ~ts~n",[SysFName, Line, Str], 1551 #{error_logger=>#{tag=>error}}) 1552 end, 1553 Errors), 1554 1555 {map(fun(Appl) -> 1556 AppName = Appl#appl.name, 1557 case is_loaded_app(AppName, Applications) of 1558 {true, Application} -> 1559 do_change_appl(make_appl(Application), 1560 Appl, SysConfig); 1561 1562 %% ignored removed apps - handled elsewhere 1563 false -> 1564 Appl 1565 end 1566 end, OldAppls), 1567 SysConfig}. 1568 1569is_loaded_app(AppName, [{application, AppName, App} | _]) -> 1570 {true, {application, AppName, App}}; 1571is_loaded_app(AppName, [_ | T]) -> is_loaded_app(AppName, T); 1572is_loaded_app(_AppName, []) -> false. 1573 1574do_change_appl({ok, {ApplData, Env, IncApps, Descr, Id, Vsn, Apps}}, 1575 OldAppl, Config) -> 1576 AppName = OldAppl#appl.name, 1577 1578 %% Merge application env with env from sys.config, if any 1579 ConfEnv = get_opt(AppName, Config, []), 1580 NewEnv1 = merge_app_env(Env, ConfEnv), 1581 1582 %% Merge application env with command line arguments, if any 1583 CmdLineEnv = get_cmd_env(AppName), 1584 NewEnv2 = merge_app_env(NewEnv1, CmdLineEnv), 1585 1586 %% Update ets table with new application env 1587 del_env(AppName), 1588 add_env(AppName, NewEnv2), 1589 1590 OldAppl#appl{appl_data=ApplData, 1591 descr=Descr, 1592 id=Id, 1593 vsn=Vsn, 1594 inc_apps=IncApps, 1595 apps=Apps}; 1596do_change_appl({error, _R} = Error, _Appl, _ConfData) -> 1597 throw(Error). 1598 1599get_opt(Key, List, Default) -> 1600 case lists:keyfind(Key, 1, List) of 1601 {_Key, Val} -> Val; 1602 _ -> Default 1603 end. 1604 1605get_cmd_env(Name) -> 1606 case init:get_argument(Name) of 1607 {ok, Args} -> 1608 foldl(fun(List, Res) -> conv(List) ++ Res end, [], Args); 1609 _ -> [] 1610 end. 1611 1612conv([Key, Val | T]) -> 1613 [{make_term(Key), make_term(Val)} | conv(T)]; 1614conv(_) -> []. 1615 1616make_term(Str) -> 1617 case erl_scan:string(Str) of 1618 {ok, Tokens, _} -> 1619 case erl_parse:parse_term(Tokens ++ [{dot, erl_anno:new(1)}]) of 1620 {ok, Term} -> 1621 Term; 1622 {error, {_,M,Reason}} -> 1623 handle_make_term_error(M, Reason, Str) 1624 end; 1625 {error, {_,M,Reason}, _} -> 1626 handle_make_term_error(M, Reason, Str) 1627 end. 1628 1629handle_make_term_error(Mod, Reason, Str) -> 1630 ?LOG_ERROR("application_controller: ~ts: ~ts~n", 1631 [Mod:format_error(Reason), Str], 1632 #{error_logger=>#{tag=>error}}), 1633 throw({error, {bad_environment_value, Str}}). 1634 1635get_env_i(Name, #state{conf_data = ConfData}) when is_list(ConfData) -> 1636 case lists:keyfind(Name, 1, ConfData) of 1637 {_Name, Env} -> Env; 1638 _ -> [] 1639 end; 1640get_env_i(_Name, _) -> []. 1641 1642%% Merges envs for all apps. Env2 overrides Env1 1643merge_env(Env1, Env2) -> 1644 merge_env(Env1, Env2, []). 1645 1646merge_env([{App, AppEnv1} | T], Env2, Res) -> 1647 case get_env_key(App, Env2) of 1648 {value, AppEnv2, RestEnv2} -> 1649 NewAppEnv = merge_app_env(AppEnv1, AppEnv2), 1650 merge_env(T, RestEnv2, [{App, NewAppEnv} | Res]); 1651 _ -> 1652 merge_env(T, Env2, [{App, AppEnv1} | Res]) 1653 end; 1654merge_env([], Env2, Res) -> 1655 Env2 ++ Res. 1656 1657%% Changes the environment for the given application 1658%% If there is no application, an empty one is created 1659change_app_env(Env, App, Fun) -> 1660 case get_env_key(App, Env) of 1661 {value, AppEnv, RestEnv} -> 1662 [{App, Fun(AppEnv)} | RestEnv]; 1663 _ -> 1664 [{App, Fun([])} | Env] 1665 end. 1666 1667%% Merges envs for an application. Env2 overrides Env1 1668merge_app_env(Env1, Env2) -> 1669 merge_app_env(Env1, Env2, []). 1670 1671merge_app_env([{Key, Val} | T], Env2, Res) -> 1672 case get_env_key(Key, Env2) of 1673 {value, NewVal, RestEnv} -> 1674 merge_app_env(T, RestEnv, [{Key, NewVal}|Res]); 1675 _ -> 1676 merge_app_env(T, Env2, [{Key, Val} | Res]) 1677 end; 1678merge_app_env([], Env2, Res) -> 1679 Env2 ++ Res. 1680 1681get_env_key(Key, Env) -> get_env_key(Env, Key, []). 1682get_env_key([{Key, Val} | T], Key, Res) -> 1683 {value, Val, T ++ Res}; 1684get_env_key([H | T], Key, Res) -> 1685 get_env_key(T, Key, [H | Res]); 1686get_env_key([], _Key, Res) -> Res. 1687 1688add_env(Name, Env) -> 1689 foreach(fun({Key, Value}) -> 1690 ets:insert(ac_tab, {{env, Name, Key}, Value}) 1691 end, 1692 Env). 1693 1694del_env(Name) -> 1695 ets:match_delete(ac_tab, {{env, Name, '_'}, '_'}). 1696 1697check_user() -> 1698 case whereis(user) of 1699 User when is_pid(User) -> group_leader(User, self()); 1700 _ -> ok 1701 end. 1702 1703 1704%%----------------------------------------------------------------- 1705%% Prepare for a release upgrade by reading all the evironment variables. 1706%%----------------------------------------------------------------- 1707do_prep_config_change(Apps) -> 1708 do_prep_config_change(Apps, []). 1709 1710do_prep_config_change([], EnvBefore) -> 1711 EnvBefore; 1712do_prep_config_change([{App, _Id} | Apps], EnvBefore) -> 1713 Env = application:get_all_env(App), 1714 do_prep_config_change(Apps, [{App, Env} | EnvBefore]). 1715 1716 1717 1718%%----------------------------------------------------------------- 1719%% Inform all running applications about the changed configuration. 1720%%----------------------------------------------------------------- 1721do_config_change(Apps, EnvBefore) -> 1722 do_config_change(Apps, EnvBefore, []). 1723 1724do_config_change([], _EnvBefore, []) -> 1725 ok; 1726do_config_change([], _EnvBefore, Errors) -> 1727 {error, Errors}; 1728do_config_change([{App, _Id} | Apps], EnvBefore, Errors) -> 1729 AppEnvNow = lists:sort(application:get_all_env(App)), 1730 AppEnvBefore = case lists:keyfind(App, 1, EnvBefore) of 1731 false -> 1732 []; 1733 {App, AppEnvBeforeT} -> 1734 lists:sort(AppEnvBeforeT) 1735 end, 1736 Res = 1737 case AppEnvNow of 1738 AppEnvBefore -> 1739 ok; 1740 _ -> 1741 case do_config_diff(AppEnvNow, AppEnvBefore) of 1742 {[], [], []} -> 1743 ok; 1744 {Changed, New, Removed} -> 1745 case application:get_key(App, mod) of 1746 {ok, {Mod, _Para}} -> 1747 case catch Mod:config_change(Changed, New, 1748 Removed) of 1749 ok -> 1750 ok; 1751 %% It is not considered as an error 1752 %% if the cb-function is not defined 1753 {'EXIT', {undef, _}} -> 1754 ok; 1755 {error, _} = Error -> 1756 Error; 1757 Else -> 1758 {error, Else} 1759 end; 1760 {ok, []} -> 1761 {error, {module_not_defined, App}}; 1762 undefined -> 1763 {error, {application_not_found, App}} 1764 end 1765 end 1766 end, 1767 1768 case Res of 1769 ok -> 1770 do_config_change(Apps, EnvBefore, Errors); 1771 {error, NewError} -> 1772 do_config_change(Apps, EnvBefore,[NewError | Errors]) 1773 end. 1774 1775 1776%%----------------------------------------------------------------- 1777%% Check if the configuration is changed in anyway. 1778%%----------------------------------------------------------------- 1779do_config_diff(AppEnvNow, AppEnvBefore) -> 1780 do_config_diff(AppEnvNow, AppEnvBefore, {[], []}). 1781 1782do_config_diff([], AppEnvBefore, {Changed, New}) -> 1783 Removed = lists:foldl(fun({Env, _Value}, Acc) -> [Env | Acc] end, [], AppEnvBefore), 1784 {Changed, New, Removed}; 1785do_config_diff(AppEnvNow, [], {Changed, New}) -> 1786 {Changed, AppEnvNow++New, []}; 1787do_config_diff([{Env, Value} | AppEnvNow], AppEnvBefore, {Changed, New}) -> 1788 case lists:keyfind(Env, 1, AppEnvBefore) of 1789 {Env, Value} -> 1790 do_config_diff(AppEnvNow, lists:keydelete(Env,1,AppEnvBefore), {Changed, New}); 1791 {Env, _OtherValue} -> 1792 do_config_diff(AppEnvNow, lists:keydelete(Env,1,AppEnvBefore), 1793 {[{Env, Value} | Changed], New}); 1794 false -> 1795 do_config_diff(AppEnvNow, AppEnvBefore, {Changed, [{Env, Value}|New]}) 1796 end. 1797 1798 1799%%----------------------------------------------------------------- 1800%% Read the .config files. 1801%%----------------------------------------------------------------- 1802check_conf() -> 1803 case init:get_argument(config) of 1804 {ok, Files} -> 1805 {ok, lists:foldl( 1806 fun([File], Env) -> 1807 BFName = filename:basename(File,".config"), 1808 FName = filename:join(filename:dirname(File), 1809 BFName ++ ".config"), 1810 case load_file(FName) of 1811 {ok, NewEnv} -> 1812 %% OTP-4867 1813 %% sys.config may now contain names of 1814 %% other .config files as well as 1815 %% configuration parameters. 1816 %% Therefore read and merge contents. 1817 if 1818 BFName =:= "sys" -> 1819 DName = filename:dirname(FName), 1820 {ok, SysEnv, Errors} = 1821 check_conf_sys(NewEnv, [], [], DName), 1822 1823 %% Report first error, if any, and 1824 %% terminate 1825 %% (backwards compatible behaviour) 1826 case Errors of 1827 [] -> 1828 merge_env(Env, SysEnv); 1829 [{error, {SysFName, Line, Str}}|_] -> 1830 throw({error, {SysFName, Line, Str}}) 1831 end; 1832 true -> 1833 merge_env(Env, NewEnv) 1834 end; 1835 {error, {Line, _Mod, Str}} -> 1836 throw({error, {FName, Line, Str}}) 1837 end 1838 end, [], Files)}; 1839 _ -> {ok, []} 1840 end. 1841 1842check_conf_sys(Env) -> 1843 check_conf_sys(Env, [], [], []). 1844 1845check_conf_sys([File|T], SysEnv, Errors, DName) when is_list(File),is_list(DName) -> 1846 BFName = filename:basename(File, ".config"), 1847 FName = filename:join(filename:dirname(File), BFName ++ ".config"), 1848 LName = case filename:pathtype(FName) of 1849 relative when (DName =/= []) -> 1850 % Check if relative to sys.config dir otherwise use legacy mode, 1851 % i.e relative to cwd. 1852 RName = filename:join(DName, FName), 1853 case erl_prim_loader:read_file_info(RName) of 1854 {ok, _} -> RName ; 1855 error -> FName 1856 end; 1857 _ -> FName 1858 end, 1859 case load_file(LName) of 1860 {ok, NewEnv} -> 1861 check_conf_sys(T, merge_env(SysEnv, NewEnv), Errors, DName); 1862 {error, {Line, _Mod, Str}} -> 1863 check_conf_sys(T, SysEnv, [{error, {LName, Line, Str}}|Errors], DName) 1864 end; 1865check_conf_sys([Tuple|T], SysEnv, Errors, DName) -> 1866 check_conf_sys(T, merge_env(SysEnv, [Tuple]), Errors, DName); 1867check_conf_sys([], SysEnv, Errors, _) -> 1868 {ok, SysEnv, lists:reverse(Errors)}. 1869 1870load_file(File) -> 1871 %% We can't use file:consult/1 here. Too bad. 1872 case erl_prim_loader:get_file(File) of 1873 {ok, Bin, _FileName} -> 1874 %% Make sure that there is some whitespace at the end of the string 1875 %% (so that reading a file with no NL following the "." will work). 1876 case file_binary_to_list(Bin) of 1877 {ok, String} -> 1878 scan_file(String ++ " "); 1879 error -> 1880 {error, {none, scan_file, "bad encoding"}} 1881 end; 1882 error -> 1883 {error, {none, open_file, "configuration file not found"}} 1884 end. 1885 1886scan_file(Str) -> 1887 case erl_scan:tokens([], Str, 1) of 1888 {done, {ok, Tokens, _}, Left} -> 1889 case erl_parse:parse_term(Tokens) of 1890 {ok,L}=Res when is_list(L) -> 1891 case only_ws(Left) of 1892 true -> 1893 Res; 1894 false -> 1895 %% There was trailing garbage found after the list. 1896 config_error() 1897 end; 1898 {ok,_} -> 1899 %% Parsing succeeded but the result is not a list. 1900 config_error(); 1901 Error -> 1902 Error 1903 end; 1904 {done, Result, _} -> 1905 {error, {none, parse_file, tuple_to_list(Result)}}; 1906 {more, _} -> 1907 {error, {none, load_file, "no ending <dot> found"}} 1908 end. 1909 1910only_ws([C|Cs]) when C =< $\s -> only_ws(Cs); 1911only_ws([$%|Cs]) -> only_ws(strip_comment(Cs)); % handle comment 1912only_ws([_|_]) -> false; 1913only_ws([]) -> true. 1914 1915strip_comment([$\n|Cs]) -> Cs; 1916strip_comment([_|Cs]) -> strip_comment(Cs); 1917strip_comment([]) -> []. 1918 1919config_error() -> 1920 {error, 1921 {none, load_file, 1922 "configuration file must contain ONE list ended by <dot>"}}. 1923 1924%%----------------------------------------------------------------- 1925%% Info messages sent to logger 1926%%----------------------------------------------------------------- 1927info_started(Name, Node) -> 1928 ?LOG_INFO(#{label=>{application_controller,progress}, 1929 report=>[{application, Name}, 1930 {started_at, Node}]}, 1931 #{domain=>[otp,sasl], 1932 report_cb=>fun logger:format_otp_report/1, 1933 logger_formatter=>#{title=>"PROGRESS REPORT"}, 1934 error_logger=>#{tag=>info_report,type=>progress}}). 1935 1936info_exited(Name, Reason, Type) -> 1937 ?LOG_NOTICE(#{label=>{application_controller,exit}, 1938 report=>[{application, Name}, 1939 {exited, Reason}, 1940 {type, Type}]}, 1941 #{domain=>[otp], 1942 report_cb=>fun logger:format_otp_report/1, 1943 error_logger=>#{tag=>info_report,type=>std_info}}). 1944 1945%%----------------------------------------------------------------- 1946%% Reply to all processes waiting this application to be started. 1947%%----------------------------------------------------------------- 1948reply_to_requester(AppName, Start_req, Res) -> 1949 R = case Res of 1950 {ok, _Id} -> 1951 ok; 1952 {info, Reason} -> 1953 {error, Reason}; 1954 Error -> 1955 Error 1956 end, 1957 1958 lists:foldl(fun(Sp, AccIn) -> 1959 case Sp of 1960 {AppName, From} -> 1961 reply(From, R), 1962 AccIn; 1963 _ -> 1964 [Sp | AccIn] 1965 end 1966 end, 1967 [], 1968 Start_req). 1969 1970 1971%%----------------------------------------------------------------- 1972%% Update the environment variable permission for an application. 1973%%----------------------------------------------------------------- 1974update_permissions(AppName, Bool) -> 1975 T = {env, kernel, permissions}, 1976 case ets:lookup(ac_tab, T) of 1977 [] -> 1978 ets:insert(ac_tab, {T, [{AppName, Bool}]}); 1979 [{_, Perm}] -> 1980 Perm2 = lists:keydelete(AppName, 1, Perm), 1981 ets:insert(ac_tab, {T, [{AppName, Bool}|Perm2]}) 1982 end. 1983 1984%%----------------------------------------------------------------- 1985%% These functions are only to be used from testsuites. 1986%%----------------------------------------------------------------- 1987test_change_apps(Apps, Conf) -> 1988 Res = test_make_apps(Apps, []), 1989 test_do_change_appl(Apps, Conf, Res). 1990 1991test_do_change_appl([], _, _) -> 1992 ok; 1993test_do_change_appl([A|Apps], [], [R|Res]) -> 1994 _ = do_change_appl(R, #appl{name = A}, []), 1995 test_do_change_appl(Apps, [], Res); 1996test_do_change_appl([A|Apps], [C|Conf], [R|Res]) -> 1997 _ = do_change_appl(R, #appl{name = A}, C), 1998 test_do_change_appl(Apps, Conf, Res). 1999 2000test_make_apps([], Res) -> 2001 lists:reverse(Res); 2002test_make_apps([A|Apps], Res) -> 2003 test_make_apps(Apps, [make_appl(A) | Res]). 2004 2005file_binary_to_list(Bin) -> 2006 Enc = case epp:read_encoding_from_binary(Bin) of 2007 none -> epp:default_encoding(); 2008 Encoding -> Encoding 2009 end, 2010 case catch unicode:characters_to_list(Bin, Enc) of 2011 String when is_list(String) -> 2012 {ok, String}; 2013 _ -> 2014 error 2015 end. 2016 2017%%----------------------------------------------------------------- 2018%% String conversion 2019%% Exit reason needs to be a printable string 2020%% (and of length <200, but init now does the chopping). 2021%%----------------------------------------------------------------- 2022 2023-spec to_string(term()) -> string(). 2024 2025to_string(Term) -> 2026 case io_lib:printable_list(Term) of 2027 true -> 2028 Term; 2029 false -> 2030 lists:flatten(io_lib:format("~0p", [Term])) 2031 end. 2032