%% 
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1996-2013. All Rights Reserved.
%% 
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% %CopyrightEnd%
%%
-module(os_sup).
-behaviour(gen_server).

%% API
-export([start_link/1, start/0, stop/0]).
-export([error_report/2]).
-export([enable/0, enable/2, disable/0, disable/2]).
-export([param_type/2, param_default/1]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

-record(state, {port, mfa, config, path, conf}).

%%----------------------------------------------------------------------
%% API
%%----------------------------------------------------------------------

start_link({win32, _OSname}) ->
    Identifier = os_sup,
    MFA = os_mon:get_env(os_sup, os_sup_mfa),
    gen_server:start_link({local, os_sup_server}, nteventlog,
			  [Identifier, MFA], []);
start_link(_OS) ->
    gen_server:start_link({local, os_sup_server}, os_sup, [], []).

start() -> % for testing
    gen_server:start({local, os_sup_server}, os_sup, [], []).

stop() ->
    gen_server:call(os_sup_server, stop).

error_report(LogData, Tag) ->
    error_logger:error_report(Tag, LogData).

enable() ->
    command(enable).
enable(Path, Conf) ->
    command(enable, Path, Conf).

disable() ->
    command(disable).
disable(Path, Conf) ->
    command(disable, Path, Conf).

param_type(os_sup_errortag, Val) when is_atom(Val) -> true;
param_type(os_sup_own, Val) -> io_lib:printable_list(Val);
param_type(os_sup_syslogconf, Val) -> io_lib:printable_list(Val);
param_type(os_sup_enable, Val) when Val==true; Val==false -> true;
param_type(os_sup_mfa, {Mod,Func,Args}) when is_atom(Mod),
					     is_atom(Func),
					     is_list(Args) -> true;
param_type(_Param, _Val) -> false.

param_default(os_sup_errortag) -> std_error;
param_default(os_sup_own) -> "/etc";
param_default(os_sup_syslogconf) -> "/etc/syslog.conf";
param_default(os_sup_enable) -> true;
param_default(os_sup_mfa) -> {os_sup, error_report, [std_error]}.

%%----------------------------------------------------------------------
%% gen_server callbacks
%%----------------------------------------------------------------------

init([]) ->
    process_flag(trap_exit, true),
    process_flag(priority, low),

    case os:type() of
	{unix, sunos} ->
	    init2();
	OS -> {stop, {unsupported_os, OS}}
    end.

init2() -> % Enable service if configured to do so
    ConfigP = os_mon:get_env(os_sup, os_sup_enable),
    case ConfigP of
	true -> % ..yes -- do enable
	    Path = os_mon:get_env(os_sup, os_sup_own),
	    Conf = os_mon:get_env(os_sup, os_sup_syslogconf),
	    case enable(Path, Conf) of
		ok ->
		    init3(#state{config=ConfigP, path=Path, conf=Conf});
		{error, Error} ->
		    {stop, {mod_syslog, Error}}
	    end;
	false -> % ..no -- skip directly to init3/1
	    init3(#state{config=ConfigP})
    end.

init3(State0) ->
    Port = start_portprogram(),

    %% Read the values of some configuration parameters
    MFA = case os_mon:get_env(os_sup, os_sup_mfa) of
	      {os_sup, error_report, _} ->
		  Tag = os_mon:get_env(os_sup, os_sup_errortag),
		  {os_sup, error_report, [Tag]};
	      MFA0 ->
		  MFA0
	  end,

    {ok, State0#state{port=Port, mfa=MFA}}.

handle_call(stop, _From, State) ->
    {stop, normal, ok, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info({_Port, {data, Data}}, #state{mfa={M,F,A}} = State) ->
    apply(M, F, [Data | A]),
    {noreply, State};
handle_info({'EXIT', _Port, Reason}, State) ->
    {stop, {port_died, Reason}, State#state{port=not_used}};
handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, #state{port=Port} = State) ->
    case State#state.config of
	true when is_port(Port) ->
	    Port ! {self(), {command, "only_stdin"}},
	    Res = disable(State#state.path, State#state.conf),
	    port_close(Port),
	    if
		Res/="0" -> exit({mod_syslog, Res});
		true -> ok
	    end;
	true ->
	    Res = disable(State#state.path, State#state.conf),
	    if
		Res/="0" -> exit({mod_syslog, Res});
		true -> ok
	    end;
	false when is_port(Port) ->
	    Port ! {self(), {command, "only_stdin"}},
	    port_close(Port);
	false ->
	    ok
    end.

%% os_mon-2.0
%% For live downgrade to/upgrade from os_mon-1.8[.1]
code_change(Vsn, PrevState, "1.8") ->
    case Vsn of

	%% Downgrade from this version
	{down, _Vsn} ->

	    %% Find out the error tag used
	    {DefM, DefF, _} = param_default(os_sup_mfa),
	    Tag = case PrevState#state.mfa of

		      %% Default callback function is used, then use
		      %% the corresponding tag
		      {DefM, DefF, [Tag0]} ->
			  Tag0;

		      %% Default callback function is *not* used
		      %% (before the downgrade, that is)
		      %% -- check the configuration parameter
		      _ ->
			  case application:get_env(os_mon,
						   os_sup_errortag) of
			      {ok, Tag1} ->
				  Tag1;

			      %% (actually, if it has no value,
			      %%  the process should terminate
			      %%  according to 1.8.1 version, but that
			      %%  seems too harsh here)
			      _ ->
				  std_error
			  end
		  end,
		      
	    %% Downgrade to old state record
	    State = {state, PrevState#state.port, Tag},
	    {ok, State};

	%% Upgrade to this version
	_Vsn ->

	    {state, Port, Tag} = PrevState,

	    {DefM, DefF, _} = param_default(os_sup_mfa),
	    MFA  = {DefM, DefF, [Tag]},

	    %% We can safely assume the following configuration
	    %% parameters are defined, otherwise os_sup would never had
	    %% started in the first place.
	    %% (We can *not* safely assume they haven't been changed,
	    %%  but that's a weakness inherited from the 1.8.1 version)
	    Path = application:get_env(os_mon, os_sup_own),
	    Conf = application:get_env(os_mon, os_sup_syslogconf),

	    %% Upgrade to this state record
	    State = #state{port=Port, mfa=MFA, config=true,
			   path=Path, conf=Conf},
	    {ok, State}
    end;
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%----------------------------------------------------------------------
%% Internal functions
%%----------------------------------------------------------------------

start_portprogram() ->
    OwnPath = os_mon:get_env(os_sup, os_sup_own),
    Command =
	"\"" ++ filename:join([code:priv_dir(os_mon), "bin", "ferrule"]) ++
	"\" " ++ OwnPath,
    open_port({spawn, Command}, [{packet, 2}]).

%% os:cmd(cmd_str(enable)) should be done BEFORE starting os_sup
%% os:cmd(cmd_str(disable)) should be done AFTER os_sup is terminated
%% Both commands return "0" if successful
command(Mode) ->
    command(Mode, "/etc", "/etc/syslog.conf").
command(Mode, Path, Conf) ->
    case os:cmd(cmd_str(Mode, Path, Conf)) of
	"0" ->
	    ok;
	Error ->
	    {error, Error}
    end.

cmd_str(Mode, Path, Conf) ->
    %% modpgm modesw ownpath syslogconf
    PrivDir = code:priv_dir(os_mon),
    ModeSw =
	case Mode of
	    enable ->
		" otp ";
	    disable ->
		" nootp "
	end,
    PrivDir ++ "/bin/mod_syslog" ++ ModeSw ++ Path ++ " " ++ Conf.
