1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2016. 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(os_sup).
21-behaviour(gen_server).
22
23%% API
24-export([start_link/1, start/0, stop/0]).
25-export([error_report/2]).
26-export([enable/0, enable/2, disable/0, disable/2]).
27-export([param_type/2, param_default/1]).
28
29%% gen_server callbacks
30-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
31	 terminate/2, code_change/3]).
32
33-record(state, {port, mfa, config, path, conf}).
34
35%%----------------------------------------------------------------------
36%% API
37%%----------------------------------------------------------------------
38
39start_link({win32, _OSname}) ->
40    Identifier = os_sup,
41    MFA = os_mon:get_env(os_sup, os_sup_mfa),
42    gen_server:start_link({local, os_sup_server}, nteventlog,
43			  [Identifier, MFA], []);
44start_link(_OS) ->
45    gen_server:start_link({local, os_sup_server}, os_sup, [], []).
46
47start() -> % for testing
48    gen_server:start({local, os_sup_server}, os_sup, [], []).
49
50stop() ->
51    gen_server:call(os_sup_server, stop).
52
53error_report(LogData, Tag) ->
54    error_logger:error_report(Tag, LogData).
55
56enable() ->
57    command(enable).
58enable(Path, Conf) ->
59    command(enable, Path, Conf).
60
61disable() ->
62    command(disable).
63disable(Path, Conf) ->
64    command(disable, Path, Conf).
65
66param_type(os_sup_errortag, Val) when is_atom(Val) -> true;
67param_type(os_sup_own, Val) -> io_lib:printable_list(Val);
68param_type(os_sup_syslogconf, Val) -> io_lib:printable_list(Val);
69param_type(os_sup_enable, Val) when Val==true; Val==false -> true;
70param_type(os_sup_mfa, {Mod,Func,Args}) when is_atom(Mod),
71					     is_atom(Func),
72					     is_list(Args) -> true;
73param_type(_Param, _Val) -> false.
74
75param_default(os_sup_errortag) -> std_error;
76param_default(os_sup_own) -> "/etc";
77param_default(os_sup_syslogconf) -> "/etc/syslog.conf";
78param_default(os_sup_enable) -> true;
79param_default(os_sup_mfa) -> {os_sup, error_report, [std_error]}.
80
81%%----------------------------------------------------------------------
82%% gen_server callbacks
83%%----------------------------------------------------------------------
84
85init([]) ->
86    process_flag(trap_exit, true),
87    process_flag(priority, low),
88
89    case os:type() of
90	{unix, sunos} ->
91	    init2();
92	OS -> {stop, {unsupported_os, OS}}
93    end.
94
95init2() -> % Enable service if configured to do so
96    ConfigP = os_mon:get_env(os_sup, os_sup_enable),
97    case ConfigP of
98	true -> % ..yes -- do enable
99	    Path = os_mon:get_env(os_sup, os_sup_own),
100	    Conf = os_mon:get_env(os_sup, os_sup_syslogconf),
101	    case enable(Path, Conf) of
102		ok ->
103		    init3(#state{config=ConfigP, path=Path, conf=Conf});
104		{error, Error} ->
105		    {stop, {mod_syslog, Error}}
106	    end;
107	false -> % ..no -- skip directly to init3/1
108	    init3(#state{config=ConfigP})
109    end.
110
111init3(State0) ->
112    Port = start_portprogram(),
113
114    %% Read the values of some configuration parameters
115    MFA = case os_mon:get_env(os_sup, os_sup_mfa) of
116	      {os_sup, error_report, _} ->
117		  Tag = os_mon:get_env(os_sup, os_sup_errortag),
118		  {os_sup, error_report, [Tag]};
119	      MFA0 ->
120		  MFA0
121	  end,
122
123    {ok, State0#state{port=Port, mfa=MFA}}.
124
125handle_call(stop, _From, State) ->
126    {stop, normal, ok, State}.
127
128handle_cast(_Msg, State) ->
129    {noreply, State}.
130
131handle_info({_Port, {data, Data}}, #state{mfa={M,F,A}} = State) ->
132    apply(M, F, [Data | A]),
133    {noreply, State};
134handle_info({'EXIT', _Port, Reason}, State) ->
135    {stop, {port_died, Reason}, State#state{port=not_used}};
136handle_info(_Info, State) ->
137    {noreply, State}.
138
139terminate(_Reason, #state{port=Port} = State) ->
140    case State#state.config of
141	true when is_port(Port) ->
142	    Port ! {self(), {command, "only_stdin"}},
143	    Res = disable(State#state.path, State#state.conf),
144	    port_close(Port),
145	    if
146		Res/="0" -> exit({mod_syslog, Res});
147		true -> ok
148	    end;
149	true ->
150	    Res = disable(State#state.path, State#state.conf),
151	    if
152		Res/="0" -> exit({mod_syslog, Res});
153		true -> ok
154	    end;
155	false when is_port(Port) ->
156	    Port ! {self(), {command, "only_stdin"}},
157	    port_close(Port);
158	false ->
159	    ok
160    end.
161
162%% os_mon-2.0
163%% For live downgrade to/upgrade from os_mon-1.8[.1]
164code_change(Vsn, PrevState, "1.8") ->
165    case Vsn of
166
167	%% Downgrade from this version
168	{down, _Vsn} ->
169
170	    %% Find out the error tag used
171	    {DefM, DefF, _} = param_default(os_sup_mfa),
172	    Tag = case PrevState#state.mfa of
173
174		      %% Default callback function is used, then use
175		      %% the corresponding tag
176		      {DefM, DefF, [Tag0]} ->
177			  Tag0;
178
179		      %% Default callback function is *not* used
180		      %% (before the downgrade, that is)
181		      %% -- check the configuration parameter
182		      _ ->
183			  case application:get_env(os_mon,
184						   os_sup_errortag) of
185			      {ok, Tag1} ->
186				  Tag1;
187
188			      %% (actually, if it has no value,
189			      %%  the process should terminate
190			      %%  according to 1.8.1 version, but that
191			      %%  seems too harsh here)
192			      _ ->
193				  std_error
194			  end
195		  end,
196
197	    %% Downgrade to old state record
198	    State = {state, PrevState#state.port, Tag},
199	    {ok, State};
200
201	%% Upgrade to this version
202	_Vsn ->
203
204	    {state, Port, Tag} = PrevState,
205
206	    {DefM, DefF, _} = param_default(os_sup_mfa),
207	    MFA  = {DefM, DefF, [Tag]},
208
209	    %% We can safely assume the following configuration
210	    %% parameters are defined, otherwise os_sup would never had
211	    %% started in the first place.
212	    %% (We can *not* safely assume they haven't been changed,
213	    %%  but that's a weakness inherited from the 1.8.1 version)
214	    Path = application:get_env(os_mon, os_sup_own),
215	    Conf = application:get_env(os_mon, os_sup_syslogconf),
216
217	    %% Upgrade to this state record
218	    State = #state{port=Port, mfa=MFA, config=true,
219			   path=Path, conf=Conf},
220	    {ok, State}
221    end;
222code_change(_OldVsn, State, _Extra) ->
223    {ok, State}.
224
225%%----------------------------------------------------------------------
226%% Internal functions
227%%----------------------------------------------------------------------
228
229start_portprogram() ->
230    OwnPath = os_mon:get_env(os_sup, os_sup_own),
231    Command =
232	"\"" ++ filename:join([code:priv_dir(os_mon), "bin", "ferrule"]) ++
233	"\" " ++ OwnPath,
234    open_port({spawn, Command}, [{packet, 2}]).
235
236%% os:cmd(cmd_str(enable)) should be done BEFORE starting os_sup
237%% os:cmd(cmd_str(disable)) should be done AFTER os_sup is terminated
238%% Both commands return "0" if successful
239command(Mode) ->
240    command(Mode, "/etc", "/etc/syslog.conf").
241command(Mode, Path, Conf) ->
242    case os:cmd(cmd_str(Mode, Path, Conf)) of
243	"0" ->
244	    ok;
245	Error ->
246	    {error, Error}
247    end.
248
249cmd_str(Mode, Path, Conf) ->
250    %% modpgm modesw ownpath syslogconf
251    PrivDir = code:priv_dir(os_mon),
252    ModeSw =
253	case Mode of
254	    enable ->
255		" otp ";
256	    disable ->
257		" nootp "
258	end,
259    PrivDir ++ "/bin/mod_syslog" ++ ModeSw ++ Path ++ " " ++ Conf.
260