1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1998-2017. 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(erlsrv).
21
22%% Purpose : Control the external erlsrv program.
23
24%%-compile(export_all).
25-export([get_all_services/0,get_service/1,get_service/2,store_service/1,
26	 store_service/2,
27	 new_service/3, new_service/4, disable_service/2,
28	 enable_service/2, disable_service/1, enable_service/1,
29	 remove_service/1, erlsrv/1, rename_service/2,
30	 rename_service/3]).
31
32erlsrv(EVer) ->
33    Root = code:root_dir(),
34    filename:join([Root, "erts-" ++ EVer, "bin", "erlsrv.exe"]).
35
36current_version() ->
37    hd(string:lexemes(erlang:system_info(version),"_ ")).
38
39%%% Returns {ok, Output} | failed | {error, Reason}
40run_erlsrv(Command) ->
41    run_erlsrv(current_version(),Command).
42run_erlsrv(EVer, Command) ->
43    case catch(open_port({spawn, "\"" ++ erlsrv(EVer) ++ "\" " ++ Command},
44			 [{line,1000}, in, eof])) of
45	{'EXIT',{Reason,_}} ->
46	    {port_error, Reason};
47	Port ->
48	    case read_all_data(Port) of
49		[] ->
50		    failed;
51		 X ->
52		    {ok, X}
53	    end
54    end.
55
56run_erlsrv_interactive(EVer, Commands) ->
57    case catch(open_port({spawn, "\""++ erlsrv(EVer) ++ "\" readargs"},
58			 [{line,1000}, eof])) of
59	{'EXIT',{Reason,_}} ->
60	    {port_error, Reason};
61	Port ->
62	    write_all_data(Port, Commands),
63	    case read_all_data(Port) of
64		[] ->
65		    failed;
66		 X ->
67		    {ok, X}
68	    end
69    end.
70
71write_all_data(Port,[]) ->
72    Port ! {self(), {command, io_lib:nl()}},
73    ok;
74write_all_data(Port,[H|T]) ->
75    Port ! {self(), {command, unicode:characters_to_binary([H,io_lib:nl()])}},
76    write_all_data(Port,T).
77
78read_all_data(Port) ->
79    Data0 = lists:reverse(read_all_data(Port,[],[])),
80    %% Convert from utf8 to a list of chars
81    [unicode:characters_to_list(list_to_binary(Data)) || Data <- Data0].
82
83read_all_data(Port,Line,Lines) ->
84    receive
85	{Port, {data, {noeol,Data}}} ->
86	    read_all_data(Port,Line++Data,Lines);
87	{Port, {data, {eol,Data}}} ->
88	    read_all_data(Port,[],[Line++Data|Lines]);
89	{Port,_Other} ->
90	    Port ! {self(), close},
91	    receive
92		{Port, closed} ->
93		    case Line of
94			[] -> Lines;
95			_ -> [Line|Lines]
96		    end
97	    end
98    end.
99
100
101%%% Get all registered erlsrv services.
102get_all_services() ->
103    case run_erlsrv("list") of
104	failed ->
105	    [];
106	{ok, [_]} ->
107	    [];
108	{ok, [_H|T]} ->
109	    F = fun(X) ->
110			hd(string:lexemes(X,"\t "))
111		end,
112	    lists:map(F,T);
113	_ ->
114	    {error, external_program_failed}
115    end.
116
117disable_service(ServiceName) ->
118    disable_service(current_version(), ServiceName).
119disable_service(EVer, ServiceName) ->
120    run_erlsrv(EVer, "disable " ++ ServiceName).
121enable_service(ServiceName) ->
122    enable_service(current_version(), ServiceName).
123enable_service(EVer, ServiceName) ->
124    run_erlsrv(EVer, "enable " ++ ServiceName).
125remove_service(ServiceName) ->
126    run_erlsrv("remove " ++ ServiceName).
127rename_service(FromName, ToName) ->
128    rename_service(current_version(), FromName, ToName).
129rename_service(EVer, FromName, ToName) ->
130    run_erlsrv(EVer, "rename " ++ FromName ++ " " ++ ToName).
131
132%%% Get all information about a service
133%%% Returns [{Field,Value | []} ...]
134%%% Field is one of:
135%%% servicename : The service name (equal to parameter...)
136%%% stopaction : The erlang expression that shall stop the node
137%%% onfail : Action to take when erlang fails unexpectedly
138%%% machine : Full pathname of the erlang machine or start_erl program
139%%% workdir : The initial working directory of the erlang machine
140%%% sname | name : The short name of the node
141%%% priority : The OS priority of the erlang process
142%%% args : All arguments correctly parsed into a list of strings
143%%% comment : The service description
144%%% internalservicename : The windows internal service name
145%%% env : A list of environment variables and values [{"VAR", "VALUE"}]
146%%% Example:
147%%% [{servicename,"kalle_R4A"},
148%%%  {stopaction,"erlang:halt()."},
149%%%  {args,["-boot", "nisse","--","-reldir", "c:\myapproot"]}
150%%%  {env,[{"FOO","BAR"},{"VEGETABLE","TOMATO"}]}]
151
152get_service(ServiceName) ->
153    get_service(current_version(), ServiceName).
154get_service(EVer, ServiceName) ->
155    case run_erlsrv(EVer, "list " ++ ServiceName) of
156	failed ->
157	    {error, no_such_service};
158	{port_error, Reason} ->
159	    {error, {port_error, Reason}};
160	{ok, Data} ->
161	    Table = [{"Service name",servicename,[]},
162		     {"StopAction",stopaction, []},
163		     {"OnFail",onfail, "ignore"},
164		     {"Machine",machine, []},
165		     {"WorkDir",workdir, []},
166		     {"SName",sname, []},
167		     {"Name",name, []},
168		     {"Priority",priority, "default"},
169		     {"DebugType",debugtype, "none"},
170		     {"Args",args,[]},
171		     {"InternalServiceName",internalservicename,[]},
172		     {"Comment",comment,[]}],
173	    %% Env has special treatment...
174	    F = fun(X) ->
175			{Name,Value} = splitline(X),
176			case lists:keysearch(Name,1,Table) of
177			    {value,{Name,_Atom,Value}} ->
178				[];
179			    {value,{Name,Atom,_}} ->
180				{Atom,Value};
181			    _ ->
182				[]
183			end
184		end,
185            %%% First split by Env:
186	    {Before, After} = split_by_env(Data),
187	    FirstPass = lists:flatten(lists:map(F,Before)),
188	    %%% If the arguments are there, split them to
189	    SecondPass = split_arglist(FirstPass),
190	    %%% And now, if After contains anything, that is vwat to
191	    %%% have in the environment list...
192	    EnvParts = lists:map(
193			 fun(S) ->
194				 X = string:trim(S, leading, "$\t"),
195				 case hd(string:lexemes(X,"=")) of
196				     X ->
197					 %% Can this happen?
198					 {X,""};
199				     Y ->
200					 {Y,
201					  lists:sublist(X,length(Y)+2,
202							length(X))}
203				 end
204			 end,
205			 After),
206	    case EnvParts of
207		[] ->
208		    SecondPass;
209		_ ->
210		    lists:append(SecondPass,[{env,EnvParts}])
211	    end
212    end.
213
214
215store_service(Service) ->
216    store_service(current_version(),Service).
217store_service(EmulatorVersion,Service) ->
218    case lists:keysearch(servicename,1,Service) of
219	false ->
220	    {error, no_servicename};
221	{value, {_,Name}} ->
222	    {Action,Service1} = case get_service(EmulatorVersion,Name) of
223			 {error, no_such_service} ->
224			     {"add",Service};
225			 _ ->
226			     {"set",
227			      lists:keydelete(internalservicename,1,Service)}
228		     end,
229	    Commands = [Action | build_commands(Name, Service1)],
230	    case run_erlsrv_interactive(EmulatorVersion,Commands) of
231		{ok, _} ->
232		    ok;
233		X ->
234		    {error, X}
235	    end;
236	_ ->
237	    {error, malformed_description}
238    end.
239
240build_commands(Action, Service) ->
241    [ Action | lists:reverse(build_commands2(Service,[]))].
242
243build_commands2([],A) ->
244    A;
245build_commands2([{env,[]}|T],A) ->
246    build_commands2(T,A);
247build_commands2([{env,[{Var,Val}|Et]}|T],A) ->
248    build_commands2([{env,Et}|T],[Var ++ "=" ++ Val, "-env" | A]);
249build_commands2([{servicename,_}|T],A) ->
250    build_commands2(T,A);
251build_commands2([{Atom,[]} | T],A) ->
252    build_commands2(T,["-" ++ atom_to_list(Atom) | A]);
253build_commands2([{args,L}|T],A) ->
254    build_commands2(T,[concat_args(L),"-args"| A]);
255build_commands2([{Atom,Value} | T],A) ->
256    build_commands2(T,[Value, "-" ++ atom_to_list(Atom) | A]).
257
258concat_args([H|T]) ->
259    H ++ concat_args2(T).
260concat_args2([]) ->
261    "";
262concat_args2([H|T]) ->
263    " " ++ H ++ concat_args2(T).
264
265
266new_service(NewServiceName, OldService, Data) ->
267    new_service(NewServiceName, OldService, Data, []).
268new_service(NewServiceName, OldService, Data, RestartName) ->
269    Tmp0 = lists:keydelete(internalservicename,1,OldService), %Remove when
270						% creating new service from
271						% old.
272    Tmp1 = lists:keyreplace(servicename, 1, Tmp0,
273			    {servicename, NewServiceName}),
274    Tmp = case lists:keysearch(env,1,Tmp1) of
275	      {value, {env,Env0}} ->
276		  Env1 = lists:keydelete("ERLSRV_SERVICE_NAME",1,Env0),
277		  lists:keyreplace(env,1,Tmp1,
278				   {env, [{"ERLSRV_SERVICE_NAME",
279					   RestartName} |
280					  Env1]});
281	      _ ->
282		  Tmp1
283	  end,
284
285    ArgsTmp = case lists:keysearch(args, 1, Tmp) of
286	       false ->
287		   [];
288	       {value, {args, OldArgs}} ->
289		   OldArgs
290	   end,
291    Args = backstrip(ArgsTmp,"++"), %% Remove trailing ++, has no meaning
292    {Found, Tail} = lists:foldr(fun(A,{Flag,AccIn}) ->
293					case {Flag, A} of
294					    {true, _} -> {Flag,AccIn};
295					    {false, "++"} -> {true, AccIn};
296					    _ -> {false, [A|AccIn]}
297					end
298				end, {false,[]}, Args),
299
300    {OtherFlags, _DataDir} = case Found of
301				true ->
302				    check_tail(Tail);
303				false ->
304				    {[], false}
305			    end,
306    NewArgs1 = case Data of
307		   [] ->
308		       OtherFlags;
309		   _ ->
310		       ["-data", Data| OtherFlags]
311	       end,
312    case Found of
313	false ->
314	    A = case NewArgs1 of
315		    [] ->
316			[];
317		    _ ->
318			["++" | NewArgs1]
319		end,
320	    case {Args,A} of
321		{[],[]} ->
322		    Tmp;
323		{[],_} ->
324		    Tmp ++ [{args, A}];
325		{_,_} ->
326		    lists:keyreplace(args, 1, Tmp, {args, Args ++ A})
327	    end;
328	true ->
329	    StripArgs = backstrip(Args,["++"|Tail]),
330	    NewArgs2 = case NewArgs1 of
331			   [] ->
332			       [];
333			   _ ->
334			       ["++" |NewArgs1]
335		       end,
336	    NewArgs = StripArgs ++ NewArgs2,
337	    lists:keyreplace(args, 1, Tmp, {args, NewArgs})
338    end.
339
340
341backstrip(List,Tail) ->
342    lists:reverse(backstrip2(lists:reverse(List),lists:reverse(Tail))).
343backstrip2([A|T1],[A|T2]) ->
344    backstrip2(T1,T2);
345backstrip2(L,_) ->
346    L.
347
348check_tail(Tail) ->
349    {A,B} = check_tail(Tail, [], false),
350    {lists:reverse(A),B}.
351
352check_tail([], OtherFlags, DataDir) ->
353    {OtherFlags, DataDir};
354check_tail(["-data", TheDataDir|T], OtherFlags, _DataDir) ->
355    check_tail(T, OtherFlags, TheDataDir);
356check_tail([H|T],OtherFlags,DataDir) ->
357    check_tail(T,[H|OtherFlags],DataDir).
358
359
360
361
362%%% Recursive, The list is small
363split_arglist([]) ->
364    [];
365split_arglist([{args,Str}|T]) ->
366    [{args,parse_arglist(Str)}|T];
367split_arglist([H|T]) ->
368    [H|split_arglist(T)].
369
370%% Not recursive, may be long...
371parse_arglist(Str) ->
372    lists:reverse(parse_arglist(Str,[])).
373parse_arglist(Str,Accum) ->
374    Stripped = string:trim(Str, leading),
375    case length(Stripped) of
376	0 ->
377	    Accum;
378	_ ->
379	    {Next, Rest} = pick_argument(Str),
380	    parse_arglist(Rest,[Next | Accum])
381    end.
382
383pick_argument(Str) ->
384    {Rev,Rest} = pick_argument(normal,Str,[]),
385    {lists:reverse(Rev),Rest}.
386
387pick_argument(_,[],Acc) ->
388    {Acc, ""};
389pick_argument(normal,[$ |T],Acc) ->
390    {Acc,T};
391pick_argument(normal,[$\\|T],Acc) ->
392    pick_argument(normal_escaped,T,[$\\|Acc]);
393pick_argument(normal,[$"|T],Acc) ->
394    pick_argument(quoted,T,[$"|Acc]);
395pick_argument(normal_escaped,[$"|T],Acc) ->
396    pick_argument(bquoted,T,[$"|Acc]);
397pick_argument(normal_escaped,[A|T],Acc) ->
398    pick_argument(normal,T,[A|Acc]);
399pick_argument(quoted_escaped,[H|T],Acc) ->
400    pick_argument(quoted,T,[H|Acc]);
401pick_argument(quoted,[$"|T],Acc) ->
402    pick_argument(normal,T,[$"|Acc]);
403pick_argument(quoted,[$\\|T],Acc) ->
404    pick_argument(quoted_escaped,T,[$\\|Acc]);
405pick_argument(quoted,[H|T],Acc) ->
406    pick_argument(quoted,T,[H|Acc]);
407pick_argument(bquoted_escaped,[$"|T],Acc) ->
408    pick_argument(normal,T,[$"|Acc]);
409pick_argument(bquoted_escaped,[H|T],Acc) ->
410    pick_argument(bquoted,T,[H|Acc]);
411pick_argument(bquoted,[$\\|T],Acc) ->
412    pick_argument(bquoted_escaped,T,[$\\|Acc]);
413pick_argument(bquoted,[H|T],Acc) ->
414    pick_argument(bquoted,T,[H|Acc]);
415pick_argument(normal,[H|T],Acc) ->
416    pick_argument(normal,T,[H|Acc]).
417
418split_helper("Env:",{Where,0}) ->
419    {Where + 1, Where};
420split_helper(_, {Where,Pos}) ->
421    {Where + 1, Pos}.
422
423split_by_env(Data) ->
424    %%% Find Env...
425    case lists:foldl(fun split_helper/2,{0,0},Data) of
426	{_,0} ->
427	    %% Not found, hmmmm....
428	    {Data,[]};
429	{Len,Pos} ->
430	    {lists:sublist(Data,Pos),lists:sublist(Data,Pos+2,Len)}
431    end.
432
433
434splitline(Line) ->
435    case string:split(Line, ":") of
436        [_] ->
437	    {Line, ""};
438        [N, V] ->
439            {N, string:slice(V, 1)}
440    end.
441