%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 1996-2025. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(application_controller).
-moduledoc false.

-compile(nowarn_deprecated_catch).

%% External exports
-export([start/1, 
	 load_application/1, unload_application/1, 
	 start_application/2, start_application_request/2,
	 start_boot_application/2, stop_application/1,
	 control_application/1, is_running/1,
	 change_application_data/2, prep_config_change/0, config_change/1,
	 which_applications/0, which_applications/1,
	 loaded_applications/0, info/0, set_env/2,
	 get_pid_env/2, get_env/2, get_env/3, get_pid_all_env/1, get_all_env/1,
	 get_pid_key/2, get_key/2, get_pid_all_key/1, get_all_key/1,
	 get_master/1, get_application/1, get_application_module/1,
	 start_type/1, permit_application/2, do_config_diff/2,
	 set_env/3, set_env/4, unset_env/2, unset_env/3]).

%% Internal exports
-export([handle_call/3, handle_cast/2, handle_info/2, terminate/2,
	 code_change/3, get_loaded/1]).

%% logger callback
-export([format_log/1, format_log/2]).

%% Test exports, only to be used from the test suites
-export([test_change_apps/2]).

-import(lists, [zf/2, map/2, foreach/2, foldl/3,
		keyfind/3, keydelete/3, keyreplace/4]).

-include("application_master.hrl").
-include("logger.hrl").

-define(AC, ?MODULE). % Name of process

%%%-----------------------------------------------------------------
%%% The application_controller controls local applications only.  A
%%% local application can be loaded/started/stopped/unloaded and
%%% changed.  The control of distributed applications is taken care of
%%% by another process (default is dist_ac).
%%%
%%% When an application has been started (by a call to application:start)
%%% it can be running or not running (on this node).  For example,
%%% a distributed application must be started on all nodes, but
%%% may be running on one node at the time.
%%%
%%% The external API to this module is in the module 'application'.
%%% 
%%% The process that controls distributed applications (called dist
%%% ac).  calls application_controller:control_application(Name) to
%%% take responsibility for an application.  The interface between AC
%%% and the dist_ac process is message-based:
%%%
%%% AC                                        DIST AC
%%% ==                                        =======
%%%     --> {ac_load_application_req, Name}
%%%     <-- {ac_load_application_reply, Name, LoadReply}
%%%     --> {ac_start_application_req, Name}       (*)
%%%     <-- {ac_start_application_reply, Name, StartReply}
%%%     --> {ac_application_run, Name, Res}
%%%     --> {ac_application_not_run, Name, Res}
%%%     --> {ac_application_stopped, Name}
%%%     --> {ac_application_unloaded, Name}
%%%     <-- {ac_change_application_req, Name, Req} (**)
%%%
%%% Where LoadReply =
%%%   ok              - App is loaded
%%%   {error, R}      - An error occurred
%%% And StartReply =
%%%   start_it        - DIST AC decided that AC should start the app
%%%   {started, Node} - The app is started distributed at Node
%%%   not_started     - The app should not be running at this time
%%%   {takeover, Node}- The app should takeover from Node
%%%   {error, R}      - an error occurred
%%% And Req =
%%%   start_it        - DIST AC wants AC to start the app locally
%%%   stop_it         - AC should stop the app.
%%%   {takeover, Node, RestartType}
%%%                   - AC should start the app as a takeover
%%%   {failover, Node, RestartType}
%%%                   - AC should start the app as a failover
%%%   {started, Node} - The app is started at Node
%%%                     NOTE: The app must have been started at this node
%%%                     before this request is sent!
%%% And Res =
%%%   ok              - Application is started locally
%%%   {error, R}      - Start of application failed
%%%
%%% (*)
%%%  The call to application:start() doesn't return until the
%%%  ac_start_application_reply has been received by AC.  AC
%%%  itself is not blocked however.
%%% (**)
%%%  DIST AC gets ACK to its ac_change_application_req, but not as a
%%%  separate messgage.  Instead the normal messages are used as:
%%%   start_it   generates an ac_application_run
%%%   stop_it    generates an ac_application_not_run
%%%   takeover   generates an ac_application_run
%%%   started    doesn't generate anything
%%%
%%% There is a distinction between application:stop and stop_it
%%% from a dist ac process.  The first one stops the application,
%%% and resets the internal structures as they were before start was
%%% called.  stop_it stops the application, but just marks it as
%%% not being running.
%%%
%%% When a dist ac process has taken control of an application, no
%%% other process can take the control.
%%%-----------------------------------------------------------------

%%-----------------------------------------------------------------
%% Naming conventions:
%%   App = appl_descr()
%%   Appl = #appl
%%   AppName = atom()
%%   Application = App | AppName
%%-----------------------------------------------------------------

-type appname() :: atom().

-record(state, {loading = [], starting = [], start_p_false = [], running = [],
		control = [], started = [], start_req = [], conf_data}).
-type state() :: #state{}.

%%-----------------------------------------------------------------
%% loading     = [{AppName, From}] - Load not yet finished
%% starting    = [{AppName, RestartType, Type, From}] - Start not
%%                 yet finished
%% start_p_false = [{AppName, RestartType, Type, From}] - Start not
%%                 executed because permit == false
%% running     = [{AppName, Pid}] - running locally (Pid == application_master)
%%               [{AppName, {distributed, Node}}] - running on Node
%% control     = [{AppName, Controller}]
%% started     = [{AppName, RestartType}] - Names of all apps that
%%                 have been started (but may not run because
%%                 permission = false)
%% conf_data   = [{AppName, Env}]
%% start_req   = [{AppName, From}] - list of all start requests
%% Id          = AMPid | undefined | {distributed, Node}
%% Env         = [{Key, Value}]
%%-----------------------------------------------------------------

-record(appl, {name, appl_data, descr, id, vsn, inc_apps, opt_apps, apps}).

%%-----------------------------------------------------------------
%% Func: start/1
%% Args: KernelApp = appl_descr()
%%       appl_descr() = [{application, Name, [appl_opt()]}]
%%       appl_opt() = {description, string()}           |
%%                    {vsn, string()}                   |
%%                    {id, string()},                   |
%%                    {modules, [Module]}  |
%%                    {registered, [atom()]}            |
%%                    {applications, [atom()]}          |
%%                    {included_applications, [atom()]} |
%%                    {env, [{atom(), term()}]}         |
%%                    {start_phases, [{atom(), term()}]}|
%%                    {maxT, integer()|infinity}        |
%%                    {maxP, integer()|infinity}        |
%%                    {mod, {Module, term()}}
%%         Module = atom()
%% Purpose: Starts the application_controller.  This process starts all
%%          application masters for the applications.
%%          The kernel application is the only application that is
%%          treated specially.  The reason for this is that the kernel
%%          starts user.  This process is special because it should
%%          be group_leader for this process.
%% Pre: All modules are loaded, or will be loaded on demand.
%% Returns: {ok, Pid} | ReasonStr
%%-----------------------------------------------------------------
start(KernelApp) ->
    %% OTP-5811 Don't start as a gen_server to prevent crash report
    %% when (if) the process terminates
    Init = self(),
    AC = spawn_link(fun() -> init(Init, KernelApp) end),
    receive
	{ack, AC, ok} ->
	    {ok, AC};
	{ack, AC, {error, Reason}} ->
	    to_string(Reason); % init doesn't want error tuple, only a reason
	{'EXIT', _Pid, Reason} ->
	    to_string(Reason)
    end.
	
%%-----------------------------------------------------------------
%% Func: load_application/1
%% Args: Application = appl_descr() | atom()
%% Purpose: Loads an application.  Currently just inserts the
%%          application's env.
%% Returns: ok | {error, Reason}
%%-----------------------------------------------------------------
load_application(Application) ->
    call({load_application, Application}, infinity).

unload_application(AppName) ->
    call({unload_application, AppName}, infinity).

%%-----------------------------------------------------------------
%% Func: start_application/2
%% Args: Application = atom()
%%       RestartType = permanent | transient | temporary
%% Purpose: Starts a new application.
%%          The RestartType specifies what should happen if the
%%          application dies:
%%          If it is permanent, all other applications are terminated,
%%            and the application_controller dies.
%%          If it is transient, and the application dies normally,
%%            this is reported and no other applications are terminated.
%%            If the application dies abnormally, all other applications
%%            are terminated, and the application_controller dies.
%%          If it is temporary and the application dies this is reported
%%            and no other applications are terminated.  In this way,
%%            an application can run in test mode, without disturbing
%%            the other applications.
%%          The caller of this function is suspended until the application
%%          is started, either locally or distributed.
%% Returns: ok | {error, Reason}
%%-----------------------------------------------------------------
start_application(AppName, RestartType) ->
    call({start_application, AppName, RestartType}, infinity).

start_application_request(AppName, RestartType) ->
    gen_server:send_request(?AC, {start_application, AppName, RestartType}).

%%-----------------------------------------------------------------
%% Func: is_running/1
%% Args: Application = atom()
%% Purpose: Checks if an application is running.
%%          This is used by application to avoid traversing an
%%          application's child in case it is already running.
%% Returns: boolean
%%-----------------------------------------------------------------
is_running(AppName) when is_atom(AppName) ->
    call({is_running, AppName}, infinity).

%%-----------------------------------------------------------------
%% Func: start_boot_application/2
%% The same as start_application/2 expect that this function is
%% called from the boot script file. It must not be used by the operator.
%% This function will cause a node crash if a permanent application 
%% fails to boot start
%%-----------------------------------------------------------------
start_boot_application(Application, RestartType) ->
    case {application:load(Application), RestartType} of
	{ok, _} ->
	    AppName = get_appl_name(Application),
	    call({start_application, AppName, RestartType}, infinity);
	{{error, {already_loaded, AppName}}, _} ->
	    call({start_application, AppName, RestartType}, infinity);
	{{error,{bad_environment_value,Env}}, permanent} ->
	    Txt = io_lib:format("Bad environment variable: ~tp  Application: ~p",
				[Env, Application]),
	    exit({error, list_to_atom(lists:flatten(Txt))});
	{Error, _} ->
	    Error
    end.

stop_application(AppName) ->
    call({stop_application, AppName}, infinity).

%%-----------------------------------------------------------------
%% Returns: [{Name, Descr, Vsn}]
%%-----------------------------------------------------------------
which_applications() ->
    call(which_applications).
which_applications(Timeout) ->
    call(which_applications, Timeout).

loaded_applications() ->
    ets:select(ac_tab,
               [{
                  {{loaded, '$1'}, #appl{descr = '$2', vsn = '$3', _ = '_'}},
                  [],
                  [{{'$1', '$2', '$3'}}]
                }]).

%% Returns some debug info
info() ->
    call(info).

control_application(AppName) ->
    call({control_application, AppName}, infinity).

%%-----------------------------------------------------------------
%% Func: change_application_data/2
%% Args: Applications = [appl_descr()]
%%       Config = [{AppName, [{Par,Val}]}]
%% Purpose: Change all applications and their parameters on this node.
%%          This function should be used from a release handler, at
%%          the same time as the .app or start.boot file is
%%          introduced.  Note that during some time the ACs may have
%%          different view of e.g. the distributed applications.
%%          This is solved by syncing the release installation.
%%          However, strange things may happen if a node crashes
%%          and two other nodes have different opinons about who's
%%          gonna start the applications.  The release handler must
%%          shutdown each involved node in this case.
%%          Note that this function is used to change existing apps,
%%          adding new/deleting old isn't handled by this function.
%%          Changes an application's vsn, descr and env.
%% Returns: ok | {error, Reason}
%%          If an error occurred, the situation may be inconsistent,
%%          so the release handler must restart the node.  E.g. if
%%          some applicatation may have got new config data.
%%-----------------------------------------------------------------
change_application_data(Applications, Config) ->
    call({change_application_data, Applications, Config},infinity).

prep_config_change() ->
    call(prep_config_change, infinity).


config_change(EnvPrev) ->
    call({config_change, EnvPrev},infinity).


get_pid_env(Master, Key) ->
    case ets:match(ac_tab, {{application_master, '$1'}, Master}) of
	[[AppName]] -> get_env(AppName, Key);
	_ -> undefined
    end.

get_env(AppName, Key) ->
    NotFound = make_ref(),
    case ets:lookup_element(ac_tab, {env, AppName, Key}, 2, NotFound) of
        NotFound -> undefined;
        Val -> {ok, Val}
    end.

get_env(AppName, Key, Default) ->
    ets:lookup_element(ac_tab, {env, AppName, Key}, 2, Default).

get_pid_all_env(Master) ->
    case ets:match(ac_tab, {{application_master, '$1'}, Master}) of
	[[AppName]] -> get_all_env(AppName);
	_ -> []
    end.

get_all_env(AppName) ->
    map(fun([Key, Val]) -> {Key, Val} end,
	ets:match(ac_tab, {{env, AppName, '$1'}, '$2'})).

get_pid_key(Master, Key) ->
    case ets:match(ac_tab, {{application_master, '$1'}, Master}) of
	[[AppName]] -> get_key(AppName, Key);
	_ -> undefined
    end.

get_key(AppName, description) ->
    get_key_direct(AppName, #appl{descr = '$1', _ = '_'});
get_key(AppName, id) ->
    get_key_direct(AppName, #appl{id = '$1', _ = '_'});
get_key(AppName, vsn) ->
    get_key_direct(AppName, #appl{vsn = '$1', _ = '_'});
get_key(AppName, modules) ->
    get_key_data(AppName, #appl_data{mods = '$1', _ = '_'});
get_key(AppName, maxP) ->
    get_key_data(AppName, #appl_data{maxP = '$1', _ = '_'});
get_key(AppName, maxT) ->
    get_key_data(AppName, #appl_data{maxT = '$1', _ = '_'});
get_key(AppName, registered) ->
    get_key_data(AppName, #appl_data{regs = '$1', _ = '_'});
get_key(AppName, included_applications) ->
    get_key_direct(AppName, #appl{inc_apps = '$1', _ = '_'});
get_key(AppName, optional_applications) ->
    get_key_direct(AppName, #appl{opt_apps = '$1', _ = '_'});
get_key(AppName, applications) ->
    get_key_direct(AppName, #appl{apps = '$1', _ = '_'});
get_key(AppName, env) ->
    case ets:member(ac_tab, {loaded, AppName}) of
        true -> {ok, get_all_env(AppName)};
        false -> undefined
    end;
get_key(AppName, mod) ->
    get_key_data(AppName, #appl_data{mod = '$1', _ = '_'});
get_key(AppName, start_phases) ->
    get_key_data(AppName, #appl_data{phases = '$1', _ = '_'});
get_key(_, _) ->
    undefined.

get_key_direct(AppName, Match) ->
    case ets:match(ac_tab, {{loaded, AppName}, Match}) of
        [[Value]] -> {ok, Value};
        [] -> undefined
    end.

get_key_data(AppName, Match) ->
	get_key_direct(AppName, #appl{appl_data = Match, _ = '_'}).

get_pid_all_key(Master) ->
    case ets:match(ac_tab, {{application_master, '$1'}, Master}) of
	[[AppName]] -> get_all_key(AppName);
	_ -> []
    end.

get_all_key(AppName) ->
    case ets:lookup(ac_tab, {loaded, AppName}) of
	[{_, Appl}] ->
	    {ok, [{description, Appl#appl.descr},
		  {id, Appl#appl.id},
		  {vsn, Appl#appl.vsn},
		  {modules, (Appl#appl.appl_data)#appl_data.mods},
		  {maxP, (Appl#appl.appl_data)#appl_data.maxP},
		  {maxT, (Appl#appl.appl_data)#appl_data.maxT},
		  {registered, (Appl#appl.appl_data)#appl_data.regs},
		  {included_applications, Appl#appl.inc_apps},
                  {optional_applications, Appl#appl.opt_apps},
		  {applications, Appl#appl.apps},
		  {env, get_all_env(AppName)},
		  {mod, (Appl#appl.appl_data)#appl_data.mod},
		  {start_phases, (Appl#appl.appl_data)#appl_data.phases}
		 ]};
	_ -> 
	    undefined
    end.


start_type(Master) ->
    case ets:match(ac_tab, {{application_master, '$1'}, Master}) of
	[[AppName]] -> 
	    call({start_type, AppName}, infinity);
	_X -> 
	    undefined
    end.






get_master(AppName) ->
    ets:lookup_element(ac_tab, {application_master, AppName}, 2, undefined).

get_application(Master) ->
    case ets:match(ac_tab, {{application_master, '$1'}, Master}) of
	[[AppName]] -> {ok, AppName};
	_ -> undefined
    end.

get_application_module(Module) ->
    ApplDataPattern = #appl_data{mods='$2', _='_'},
    ApplPattern = #appl{appl_data=ApplDataPattern, _='_'},
    AppModules = ets:match(ac_tab, {{loaded, '$1'}, ApplPattern}),
    get_application_module(Module, AppModules).

get_application_module(Module, [[AppName, Modules]|AppModules]) ->
    case lists:member(Module, Modules) of
	true ->
	    {ok, AppName};
	false ->
	    get_application_module(Module, AppModules)
    end;
get_application_module(_Module, []) ->
    undefined.

permit_application(ApplName, Flag) ->
    call({permit_application, ApplName, Flag},infinity).

set_env(Config, Opts) ->
    case check_conf_data(Config) of
	ok ->
	    Timeout = proplists:get_value(timeout, Opts, 5000),
	    call({set_env, Config, Opts}, Timeout);

	{error, _} = Error ->
	    Error
    end.

set_env(AppName, Key, Val) ->
    call({set_env, AppName, Key, Val, []}).
set_env(AppName, Key, Val, Opts) ->
    Timeout = proplists:get_value(timeout, Opts, 5000),
    call({set_env, AppName, Key, Val, Opts}, Timeout).

unset_env(AppName, Key) ->
    call({unset_env, AppName, Key, []}).
unset_env(AppName, Key, Opts) ->
    Timeout = proplists:get_value(timeout, Opts, 5000),
    call({unset_env, AppName, Key, Opts}, Timeout).

call(Cmd) ->
    case gen_server:call(?AC, Cmd) of
        {error, terminating} ->
            exit(terminating);
        Res ->
            Res
    end.
call(Cmd, Timeout) ->
    case gen_server:call(?AC, Cmd, Timeout) of
        {error, terminating} ->
            exit(terminating);
        Res ->
            Res
    end.

%%%-----------------------------------------------------------------
%%% call-back functions from gen_server
%%%-----------------------------------------------------------------
init(Init, Kernel) ->
    register(?AC, self()),
    process_flag(trap_exit, true),
    put('$ancestors', [Init]), % OTP-5811, for gen_server compatibility
    put('$initial_call', {application_controller, start, 1}),

    case catch check_conf() of
	{ok, ConfData} ->
	    %% Actually, we don't need this info in an ets table anymore.
	    %% This table was introduced because starting applications
	    %% should be able to get some info from AC (e.g. loaded_apps).
	    %% The new implementation makes sure the AC process can be
	    %% called during start-up of any app.
	    case check_conf_data(ConfData) of
		ok ->
		    _ = ets:new(ac_tab, [set, public, named_table,
                                         {read_concurrency,true}]),
		    S = #state{conf_data = ConfData},
		    {ok, KAppl} = make_appl(Kernel),
		    case catch load(S, KAppl) of
			{'EXIT', LoadError} ->
			    Reason = {'load error', LoadError},
			    Init ! {ack, self(), {error, to_string(Reason)}};
                        {error, Error} ->
                            Init ! {ack, self(), {error, to_string(Error)}};
			{ok, NewS} ->
			    Init ! {ack, self(), ok},
			    gen_server:enter_loop(?MODULE, [], NewS,
						  {local, ?AC})
		    end;
		{error, ErrorStr} ->
		    Str = lists:flatten(io_lib:format("invalid config data: ~ts", [ErrorStr])),
		    Init ! {ack, self(), {error, to_string(Str)}}
	    end;
	{error, {File, Line, Str}} ->
	    ReasonStr =
		lists:flatten(io_lib:format("error in config file "
					    "~tp (~w): ~ts",
					    [File, Line, Str])),
	    Init ! {ack, self(), {error, to_string(ReasonStr)}};
        {error, {file_descriptor, FDString, Line, Str}} ->
	    ReasonStr =
		lists:flatten(io_lib:format("error in config read from file descriptor "
					    "~tp (~w): ~ts",
					    [FDString, Line, Str])),
	    Init ! {ack, self(), {error, to_string(ReasonStr)}}
    end.


%% Check the syntax of the .config file
%%  [{ApplicationName, [{Parameter, Value}]}].

check_conf_data([]) ->
    ok;
check_conf_data(ConfData) when is_list(ConfData) ->
    [Application | ConfDataRem] = ConfData,
    case Application of
	{AppName, List} when is_atom(AppName), is_list(List) ->
	    case lists:keymember(AppName, 1, ConfDataRem) of
		true ->
		    {error, "duplicate application config: " ++ atom_to_list(AppName)};
		false ->
		    case check_para(List, AppName) of
			ok -> check_conf_data(ConfDataRem);
			Error -> Error
		    end
	    end;
	{AppName, List} when is_list(List)  ->
	    ErrMsg = "application: "
		++ lists:flatten(io_lib:format("~tp",[AppName]))
		++ "; application name must be an atom",
	    {error, ErrMsg};
	{AppName, _List} ->
	    ErrMsg = "application: "
		++ lists:flatten(io_lib:format("~tp",[AppName]))
		++ "; parameters must be a list",
	    {error, ErrMsg};
	Else ->
	    ErrMsg = "invalid application config: "
		++ lists:flatten(io_lib:format("~tp",[Else])),
	    {error, ErrMsg}
    end;
check_conf_data(_ConfData) ->
    {error, "configuration must be a list ended by <dot><whitespace>"}.


check_para([], _AppName) ->
    ok;
check_para([{Para, Val} | ParaList], AppName) when is_atom(Para) ->
    case lists:keymember(Para, 1, ParaList) of
	true ->
	    ErrMsg =  "application: " ++ atom_to_list(AppName)
		++ "; duplicate parameter: " ++ atom_to_list(Para),
	    {error, ErrMsg};
	false ->
	    case check_para_value(Para, Val, AppName) of
		ok -> check_para(ParaList, AppName);
		{error, _} = Error -> Error
	    end
    end;
check_para([{Para, _Val} | _ParaList], AppName) ->
    {error, "application: " ++ atom_to_list(AppName) ++ "; invalid parameter name: " ++
     lists:flatten(io_lib:format("~tp",[Para]))};
check_para([Else | _ParaList], AppName) ->
    {error, "application: " ++ atom_to_list(AppName) ++ "; invalid parameter: " ++
     lists:flatten(io_lib:format("~tp",[Else]))}.

check_para_value(distributed, Apps, kernel) -> check_distributed(Apps);
check_para_value(_Para, _Val, _AppName) -> ok.

%% Special check of distributed parameter for kernel
check_distributed([]) ->
    ok;
check_distributed([{App, List} | Apps]) when is_atom(App), is_list(List) ->
    check_distributed(Apps);
check_distributed([{App, infinity, List} | Apps]) when is_atom(App), is_list(List) ->
    check_distributed(Apps);
check_distributed([{App, Time, List} | Apps]) when is_atom(App), is_integer(Time), is_list(List) ->
    check_distributed(Apps);
check_distributed(_Else) ->
    {error, "application: kernel; erroneous parameter: distributed"}.


-type calls() :: 'info' | 'prep_config_change' | 'which_applications'
               | {'config_change' | 'control_application' |
		  'load_application' | 'start_type' | 'stop_application' |
		  'unload_application', term()}
               | {'change_application_data', _, _}
               | {'permit_application', atom() | {'application',atom(),_},_}
               | {'start_application', _, _}
               | {'unset_env', _, _, _}
               | {'set_env', _, _, _, _}.

-spec handle_call(calls(), {pid(), term()}, state()) ->
        {'noreply', state()} | {'reply', term(), state()}.

handle_call({load_application, Application}, From, S) ->
    case catch maybe_load_application(Application, S) of
	{ok, NewS} ->
	    AppName = get_appl_name(Application),
	    case cntrl(AppName, S, {ac_load_application_req, AppName}) of
		true ->
		    {noreply, S#state{loading = [{AppName, From} |
						 S#state.loading]}};
		false ->
		    {reply, ok, NewS}
	    end;
	{error, _} = Error ->
	    {reply, Error, S};
	{'EXIT', R} ->
	    {reply, {error, R}, S}
    end;

handle_call({unload_application, AppName}, _From, S) ->
    case lists:keymember(AppName, 1, S#state.running) of
	true -> {reply, {error, {running, AppName}}, S};
	false ->
	    case get_loaded(AppName) of
		{true, _} ->
		    NewS = unload(AppName, S),
		    cntrl(AppName, S, {ac_application_unloaded, AppName}),
		    {reply, ok, NewS};
		false ->
		    {reply, {error, {not_loaded, AppName}}, S}
	    end
    end;

handle_call({start_application, AppName, RestartType}, From, S) ->
    #state{running = Running, starting = Starting, start_p_false = SPF, 
	   started = Started, start_req = Start_req} = S,
    %% Check if the commandline environment variables are OK.
    %% In case of erroneous variables do not start the application,
    %% if the application is permanent crash the node.
    %% Check if the application is already starting.
    case lists:keyfind(AppName, 1, Start_req) of
	false ->
	    case catch check_start_cond(AppName, RestartType, Started, Running) of
		{ok, Appl} ->
		    Cntrl = cntrl(AppName, S, {ac_start_application_req, AppName}),
		    Perm = application:get_env(kernel, permissions),
		    case {Cntrl, Perm} of
			{true, _} ->
			    {noreply, S#state{starting = [{AppName, RestartType, normal, From} |
							  Starting],
					      start_req = [{AppName, From} | Start_req]}};
			{false, undefined} ->
			    spawn_starter(Appl, S, normal),
			    {noreply, S#state{starting = [{AppName, RestartType, normal, From} |
							  Starting],
					      start_req = [{AppName, From} | Start_req]}};
			{false, {ok, Perms}} ->
			    case lists:member({AppName, false}, Perms) of
				false ->
				    spawn_starter(Appl, S, normal),
				    {noreply, S#state{starting = [{AppName, RestartType, normal, From} |
								  Starting],
						      start_req = [{AppName, From} | Start_req]}};
				true ->
				    SS = S#state{start_p_false = [{AppName, RestartType, normal, From} |
								  SPF]},
				    {reply, ok, SS}
			    end
		    end;
		{error, _R} = Error ->
		    {reply, Error, S}
	    end;
	{AppName, _FromX} ->
	    SS = S#state{start_req = [{AppName, From} | Start_req]},
	    {noreply, SS}
    end;

handle_call({is_running, AppName}, _From, S) ->
    #state{running = Running} = S,
    {reply, lists:keymember(AppName, 1, Running), S};

handle_call({permit_application, AppName, Bool}, From, S) ->
    Control = S#state.control,
    Starting = S#state.starting,
    SPF = S#state.start_p_false,
    Started = S#state.started,
    Running = S#state.running,
    Start_req = S#state.start_req,
    IsLoaded = get_loaded(AppName),
    IsStarting = lists:keysearch(AppName, 1, Starting),
    IsSPF = lists:keysearch(AppName, 1, SPF),
    IsStarted = lists:keysearch(AppName, 1, Started),
    IsRunning = lists:keysearch(AppName, 1, Running),

    case lists:keymember(AppName, 1, Control) of
	%%========================
	%% distributed application
	%%========================
	true ->
	    case {IsLoaded, IsStarting, IsStarted} of
		%% not loaded
		{false, _, _} ->
		    {reply, {error, {not_loaded, AppName}}, S};
		%% only loaded
		{{true, _Appl}, false, false} ->
		    update_permissions(AppName, Bool),
		    {reply, {distributed_application, only_loaded}, S};
		_ ->
		    update_permissions(AppName, Bool),
		    {reply, distributed_application, S}
	    end;
	%%========================
	%% local application
	%%========================
	false ->
	    case {Bool, IsLoaded, IsStarting, IsSPF, IsStarted, IsRunning} of
		%%------------------------
		%% permit the applicaition
		%%------------------------
		%% already running
		{true, _, _, _, _, {value, _Tuple}} ->
		    {reply, ok, S};
		%% not loaded
		{true, false, _, _, _, _} ->
		    {reply, {error, {not_loaded, AppName}}, S};
		%% only loaded
		{true, {true, _Appl}, false, false, false, false} ->
		    update_permissions(AppName, Bool),
                    {reply, ok, S}; 
		%% starting
		{true, {true, _Appl}, {value, _Tuple}, false, false, false} ->
		    update_permissions(AppName, Bool),
                    {reply, ok, S}; %% check the permission after then app is started
		%% start requested but not started because permit was false
		{true, {true, Appl}, false, {value, Tuple}, false, false} ->
		    update_permissions(AppName, Bool),
		    {_AppName2, RestartType, normal, _From} = Tuple,
		    spawn_starter(Appl, S, normal),
		    SS = S#state{starting = [{AppName, RestartType, normal, From} | Starting],
				 start_p_false = keydelete(AppName, 1, SPF),
				 start_req = [{AppName, From} | Start_req]},
		    {noreply, SS};
		%% started but not running
		{true, {true, Appl}, _, _, {value, {AppName, RestartType}}, false} ->
		    update_permissions(AppName, Bool),
		    spawn_starter(Appl, S, normal),
		    SS = S#state{starting = [{AppName, RestartType, normal, From} | Starting],
				 started = keydelete(AppName, 1, Started),
				 start_req = [{AppName, From} | Start_req]},
		    {noreply, SS};

		%%==========================
		%% unpermit the application
		%%==========================
		%% running
		{false, _, _, _,  _, {value, {_AppName, Id}}} ->
		    {_AppName2, Type} = lists:keyfind(AppName, 1, Started),
		    stop_appl(AppName, Id, Type),
		    NRunning = keydelete(AppName, 1, Running),
		    {reply, ok, S#state{running = NRunning}};
		%% not loaded
		{false, false, _, _, _,  _} ->
		    {reply, {error, {not_loaded, AppName}}, S};
		%% only loaded
		{false, {true, _Appl}, false, false, false, false} ->
		    update_permissions(AppName, Bool),
                    {reply, ok, S}; 
		%% starting
		{false, {true, _Appl}, {value, _Tuple}, false, false, false} ->
		    update_permissions(AppName, Bool),
		    {reply, ok, S};
		%% start requested but not started because permit was false
		{false, {true, _Appl}, false, {value, _Tuple}, false, false} ->
		    update_permissions(AppName, Bool),
		    SS = S#state{start_p_false = keydelete(AppName, 1, SPF)},
		    {reply, ok, SS};
		%% started but not running
		{false, {true, _Appl}, _,  _, {value, _Tuple}, false} ->
		    update_permissions(AppName, Bool),
		    {reply, ok, S}

	    end
    end;

handle_call({stop_application, AppName}, _From, S) ->
    #state{running = Running, started = Started} = S,
    case lists:keyfind(AppName, 1, Running) of
	{_AppName, Id} ->
	    {_AppName2, Type} = lists:keyfind(AppName, 1, Started),
	    stop_appl(AppName, Id, Type),
	    NRunning = keydelete(AppName, 1, Running),
	    NStarted = keydelete(AppName, 1, Started),
	    cntrl(AppName, S, {ac_application_stopped, AppName}),
	    {reply, ok, S#state{running = NRunning, started = NStarted}};
	false ->
	    case lists:keymember(AppName, 1, Started) of
		true ->
		    NStarted = keydelete(AppName, 1, Started),
		    cntrl(AppName, S, {ac_application_stopped, AppName}),
		    {reply, ok, S#state{started = NStarted}};
		false ->
		    {reply, {error, {not_started, AppName}}, S}
	    end
    end;

handle_call({change_application_data, Applications, Config}, _From, S) ->
    OldAppls = ets:foldl(fun({{loaded, _AppName}, Appl}, Acc) ->
                                 [Appl|Acc];
                            (_, Acc) ->
                                 Acc
                         end, [], ac_tab),
    case catch do_change_apps(Applications, Config, OldAppls) of
	{error, _} = Error ->
	    {reply, Error, S};
	{'EXIT', R} ->
	    {reply, {error, R}, S};
	{NewAppls, NewConfig} ->
	    lists:foreach(fun(Appl) ->
				  ets:insert(ac_tab, {{loaded, Appl#appl.name},
						      Appl})
			  end, NewAppls),
	    {reply, ok, S#state{conf_data = NewConfig}}
    end;

handle_call(prep_config_change, _From, S) ->
    RunningApps = S#state.running,
    EnvBefore = lists:reverse(do_prep_config_change(RunningApps)),
    {reply, EnvBefore, S};

handle_call({config_change, EnvBefore}, _From, S) ->
    RunningApps = S#state.running,
    R = do_config_change(RunningApps, EnvBefore),
    {reply, R, S};

handle_call(which_applications, _From, S) ->
    Reply = zf(fun({Name, Id}) ->
		       case Id of
			   {distributed, _Node} ->
			       false;
			   _ ->
			       {true, #appl{descr = Descr, vsn = Vsn}} =
				   get_loaded(Name),
			       {true, {Name, Descr, Vsn}}
		       end
	       end, S#state.running),
    {reply, Reply, S};

handle_call({set_env, Config, Opts}, _From, S) ->
    _ = [add_env(AppName, Env) || {AppName, Env} <- Config],

    case proplists:get_value(persistent, Opts, false) of
	true ->
	    {reply, ok, S#state{conf_data = merge_env(S#state.conf_data, Config)}};
	false ->
	    {reply, ok, S}
    end;

handle_call({set_env, AppName, Key, Val, Opts}, _From, S) ->
    ets:insert(ac_tab, {{env, AppName, Key}, Val}),
    case proplists:get_value(persistent, Opts, false) of
	true ->
	    Fun = fun(Env) -> lists:keystore(Key, 1, Env, {Key, Val}) end,
	    {reply, ok, S#state{conf_data = change_app_env(S#state.conf_data, AppName, Fun)}};
	false ->
	    {reply, ok, S}
    end;

handle_call({unset_env, AppName, Key, Opts}, _From, S) ->
    ets:delete(ac_tab, {env, AppName, Key}),
    case proplists:get_value(persistent, Opts, false) of
	true ->
	    Fun = fun(Env) -> lists:keydelete(Key, 1, Env) end,
	    {reply, ok, S#state{conf_data = change_app_env(S#state.conf_data, AppName, Fun)}};
	false ->
	    {reply, ok, S}
    end;

handle_call({control_application, AppName}, {Pid, _Tag}, S) ->
    Control = S#state.control,
    case lists:keymember(AppName, 1, Control) of
	false ->
	    link(Pid),
	    {reply, true, S#state{control = [{AppName, Pid} | Control]}};
	true ->
	    {reply, false, S}
    end;

handle_call({start_type, AppName}, _From, S) ->
    Starting = S#state.starting,
    StartType = case lists:keyfind(AppName, 1, Starting) of
		    false ->
			local;
		    {_AppName, _RestartType, Type, _F} ->
			Type
		end,
    {reply, StartType, S};

handle_call(info, _From, S) ->
    Reply = [{loaded, loaded_applications()},
	     {loading, S#state.loading},
	     {started, S#state.started},
	     {start_p_false, S#state.start_p_false},
	     {running, S#state.running},
	     {starting, S#state.starting}],
    {reply, Reply, S}.

-spec handle_cast({'application_started', appname(), _}, state()) ->
        {'noreply', state()} | {'stop', string(), state()}.

handle_cast({application_started, AppName, Res}, S) ->
    handle_application_started(AppName, Res, S).

handle_application_started(AppName, Res, S) ->
    #state{starting = Starting, running = Running, started = Started, 
	   start_req = Start_req} = S,
    Start_reqN = reply_to_requester(AppName, Start_req, Res),
    {AppName, RestartType, _Type, _From} = lists:keyfind(AppName, 1, Starting),
    case Res of
	{ok, Id} ->
	    case AppName of
		kernel -> check_user();
		_ -> ok
	    end,
	    info_started(AppName, nd(Id)),
	    notify_cntrl_started(AppName, Id, S, ok),
	    NRunning = keyreplaceadd(AppName, 1, Running,{AppName,Id}),
	    NStarted = keyreplaceadd(AppName, 1, Started,{AppName,RestartType}),
	    NewS =  S#state{starting = keydelete(AppName, 1, Starting),
			    running = NRunning,
			    started = NStarted,
			    start_req = Start_reqN},
	    %% The permission may have been changed during start
	    Perm = application:get_env(kernel, permissions),
	    case {Perm, Id} of
		{undefined, _} ->
		    {noreply, NewS};
		%% Check only if the application is started on the own node
		{{ok, Perms}, {distributed, StartNode}} when StartNode =:= node() ->
		    case lists:member({AppName, false}, Perms) of
			true ->
			    #state{running = StopRunning, started = StopStarted} = NewS,
			    case lists:keyfind(AppName, 1, StopRunning) of
				{AppName, Id} ->
				    {AppName, Type} =
					lists:keyfind(AppName, 1, StopStarted),
				    stop_appl(AppName, Id, Type),
				    NStopRunning = keydelete(AppName, 1, StopRunning),
				    cntrl(AppName, NewS, {ac_application_stopped, AppName}),
				    {noreply, NewS#state{running = NStopRunning, 
							started = StopStarted}};
				false ->
				    {noreply, NewS}
			    end;
			false ->
			    {noreply, NewS}
		    end;
		_ ->
		    {noreply, NewS}
	    end;
	{error, R} = Error when RestartType =:= temporary ->
	    notify_cntrl_started(AppName, undefined, S, Error),
	    info_exited(AppName, R, RestartType),
	    {noreply, S#state{starting = keydelete(AppName, 1, Starting),
			      start_req = Start_reqN}};
	{info, R} when RestartType =:= temporary ->
	    notify_cntrl_started(AppName, undefined, S, {error, R}),
	    {noreply, S#state{starting = keydelete(AppName, 1, Starting),
			      start_req = Start_reqN}};
	{ErrInf, R} when RestartType =:= transient, ErrInf =:= error;
			 RestartType =:= transient, ErrInf =:= info ->
	    notify_cntrl_started(AppName, undefined, S, {error, R}),
	    case ErrInf of
		error ->
		    info_exited(AppName, R, RestartType);
		info ->
		    ok
	    end,
	    case R of
		{{'EXIT',normal},_Call} ->
		    {noreply, S#state{starting = keydelete(AppName, 1, Starting),
				      start_req = Start_reqN}};
		_ ->
		    Reason = {application_start_failure, AppName, R},
		    {stop, to_string(Reason), S}
	    end;
	{error, R} = Error -> %% permanent
	    notify_cntrl_started(AppName, undefined, S, Error),
	    info_exited(AppName, R, RestartType),
	    Reason = {application_start_failure, AppName, R},
	    {stop, to_string(Reason), S};
	{info, R} -> %% permanent
	    notify_cntrl_started(AppName, undefined, S, {error, R}),
	    Reason = {application_start_failure, AppName, R},
	    {stop, to_string(Reason), S}
    end.

-spec handle_info(term(), state()) ->
        {'noreply', state()} | {'stop', string(), state()}.

handle_info({ac_load_application_reply, AppName, Res}, S) ->
    case keysearchdelete(AppName, 1, S#state.loading) of
	{value, {_AppName, From}, Loading} ->
	    gen_server:reply(From, Res),
	    case Res of
		ok ->
		    {noreply, S#state{loading = Loading}};
		{error, _R} ->
		    NewS = unload(AppName, S),
		    {noreply, NewS#state{loading = Loading}}
	    end;
	false ->
	    {noreply, S}
    end;

handle_info({ac_start_application_reply, AppName, Res}, S) ->
    Start_req = S#state.start_req,
    case lists:keyfind(AppName, 1, Starting = S#state.starting) of
	{_AppName, RestartType, Type, From} ->
	    case Res of
		start_it ->
		    {true, Appl} = get_loaded(AppName),
		    spawn_starter(Appl, S, Type),
		    {noreply, S};
		{started, Node} ->
		    handle_application_started(AppName, 
					       {ok, {distributed, Node}}, 
					       S);
		not_started ->
		    Started = S#state.started,
		    Start_reqN =
			reply_to_requester(AppName, Start_req, ok),
		    {noreply, 
		     S#state{starting = keydelete(AppName, 1, Starting),
			     started = [{AppName, RestartType} | Started],
			     start_req = Start_reqN}};
		{takeover, _Node} = Takeover ->
		    {true, Appl} = get_loaded(AppName),
		    spawn_starter(Appl, S, Takeover),
		    NewStarting1 = keydelete(AppName, 1, Starting),
		    NewStarting = [{AppName, RestartType, Takeover, From} | NewStarting1],
		    {noreply, S#state{starting = NewStarting}};
		{error, Reason} = Error when RestartType =:= permanent ->
		    Start_reqN = reply_to_requester(AppName, Start_req, Error),
		    {stop, to_string(Reason), S#state{start_req = Start_reqN}};
		{error, _Reason} = Error ->
		    Start_reqN = reply_to_requester(AppName, Start_req, Error),
		    {noreply, S#state{starting =
					  keydelete(AppName, 1, Starting),
				      start_req = Start_reqN}}
	    end;
	false ->
	    {noreply, S} % someone called stop before control got that
    end;

handle_info({ac_change_application_req, AppName, Msg}, S) ->
    Running = S#state.running,
    Started = S#state.started,
    Starting = S#state.starting,
    case {keyfind(AppName, 1, Running), keyfind(AppName, 1, Started)} of
	{{AppName, Id}, {_AppName2, Type}} ->
	    case Msg of
		{started, Node} ->
		    stop_appl(AppName, Id, Type),
		    NRunning = [{AppName, {distributed, Node}} |
				keydelete(AppName, 1, Running)],
		    {noreply, S#state{running = NRunning}};
		{takeover, _Node, _RT} when is_pid(Id) -> % it is running already
		    notify_cntrl_started(AppName, Id, S, ok),
		    {noreply, S};
		{takeover, Node, RT} ->
		    NewS = do_start(AppName, RT, {takeover, Node}, undefined, S),
		    {noreply, NewS};
		{failover, _Node, _RT} when is_pid(Id) -> % it is running already
		    notify_cntrl_started(AppName, Id, S, ok),
		    {noreply, S};
		{failover, Node, RT} ->
		    case application:get_key(AppName, start_phases) of
			{ok, undefined} ->
			    %% to be backwards compatible the application
			    %% is not started as failover if start_phases  
			    %% is not defined in the .app file
			    NewS = do_start(AppName, RT, normal, undefined, S),
			    {noreply, NewS};
			{ok, _StartPhases} ->
			    NewS = do_start(AppName, RT, {failover, Node}, undefined, S),
			    {noreply, NewS}
		    end;
		stop_it ->
		    stop_appl(AppName, Id, Type),
		    cntrl(AppName, S, {ac_application_not_run, AppName}),
		    NRunning = keyreplace(AppName, 1, Running, 
					 {AppName, {distributed, []}}),
		    {noreply, S#state{running = NRunning}};
		%% We should not try to start a running application!
		start_it when is_pid(Id) ->
		    notify_cntrl_started(AppName, Id, S, ok),
		    {noreply, S};
		start_it ->
		    NewS = do_start(AppName, undefined, normal, undefined, S),
		    {noreply, NewS};
		not_running ->
		    NRunning = keydelete(AppName, 1, Running),
		    {noreply, S#state{running = NRunning}};
		_ ->
		    {noreply, S}
	    end;
	_ ->
	    IsLoaded = get_loaded(AppName),
	    IsStarting = lists:keysearch(AppName, 1, Starting),
	    IsStarted = lists:keysearch(AppName, 1, Started),
	    IsRunning = lists:keysearch(AppName, 1, Running),

	    case Msg of
		start_it ->
		    case {IsLoaded, IsStarting, IsStarted, IsRunning} of
			%% already running
			{_, _, _, {value, _Tuple}} ->
			    {noreply, S};
			%% not loaded
			{false, _, _, _} ->
			    {noreply, S};
			%% only loaded
			{{true, _Appl}, false, false, false} ->
			    {noreply, S};
			%% starting
			{{true, _Appl}, {value, Tuple}, false, false} ->
			    {_AppName, _RStype, _Type, From} = Tuple,
			    NewS = do_start(AppName, undefined, normal, From, S),
			    {noreply, NewS};
			%% started but not running
			{{true, _Appl}, _, {value, {AppName, _RestartType}}, false} ->
			    NewS = do_start(AppName, undefined, normal, undefined, S),
			    SS = NewS#state{started = keydelete(AppName, 1, Started)},
			    {noreply, SS}
		    end;
		{started, Node} ->
		    NRunning = [{AppName, {distributed, Node}} |
				keydelete(AppName, 1, Running)],
		    {noreply, S#state{running = NRunning}};
		_ ->
		    {noreply, S} % someone called stop before control got that
	    end
    end;

%%-----------------------------------------------------------------
%% An application died.  Check its restart_type.  Maybe terminate
%% all other applications.
%%-----------------------------------------------------------------
handle_info({'EXIT', Pid, Reason}, S) ->
    ets:match_delete(ac_tab, {{application_master, '_'}, Pid}),
    NRunning = keydelete(Pid, 2, S#state.running),
    NewS = S#state{running = NRunning},
    case lists:keyfind(Pid, 2, S#state.running) of
	{AppName, _AmPid} ->
	    cntrl(AppName, S, {ac_application_stopped, AppName}),
	    case lists:keyfind(AppName, 1, S#state.started) of
		{_AppName, temporary} ->
		    info_exited(AppName, Reason, temporary),
		    {noreply, NewS};
		{_AppName, transient} when Reason =:= normal ->
		    info_exited(AppName, Reason, transient),
		    {noreply, NewS};
		{_AppName, Type} ->
		    info_exited(AppName, Reason, Type),
		    {stop, to_string({application_terminated, AppName, Reason}), NewS}
	    end;
	false ->
	    {noreply, S#state{control = del_cntrl(S#state.control, Pid)}}
    end;
    
handle_info(_, S) ->
    {noreply, S}.

-spec terminate(term(), state()) -> 'ok'.

terminate(Reason, S) ->
    case application:get_env(kernel, shutdown_func) of
	{ok, {M, F}} ->
	    catch M:F(Reason);
	_ ->
	    ok
    end,
    ShutdownTimeout =
	case application:get_env(kernel, shutdown_timeout) of
	    undefined -> infinity;
	    {ok,T} -> T
	end,
    foreach(fun({_AppName, Id}) when is_pid(Id) -> 
		    Ref = erlang:monitor(process, Id),
		    unlink(Id),
		    exit(Id, shutdown),
		    receive
			%% Proc died before link
			{'EXIT', Id, _} -> ok
		    after 0 ->
                            (fun F() ->
                                     receive
                                         {'DOWN', Ref, process, Id, _} -> ok;
                                         %% We need to handle any gen_server:call here
                                         %% and reply to them so that they don't deadlock
                                         {'$gen_call', From, _Msg} ->
                                             gen_server:reply(From, {error, terminating}),
                                             F()
                                     after ShutdownTimeout ->
                                             exit(Id, kill),
                                             receive
                                                 {'DOWN', Ref, process, Id, _} -> ok
                                             end
                                     end
                             end)()
		    end;
	       (_) -> ok
	    end,
	    S#state.running),
    true = ets:delete(ac_tab),
    ok.

-spec code_change(term(), state(), term()) -> {'ok', state()}.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%%%-----------------------------------------------------------------
%%% Internal functions
%%%-----------------------------------------------------------------
cntrl(AppName, #state{control = Control}, Msg) ->
    case lists:keyfind(AppName, 1, Control) of
	{_AppName, Pid} ->
	    Pid ! Msg,
	    true;
	false -> 
	    false
    end.

notify_cntrl_started(_AppName, {distributed, _Node}, _S, _Res) ->
    ok;
notify_cntrl_started(AppName, _Id, S, Res) ->
    cntrl(AppName, S, {ac_application_run, AppName, Res}).
    
del_cntrl([{_, Pid}|T], Pid) ->
    del_cntrl(T, Pid);
del_cntrl([H|T], Pid) ->
    [H|del_cntrl(T, Pid)];
del_cntrl([], _Pid) ->
    [].

get_loaded(App) ->
    AppName = get_appl_name(App),
    case ets:lookup(ac_tab, {loaded, AppName}) of 
	[{_Key, Appl}] -> {true, Appl};
	_  -> false
    end.

maybe_load_application(Application, S) ->
    case get_loaded(Application) of
	{true, _} ->
	    throw({error, {already_loaded, Application}});
	false ->
	    do_load_application(Application, S)
    end.

do_load_application(Application, S) ->
    case make_appl(Application) of
	{ok, Appl} -> load(S, Appl);
	Error -> Error
    end.

%% Recursively load the application and its included apps.
%load(S, {ApplData, ApplEnv, IncApps, Descr, Vsn, Apps}) ->
load(S, {ApplData, ApplEnv, IncApps, OptApps, Descr, Id, Vsn, Apps}) ->
    Name = ApplData#appl_data.name,
    ConfEnv = get_env_i(Name, S),
    NewEnv = merge_app_env(ApplEnv, ConfEnv),
    CmdLineEnv = get_cmd_env(Name),
    NewEnv2 = merge_app_env(NewEnv, CmdLineEnv),
    add_env(Name, NewEnv2),
    Appl = #appl{name = Name, descr = Descr, id = Id, vsn = Vsn, apps = Apps,
		 appl_data = ApplData, inc_apps = IncApps, opt_apps = OptApps},
    ets:insert(ac_tab, {{loaded, Name}, Appl}),
    NewS =
	foldl(fun(App, S1) ->
		      case get_loaded(App) of
			  {true, _} -> S1;
			  false ->
			      case do_load_application(App, S1) of
				  {ok, S2} -> S2;
				  Error -> throw(Error)
			      end
		      end
	      end, S, IncApps),
    {ok, NewS}.

unload(AppName, S) ->
    {ok, IncApps} = get_key(AppName, included_applications),
    del_env(AppName),
    ets:delete(ac_tab, {loaded, AppName}),
    foldl(fun(App, S1) ->
		  case get_loaded(App) of
		      false -> S1;
		      {true, _} -> unload(App, S1)
		  end
	  end, S, IncApps).

check_start_cond(AppName, RestartType, Started, Running) ->
    validRestartType(RestartType),
    case get_loaded(AppName) of
	{true, Appl} ->
	    %% Check Running; not Started.  An exited app is not running,
	    %% but started.  It must be possible to start an exited app!
	    case lists:keymember(AppName, 1, Running) of
		true ->
		    {error, {already_started, AppName}};
		false ->
                    case find_missing_dependency(Appl, Started) of
                        {value, NotStarted} -> throw({error, {not_started, NotStarted}});
                        false -> ok
                    end,
		    {ok, Appl}
	    end;
	false ->
	    {error, {not_loaded, AppName}}
    end.

do_start(AppName, RT, Type, From, S) ->
    RestartType = case lists:keyfind(AppName, 1, S#state.started) of
		      {_AppName2, OldRT} ->
			  get_restart_type(RT, OldRT);
		      false ->
			  RT
		  end,
    %% UW 990913: We check start_req instead of starting, because starting 
    %% has already been checked.
    case lists:keymember(AppName, 1, S#state.start_req) of
	false ->
	    {true, Appl} = get_loaded(AppName),
	    Start_req = S#state.start_req,
	    spawn_starter(Appl, S, Type),
	    Starting = case lists:keymember(AppName, 1, S#state.starting) of
			   false ->
			       %% UW: don't know if this is necessary
			       [{AppName, RestartType, Type, From} | 
				S#state.starting];
			   true ->
			       S#state.starting
		       end,
	    S#state{starting = Starting, 
		    start_req = [{AppName, From} | Start_req]};
	true -> % otherwise we're already starting the app...
	    S
    end.

spawn_starter(#appl{appl_data=#appl_data{mod=[]},name=Name}, _S, _Type) ->
    gen_server:cast(?AC, {application_started, Name, {ok, undefined}});
spawn_starter(Appl, S, Type) ->
    case find_missing_dependency(Appl, S#state.running) of
        {value, NotRunning} ->
            Reply = {info, {not_running, NotRunning}},
            gen_server:cast(?AC, {application_started, Appl#appl.name, Reply});
        false ->
            application_master:start_link(Appl#appl.appl_data, Type)
    end.

reply(undefined, _Reply) ->
    ok;
reply(From, Reply) -> gen_server:reply(From, Reply).

find_missing_dependency(Appl, Applications) ->
    Pred = fun(AppName) -> not lists:keymember(AppName, 1, Applications) end,
    lists:search(Pred, Appl#appl.apps -- Appl#appl.opt_apps).

%%-----------------------------------------------------------------
%% Stop application locally.
%%-----------------------------------------------------------------
stop_appl(AppName, Id, Type) when is_pid(Id) ->
    unlink(Id),
    application_master:stop(Id),
    info_exited(AppName, stopped, Type),
    ets:delete(ac_tab, {application_master, AppName});
stop_appl(AppName, undefined, Type) ->
    %% Code-only application stopped
    info_exited(AppName, stopped, Type);
stop_appl(_AppName, _Id, _Type) ->
    %% Distributed application stopped
    ok. 

keysearchdelete(Key, Pos, List) ->
    ksd(Key, Pos, List, []).

ksd(Key, Pos, [H | T], Rest) when element(Pos, H) =:= Key ->
    {value, H, Rest ++ T};
ksd(Key, Pos, [H | T], Rest) ->
    ksd(Key, Pos, T, [H | Rest]);
ksd(_Key, _Pos, [], _Rest) ->
    false.
    
keyreplaceadd(Key, Pos, List, New) ->
    %% Maintains the order!
    case lists:keymember(Key, Pos, List) of
	true -> keyreplace(Key, Pos, List, New);
	false -> [New | List]
    end.

validRestartType(permanent)   -> true;
validRestartType(temporary)   -> true;
validRestartType(transient)   -> true;
validRestartType(RestartType) ->
    throw({error, {invalid_restart_type, RestartType}}).

nd({distributed, Node}) -> Node;
nd(_) -> node().
	  
get_restart_type(undefined, OldRT) ->
    OldRT;
get_restart_type(RT, _OldRT) ->
    RT.

get_appl_name(Name) when is_atom(Name) -> Name;
get_appl_name({application, Name, _}) when is_atom(Name) -> Name;
get_appl_name(Appl) -> throw({error, {bad_application, Appl}}).

make_appl(Name) when is_atom(Name) ->
    FName = atom_to_list(Name) ++ ".app",
    case code:where_is_file(FName) of
	non_existing ->
	    {error, {file:format_error(enoent), FName}};
	FullName ->
	    case prim_consult(FullName) of
		{ok, [Application]} ->
		    {ok, make_appl_i(Application)};
		{error, Reason} -> 
		    {error, {file:format_error(Reason), FName}};
                error ->
                    {error, "bad encoding"}
	    end
    end;
make_appl(Application) ->
    {ok, make_appl_i(Application)}.

prim_consult(FullName) ->
    case erl_prim_loader:read_file(FullName) of
	{ok, Bin} ->
            case file_binary_to_list(Bin) of
                {ok, String} ->
                    case erl_scan:string(String) of
                        {ok, Tokens, _EndLine} ->
                            prim_parse(Tokens, []);
                        {error, Reason, _EndLine} ->
                            {error, Reason}
                    end;
                error ->
                    error
            end;
	error ->
	    {error, enoent}
    end.

prim_parse(Tokens, Acc) ->
    case lists:splitwith(fun(T) -> element(1,T) =/= dot end, Tokens) of
	{[], []} ->
	    {ok, lists:reverse(Acc)};
	{Tokens2, [{dot,_} = Dot | Rest]} ->
	    case erl_parse:parse_term(Tokens2 ++ [Dot]) of
		{ok, Term} ->
		    prim_parse(Rest, [Term | Acc]);
		{error, _R} = Error ->
		    Error
	    end;
	{Tokens2, []} ->
	    case erl_parse:parse_term(Tokens2) of
		{ok, Term} ->
		    {ok, lists:reverse([Term | Acc])};
		{error, _R} = Error ->
		    Error
	    end
    end.

make_appl_i({application, Name, Opts}) when is_atom(Name), is_list(Opts) ->
    Descr = get_opt(description, Opts, ""),
    Id = get_opt(id, Opts, ""),
    Vsn = get_opt(vsn, Opts, ""),
    Mods = get_opt(modules, Opts, []),
    Regs = get_opt(registered, Opts, []),
    Apps = get_opt(applications, Opts, []),
    Mod =
	case get_opt(mod, Opts, []) of
	    {M,_A}=MA when is_atom(M) -> MA;
	    [] -> [];
	    Other -> throw({error, {badstartspec, Other}})
	end,
    Phases = get_opt(start_phases, Opts, undefined),
    Env = get_opt(env, Opts, []),
    case check_para(Env, Name) of
        ok -> ok;
        {error, Reason} ->
            throw({error, {invalid_options, Reason}})
    end,
    MaxP = get_opt(maxP, Opts, infinity),
    MaxT = get_opt(maxT, Opts, infinity),
    IncApps = get_opt(included_applications, Opts, []),
    OptApps = get_opt(optional_applications, Opts, []),
    {#appl_data{name = Name, regs = Regs, mod = Mod, phases = Phases,
		mods = Mods, maxP = MaxP, maxT = MaxT},
     Env, IncApps, OptApps, Descr, Id, Vsn, Apps};
make_appl_i({application, Name, Opts}) when is_list(Opts) ->
    throw({error,{invalid_name,Name}});
make_appl_i({application, _Name, Opts}) ->
    throw({error,{invalid_options, Opts}});
make_appl_i(Appl) -> throw({error, {bad_application, Appl}}).


%%-----------------------------------------------------------------
%% Merge current applications with changes.  
%%-----------------------------------------------------------------

%% do_change_apps(Applications, Config, OldAppls) -> NewAppls
%%   Applications = [{application, AppName, [{Key,Value}]}]
%%   Config = [{AppName,[{Par,Value}]} | File]
%%   OldAppls = NewAppls = [#appl{}]
do_change_apps(Applications, Config, OldAppls) ->

    %% OTP-4867
    %% Config = contents of sys.config file
    %% May now contain names of other .config files as well as
    %% configuration parameters.
    %% Therefore read and merge contents.
    {ok, SysConfig, Errors} = check_conf_sys(Config),

    %% Report errors, but do not terminate
    %% (backwards compatible behaviour)
    lists:foreach(fun({error, {SysFName, Line, Str}}) ->
			  ?LOG_ERROR("~tp: ~w: ~ts~n",[SysFName, Line, Str],
                                     #{error_logger=>#{tag=>error}})
		  end,
		  Errors),

    {map(fun(Appl) ->
		 AppName = Appl#appl.name,
		 case is_loaded_app(AppName, Applications) of
		     {true, Application} ->
			 do_change_appl(make_appl(Application),
					Appl, SysConfig);

		     %% ignored removed apps - handled elsewhere
		     false ->
			 Appl
		 end
	 end, OldAppls),
     SysConfig}.

is_loaded_app(AppName, [{application, AppName, App} | _]) ->
    {true, {application, AppName, App}};
is_loaded_app(AppName, [_ | T]) -> is_loaded_app(AppName, T);
is_loaded_app(_AppName, []) -> false.

do_change_appl({ok, {ApplData, Env, IncApps, OptApps, Descr, Id, Vsn, Apps}},
	       OldAppl, Config) ->
    AppName = OldAppl#appl.name,

    %% Merge application env with env from sys.config, if any
    ConfEnv = get_opt(AppName, Config, []),
    NewEnv1 = merge_app_env(Env, ConfEnv),

    %% Merge application env with command line arguments, if any
    CmdLineEnv = get_cmd_env(AppName),
    NewEnv2 = merge_app_env(NewEnv1, CmdLineEnv),

    %% Update ets table with new application env
    del_env(AppName),
    add_env(AppName, NewEnv2),

    OldAppl#appl{appl_data=ApplData,
		 descr=Descr,
		 id=Id,
		 vsn=Vsn,
		 inc_apps=IncApps,
                 opt_apps=OptApps,
		 apps=Apps};
do_change_appl({error, _R} = Error, _Appl, _ConfData) ->
    throw(Error).

get_opt(Key, List, Default) ->
    case lists:keyfind(Key, 1, List) of
	{_Key, Val} -> Val;
	_ -> Default
    end.

get_cmd_env(Name) ->
    case init:get_argument(Name) of
	{ok, Args} ->
	   foldl(fun(List, Res) -> conv(List) ++ Res end, [], Args);
	_ -> []
    end.

conv([Key, Val | T]) ->
    [{make_term(Key), make_term(Val)} | conv(T)];
conv(_) -> [].

make_term(Str) -> 
    case erl_scan:string(Str) of
	{ok, Tokens, _} ->		  
	    case erl_parse:parse_term(Tokens ++ [{dot, erl_anno:new(1)}]) of
		{ok, Term} ->
		    Term;
		{error, {_,M,Reason}} ->
                    handle_make_term_error(M, Reason, Str)
	    end;
	{error, {_,M,Reason}, _} ->
            handle_make_term_error(M, Reason, Str)
    end.

handle_make_term_error(Mod, Reason, Str) ->
    ?LOG_ERROR("application_controller: ~ts: ~ts~n",
               [Mod:format_error(Reason), Str],
               #{error_logger=>#{tag=>error}}),
    throw({error, {bad_environment_value, Str}}).

get_env_i(Name, #state{conf_data = ConfData}) when is_list(ConfData) ->
    case lists:keyfind(Name, 1, ConfData) of
	{_Name, Env} -> Env;
	_ -> []
    end;
get_env_i(_Name, _) -> [].

%% Merges envs for all apps.  Env2 overrides Env1
merge_env(Env1, Env2) ->
    merge_env(Env1, Env2, []).

merge_env([{App, AppEnv1} | T], Env2, Res) ->
    case get_env_key(App, Env2) of
	{value, AppEnv2, RestEnv2} ->
	    NewAppEnv = merge_app_env(AppEnv1, AppEnv2),
	    merge_env(T, RestEnv2, [{App, NewAppEnv} | Res]);
	_ ->
	    merge_env(T, Env2, [{App, AppEnv1} | Res])
    end;
merge_env([], Env2, Res) ->
    Env2 ++ Res.

%% Changes the environment for the given application
%% If there is no application, an empty one is created
change_app_env(Env, App, Fun) ->
    case get_env_key(App, Env) of
	{value, AppEnv, RestEnv} ->
	    [{App, Fun(AppEnv)} | RestEnv];
	_ ->
	    [{App, Fun([])} | Env]
    end.

%% Merges envs for an application.  Env2 overrides Env1
merge_app_env(Env1, Env2) ->
    merge_app_env(Env1, Env2, []).

merge_app_env([{Key, Val} | T], Env2, Res) ->
    case get_env_key(Key, Env2) of
	{value, NewVal, RestEnv} ->
	    merge_app_env(T, RestEnv, [{Key, NewVal}|Res]);
	_ ->
	    merge_app_env(T, Env2, [{Key, Val} | Res])
    end;
merge_app_env([], Env2, Res) ->
    Env2 ++ Res.

get_env_key(Key, Env) -> get_env_key(Env, Key, []).
get_env_key([{Key, Val} | T], Key, Res) ->
    {value, Val, T ++ Res};
get_env_key([H | T], Key, Res) ->
    get_env_key(T, Key, [H | Res]);
get_env_key([], _Key, Res) -> Res.

add_env(Name, Env) ->
    foreach(fun({Key, Value}) ->
			  ets:insert(ac_tab, {{env, Name, Key}, Value})
		  end,
		  Env).

del_env(Name) ->
    ets:match_delete(ac_tab, {{env, Name, '_'}, '_'}).

check_user() ->
    case whereis(user) of
	User when is_pid(User) -> group_leader(User, self());
	_ -> ok
    end.


%%-----------------------------------------------------------------
%% Prepare for a release upgrade by reading all the environment variables.
%%-----------------------------------------------------------------
do_prep_config_change(Apps) ->
    do_prep_config_change(Apps, []).

do_prep_config_change([], EnvBefore) ->
    EnvBefore;
do_prep_config_change([{App, _Id} | Apps], EnvBefore) ->
    Env = application:get_all_env(App),
    do_prep_config_change(Apps, [{App, Env} | EnvBefore]).
    


%%-----------------------------------------------------------------
%% Inform all running applications about the changed configuration.
%%-----------------------------------------------------------------
do_config_change(Apps, EnvBefore) ->
    do_config_change(Apps, EnvBefore, []).

do_config_change([], _EnvBefore, []) ->
    ok;
do_config_change([], _EnvBefore, Errors) ->
    {error, Errors};
do_config_change([{App, _Id} | Apps], EnvBefore, Errors) ->
    AppEnvNow = lists:sort(application:get_all_env(App)),
    AppEnvBefore = case lists:keyfind(App, 1, EnvBefore) of
		       false ->
			   [];
		       {App, AppEnvBeforeT} ->
			   lists:sort(AppEnvBeforeT)
		   end,
    Res = 
	case AppEnvNow of
	    AppEnvBefore ->
		ok;
	    _ ->
		case do_config_diff(AppEnvNow, AppEnvBefore) of
		    {[], [], []} ->
			ok;
		    {Changed, New, Removed} ->
			case application:get_key(App, mod) of
			    {ok, {Mod, _Para}} ->
				case catch Mod:config_change(Changed, New, 
							     Removed) of
				    ok ->
					ok;
				    %% It is not considered as an error
				    %% if the cb-function is not defined 
				    {'EXIT', {undef, _}} ->
					ok;
				    {error, _} = Error ->
					Error;
				    Else ->
					{error, Else}
				end;
			    {ok, []} ->
				{error, {module_not_defined, App}};
			    undefined ->
				{error, {application_not_found, App}}
			end
		end
	end,
    
    case Res of
	ok ->
	    do_config_change(Apps, EnvBefore, Errors);
	{error, NewError} ->
	    do_config_change(Apps, EnvBefore,[NewError | Errors])
    end.


%%-----------------------------------------------------------------
%% Check if the configuration is changed in anyway.
%%-----------------------------------------------------------------
do_config_diff(AppEnvNow, AppEnvBefore) ->
    do_config_diff(AppEnvNow, AppEnvBefore, {[], []}).

do_config_diff([], AppEnvBefore, {Changed, New}) ->
    Removed = lists:foldl(fun({Env, _Value}, Acc) -> [Env | Acc] end, [], AppEnvBefore),
    {Changed, New, Removed};
do_config_diff(AppEnvNow, [], {Changed, New}) ->
    {Changed, AppEnvNow++New, []};
do_config_diff([{Env, Value} | AppEnvNow], AppEnvBefore, {Changed, New}) ->
    case lists:keyfind(Env, 1, AppEnvBefore) of
	{Env, Value} ->
	    do_config_diff(AppEnvNow, lists:keydelete(Env,1,AppEnvBefore), {Changed, New});
	{Env, _OtherValue} ->
	    do_config_diff(AppEnvNow, lists:keydelete(Env,1,AppEnvBefore), 
			   {[{Env, Value} | Changed], New});
	false ->
	    do_config_diff(AppEnvNow, AppEnvBefore, {Changed, [{Env, Value}|New]})
    end.


conf_param_to_conf({config, FileName}) ->
    BFName = filename:basename(FileName,".config"),
    FName = filename:join(filename:dirname(FileName),
                          BFName ++ ".config"),
    case load_file(FName) of
        {ok, NewEnv} ->
            %% OTP-4867 sys.config may now contain names of other
            %% .config files as well as configuration parameters.
            %% Therefore read and merge contents.
            if
                BFName =:= "sys" ->
                    DName = filename:dirname(FName),
                    {ok, SysEnv, Errors} =
                        check_conf_sys(NewEnv, [], [], DName),
                    %% Report first error, if any, and terminate
                    %% (backwards compatible behaviour)
                    case Errors of
                        [] ->
                            SysEnv;
                        [{error, {SysFName, Line, Str}}|_] ->
                            throw({error, {SysFName, Line, Str}})
                    end;
                true ->
                    NewEnv
            end;
        {error, {Line, _Mod, Str}} ->
            throw({error, {FName, Line, Str}})
    end;
conf_param_to_conf({configfd, FileDescStrP}) ->
    FileDescStr = unicode:characters_to_nfc_list(FileDescStrP),
    IsDigit = fun(C) -> lists:member(C, "0123456789") end,
    {FdString, FileDescType} = lists:splitwith(IsDigit, FileDescStr),
    FileDesc =
        try list_to_integer(FdString)
        catch
            error:badarg ->
                throw({error, {file_descriptor,
                               FileDescStr,
                               invalid_file_desc,
                               "The given file descriptor has incorrect format. "
                               "The format should be \"FileDescId[.FileType]\". "
                               "Examples: 3 or 3.config"}})
        end,
    case lists:member(FileDescType, [".config", ""])  of
        false ->
            throw({error, {file_descriptor,
                           FileDescStr,
                           invalid_file_desc,
                           io_lib:format("Cannot parse file descriptor of type: ~ts",
                                         [FileDescType])}});
        true -> ok
    end,
    case load_file_descriptor(FileDesc) of
        {ok, NewEnv} ->
            %% Load config parameters from included config files
            BootScript = init:script_name(),
            DName = filename:dirname(BootScript),
            {ok, SysEnv, Errors} =
                check_conf_sys(NewEnv, [], [], DName),
            case Errors of
                [] ->
                    SysEnv;
                [{error, {SysFName, Line, Str}}|_] ->
                    throw({error, {SysFName, Line, Str}})
            end;
        {error, {Line, _Mod, Str}} ->
            throw({error, {file_descriptor,
                           FileDescStr,
                           Line,
                           Str}})
    end.

config_param_to_list({Type, [First | _] = ConfigVals})
  when
      is_list(First),
      Type =:= config orelse Type =:= configfd ->
    [{Type, Val} || Val <- ConfigVals];
config_param_to_list({config, Val}) ->
    [{config, Val}];
config_param_to_list({configfd, Val}) ->
    [{configfd, Val}];
config_param_to_list(_) ->
    [].



%%-----------------------------------------------------------------
%% Read the .config files.
%%-----------------------------------------------------------------
check_conf() ->
    ConfigParameters =
        lists:flatmap(
          fun config_param_to_list/1,
          init:get_arguments()),
    MergedConf =
        lists:foldl(fun (ConfigParameter, Env) ->
                            NewEnv = conf_param_to_conf(ConfigParameter),
                            merge_env(Env, NewEnv)
                    end,
                    [],
                    ConfigParameters),
    {ok, MergedConf}.


check_conf_sys(Env) ->
    check_conf_sys(Env, [], [], []).

check_conf_sys([File|T], SysEnv, Errors, DName) when is_list(File),is_list(DName) ->
    BFName = filename:basename(File, ".config"),
    FName = filename:join(filename:dirname(File), BFName ++ ".config"),
    LName = case filename:pathtype(FName) of
               relative when (DName =/= []) ->
                  % Check if relative to sys.config dir otherwise use legacy mode,
                  % i.e relative to cwd.
                  RName = filename:join(DName, FName),
                  case erl_prim_loader:read_file_info(RName) of
                     {ok, _} -> RName ;
                     error   -> FName
                  end;
		_          -> FName
	    end,
    case load_file(LName) of
	{ok, NewEnv} ->
	    check_conf_sys(T, merge_env(SysEnv, NewEnv), Errors, DName);
	{error, {Line, _Mod, Str}} ->
	    check_conf_sys(T, SysEnv, [{error, {LName, Line, Str}}|Errors], DName)
    end;
check_conf_sys([Tuple|T], SysEnv, Errors, DName) ->
    check_conf_sys(T, merge_env(SysEnv, [Tuple]), Errors, DName);
check_conf_sys([], SysEnv, Errors, _) ->
    {ok, SysEnv, lists:reverse(Errors)}.

load_file(File) ->
    %% We can't use file:consult/1 here. Too bad.
    case erl_prim_loader:read_file(File) of
	{ok, Bin} ->
	    %% Make sure that there is some whitespace at the end of the string
	    %% (so that reading a file with no NL following the "." will work).
            case file_binary_to_list(Bin) of
                {ok, String} ->
                    scan_file(String ++ " ");
                error ->
                    {error, {none, scan_file, "bad encoding"}}
            end;
	error ->
	    {error, {none, open_file, "configuration file not found"}}
    end.

load_file_descriptor(FileDescriptorId) ->
    %% We do not want to read from the same file descriptor again if
    %% init:restart is called so we use the old config obtained from
    %% the file descriptor
    case init:get_configfd(FileDescriptorId) of
        none ->
            WarningIntervalMs = 20000, % 20 seconds
            MaxConfSizeBytes = 1024*1024*128, % 134 MB
            case read_fd_until_end_and_close(FileDescriptorId,
                                             WarningIntervalMs,
                                             MaxConfSizeBytes) of
                {ok, Bin} ->
                    %% Make sure that there is some whitespace at the end of the string
                    %% (so that reading a file with no NL following the "." will work).
                    case file_binary_to_list(Bin) of
                        {ok, String} ->
                            case scan_file(String ++ " ") of
                                {ok, Config} ->
                                    init:set_configfd(FileDescriptorId, Config),
                                    {ok, Config};
                                Error ->
                                    Error
                            end;
                        error ->
                            {error, {none, scan_file, "bad encoding"}}
                    end;
                {error, Reason} ->
                    {error, {none,
                             read_from_file_descriptor,
                             io_lib:format("Could not read from file descriptor: ~s", [Reason])}}
            end;
        Config -> {ok, Config}
    end.

scan_file(Str) ->
    case erl_scan:tokens([], Str, 1) of
	{done, {ok, Tokens, _}, Left} ->
	    case erl_parse:parse_term(Tokens) of
		{ok,L}=Res when is_list(L) ->
		    case only_ws(Left) of
			true ->
			    Res;
			false ->
			    %% There was trailing garbage found after the list.
			    config_error()
		    end;
		{ok,_} ->
		    %% Parsing succeeded but the result is not a list.
		    config_error();
		Error ->
		    Error
	    end;
	{done, Result, _} ->
	    {error, {none, parse_file, tuple_to_list(Result)}};
	{more, _} ->
	    {error, {none, load_file, "no ending <dot> found"}}
    end.

read_fd_until_end_and_close(FileDescriptorId,
                            TimeBetweenWarningsMilliseconds,
                            MaxSizeBytes) ->
    ReadLimit = 1024,
    Read =
        fun Read(Ref, _Fd, _Data, DataSize) when DataSize > MaxSizeBytes ->
                Reason = io_lib:format("Max size ~w bytes exceeded",
                                       [MaxSizeBytes]),
                {Ref, error, Reason};
            Read(Ref, Fd, Data, DataSize) ->
                case file:read(Fd, ReadLimit) of
                    eof ->
                        {Ref, ok, erlang:iolist_to_binary(Data)};
                    {ok, Bin} ->
                        Read(Ref, Fd, [Data, Bin], DataSize + byte_size(Bin));
                    {error, Reason} ->
                        {Ref, error, Reason}
                end
        end,
    Receiver = self(),
    Ref = make_ref(),
    Reader =
        spawn(
          fun() ->
                  case prim_file:file_desc_to_ref(FileDescriptorId, [read]) of
                      {ok, Fd} ->
                          Res = Read(Ref, Fd, [], 0),
                          prim_file:close(Fd),
                          Receiver ! Res;
                      {error, _} ->
                          Receiver ! {Ref,
                                      error,
                                      "Invalid file descriptor"}
                  end
          end),
    Reader ! {reader_ref, Ref},
    (fun GetResult() ->
            receive
                {Ref, ok, Data} ->
                    {ok, erlang:iolist_to_binary(Data)};
                {Ref, error, Reason} ->
                    {error, Reason}
            after TimeBetweenWarningsMilliseconds ->
                    Msg =
                        io_lib:format(
                          "Slow -configfd file descriptor ~p. "
                          "The system will continue to read from "
                          "the file descriptor and will be blocked "
                          "until end of file is received.",
                          [FileDescriptorId]),
                    ?LOG_WARNING(Msg),
                    GetResult()
            end
    end)().

only_ws([C|Cs]) when C =< $\s -> only_ws(Cs);
only_ws([$%|Cs]) -> only_ws(strip_comment(Cs));   % handle comment
only_ws([_|_]) -> false;
only_ws([]) -> true.
    
strip_comment([$\n|Cs]) -> Cs;
strip_comment([_|Cs]) -> strip_comment(Cs);
strip_comment([]) -> [].

config_error() ->
    {error,
     {none, load_file,
      "configuration file must contain ONE list ended by <dot>"}}.

%%-----------------------------------------------------------------
%% Info messages sent to logger
%%-----------------------------------------------------------------
info_started(Name, Node) ->
    ?LOG_INFO(#{label=>{application_controller,progress},
                report=>[{application, Name},
                         {started_at, Node}]},
              #{domain=>[otp,sasl],
                report_cb=>fun application_controller:format_log/2,
                logger_formatter=>#{title=>"PROGRESS REPORT"},
                error_logger=>#{tag=>info_report,
                                type=>progress,
                                report_cb=>
                                    fun application_controller:format_log/1}}).

info_exited(Name, Reason, Type) ->
    ?LOG_NOTICE(#{label=>{application_controller,exit},
                  report=>[{application, Name},
                           {exited, Reason},
                           {type, Type}]},
                #{domain=>[otp],
                  report_cb=>fun application_controller:format_log/2,
                error_logger=>#{tag=>info_report,
                                type=>std_info,
                                report_cb=>
                                    fun application_controller:format_log/1}}).

%% format_log/1 is the report callback used by Logger handler
%% error_logger only. It is kept for backwards compatibility with
%% legacy error_logger event handlers. This function must always
%% return {Format,Args} compatible with the arguments in this module's
%% calls to error_logger prior to OTP-21.0.
format_log(LogReport) ->
    Depth = error_logger:get_format_depth(),
    FormatOpts = #{chars_limit => unlimited,
                   depth => Depth,
                   single_line => false,
                   encoding => utf8},
    format_log_multi(limit_report(LogReport, Depth), FormatOpts).

limit_report(LogReport, unlimited) ->
    LogReport;
limit_report(#{label:={application_controller,progress},
               report:=[{application,_}=Application,
                        {started_at,Node}]}=LogReport,
             Depth) ->
    LogReport#{report=>[Application,
                        {started_at,io_lib:limit_term(Node, Depth)}]};
limit_report(#{label:={application_controller,exit},
               report:=[{application,_}=Application,
                        {exited,Reason},{type,Type}]}=LogReport,
             Depth) ->
    LogReport#{report=>[Application,
                        {exited,io_lib:limit_term(Reason, Depth)},
                        {type,io_lib:limit_term(Type, Depth)}]}.

%% format_log/2 is the report callback for any Logger handler, except
%% error_logger.
format_log(Report, FormatOpts0) ->
    Default = #{chars_limit => unlimited,
                depth => unlimited,
                single_line => false,
                encoding => utf8},
    FormatOpts = maps:merge(Default, FormatOpts0),
    IoOpts =
        case FormatOpts of
            #{chars_limit:=unlimited} ->
                [];
            #{chars_limit:=Limit} ->
                [{chars_limit,Limit}]
        end,
    {Format,Args} = format_log_single(Report, FormatOpts),
    io_lib:format(Format, Args, IoOpts).

format_log_single(#{label:={application_controller,progress},
                    report:=[{application,Name},{started_at,Node}]},
                  #{single_line:=true,depth:=Depth}=FormatOpts) ->
    P = p(FormatOpts),
    Format = "Application: "++P++". Started at: "++P++".",
    Args =
        case Depth of
            unlimited ->
                [Name,Node];
            _ ->
                [Name,Depth,Node,Depth]
        end,
    {Format,Args};
format_log_single(#{label:={application_controller,exit},
                    report:=[{application,Name},
                             {exited,Reason},
                             {type,Type}]},
                  #{single_line:=true,depth:=Depth}=FormatOpts) ->
    P = p(FormatOpts),
    Format = lists:append(["Application: ",P,". Exited: ",P,
                            ". Type: ",P,"."]),
    Args =
        case Depth of
            unlimited ->
                [Name,Reason,Type];
            _ ->
                [Name,Depth,Reason,Depth,Type,Depth]
        end,
    {Format,Args};
format_log_single(Report,FormatOpts) ->
    format_log_multi(Report,FormatOpts).

format_log_multi(#{label:={application_controller,progress},
                   report:=[{application,Name},
                            {started_at,Node}]},
                 #{depth:=Depth}=FormatOpts) ->
    P = p(FormatOpts),
    Format =
        lists:append(
          ["    application: ",P,"~n",
           "    started_at: ",P,"~n"]),
    Args =
        case Depth of
            unlimited ->
                [Name,Node];
            _ ->
                [Name,Depth,Node,Depth]
        end,
    {Format,Args};
format_log_multi(#{label:={application_controller,exit},
                   report:=[{application,Name},
                            {exited,Reason},
                            {type,Type}]},
                 #{depth:=Depth}=FormatOpts) ->
    P = p(FormatOpts),
    Format =
        lists:append(
          ["    application: ",P,"~n",
           "    exited: ",P,"~n",
           "    type: ",P,"~n"]),
    Args =
        case Depth of
            unlimited ->
                [Name,Reason,Type];
            _ ->
                [Name,Depth,Reason,Depth,Type,Depth]
        end,
    {Format,Args}.

p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
    "~"++single(Single)++mod(Enc)++p(Depth);
p(unlimited) ->
    "p";
p(_Depth) ->
    "P".

single(true) -> "0";
single(false) -> "".

mod(latin1) -> "";
mod(_) -> "t".

%%-----------------------------------------------------------------
%% Reply to all processes waiting this application to be started.  
%%-----------------------------------------------------------------
reply_to_requester(AppName, Start_req, Res) ->
    R = case Res of
	    {ok, _Id} ->
		ok;
	    {info, Reason} ->
		{error, Reason};
	    Error ->
		Error
	end,

    lists:foldl(fun(Sp, AccIn) ->
			case Sp of
			    {AppName, From} ->
				reply(From, R),
				AccIn;
			    _ ->
				[Sp | AccIn]
			end
		end,
		[],
		Start_req).
    

%%-----------------------------------------------------------------
%% Update the environment variable permission for an application.  
%%-----------------------------------------------------------------
update_permissions(AppName, Bool) ->
    T = {env, kernel, permissions},
    case ets:lookup(ac_tab, T) of
	[] ->
	    ets:insert(ac_tab, {T, [{AppName, Bool}]});
	[{_, Perm}] ->
	    Perm2 = lists:keydelete(AppName, 1, Perm),
	    ets:insert(ac_tab, {T, [{AppName, Bool}|Perm2]})
    end.

%%-----------------------------------------------------------------
%% These functions are only to be used from testsuites.  
%%-----------------------------------------------------------------
test_change_apps(Apps, Conf) ->
    Res = test_make_apps(Apps, []),
    test_do_change_appl(Apps, Conf, Res).

test_do_change_appl([], _, _) ->
    ok;
test_do_change_appl([A|Apps], [], [R|Res]) ->
    _ = do_change_appl(R, #appl{name = A}, []),
    test_do_change_appl(Apps, [], Res);
test_do_change_appl([A|Apps], [C|Conf], [R|Res]) ->
    _ = do_change_appl(R, #appl{name = A}, C),
    test_do_change_appl(Apps, Conf, Res).

test_make_apps([], Res) ->
    lists:reverse(Res);
test_make_apps([A|Apps], Res) ->
    test_make_apps(Apps, [make_appl(A) | Res]).

file_binary_to_list(Bin) ->
    Enc = case epp:read_encoding_from_binary(Bin) of
              none -> epp:default_encoding();
              Encoding -> Encoding
          end,
    case catch unicode:characters_to_list(Bin, Enc) of
        String when is_list(String) ->
            {ok, String};
        _ ->
            error
    end.

%%-----------------------------------------------------------------
%% String conversion
%% Exit reason needs to be a printable string
%% (and of length <200, but init now does the chopping).
%%-----------------------------------------------------------------

-spec to_string(term()) -> string().

to_string(Term) ->
    case io_lib:printable_list(Term) of
	true ->
	    Term;
	false ->
	    lists:flatten(io_lib:format("~0p", [Term]))
    end.
