%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1998-2017. 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(erlsrv). %% Purpose : Control the external erlsrv program. %%-compile(export_all). -export([get_all_services/0,get_service/1,get_service/2,store_service/1, store_service/2, new_service/3, new_service/4, disable_service/2, enable_service/2, disable_service/1, enable_service/1, remove_service/1, erlsrv/1, rename_service/2, rename_service/3]). erlsrv(EVer) -> Root = code:root_dir(), filename:join([Root, "erts-" ++ EVer, "bin", "erlsrv.exe"]). current_version() -> hd(string:lexemes(erlang:system_info(version),"_ ")). %%% Returns {ok, Output} | failed | {error, Reason} run_erlsrv(Command) -> run_erlsrv(current_version(),Command). run_erlsrv(EVer, Command) -> case catch(open_port({spawn, "\"" ++ erlsrv(EVer) ++ "\" " ++ Command}, [{line,1000}, in, eof])) of {'EXIT',{Reason,_}} -> {port_error, Reason}; Port -> case read_all_data(Port) of [] -> failed; X -> {ok, X} end end. run_erlsrv_interactive(EVer, Commands) -> case catch(open_port({spawn, "\""++ erlsrv(EVer) ++ "\" readargs"}, [{line,1000}, eof])) of {'EXIT',{Reason,_}} -> {port_error, Reason}; Port -> write_all_data(Port, Commands), case read_all_data(Port) of [] -> failed; X -> {ok, X} end end. write_all_data(Port,[]) -> Port ! {self(), {command, io_lib:nl()}}, ok; write_all_data(Port,[H|T]) -> Port ! {self(), {command, unicode:characters_to_binary([H,io_lib:nl()])}}, write_all_data(Port,T). read_all_data(Port) -> Data0 = lists:reverse(read_all_data(Port,[],[])), %% Convert from utf8 to a list of chars [unicode:characters_to_list(list_to_binary(Data)) || Data <- Data0]. read_all_data(Port,Line,Lines) -> receive {Port, {data, {noeol,Data}}} -> read_all_data(Port,Line++Data,Lines); {Port, {data, {eol,Data}}} -> read_all_data(Port,[],[Line++Data|Lines]); {Port,_Other} -> Port ! {self(), close}, receive {Port, closed} -> case Line of [] -> Lines; _ -> [Line|Lines] end end end. %%% Get all registered erlsrv services. get_all_services() -> case run_erlsrv("list") of failed -> []; {ok, [_]} -> []; {ok, [_H|T]} -> F = fun(X) -> hd(string:lexemes(X,"\t ")) end, lists:map(F,T); _ -> {error, external_program_failed} end. disable_service(ServiceName) -> disable_service(current_version(), ServiceName). disable_service(EVer, ServiceName) -> run_erlsrv(EVer, "disable " ++ ServiceName). enable_service(ServiceName) -> enable_service(current_version(), ServiceName). enable_service(EVer, ServiceName) -> run_erlsrv(EVer, "enable " ++ ServiceName). remove_service(ServiceName) -> run_erlsrv("remove " ++ ServiceName). rename_service(FromName, ToName) -> rename_service(current_version(), FromName, ToName). rename_service(EVer, FromName, ToName) -> run_erlsrv(EVer, "rename " ++ FromName ++ " " ++ ToName). %%% Get all information about a service %%% Returns [{Field,Value | []} ...] %%% Field is one of: %%% servicename : The service name (equal to parameter...) %%% stopaction : The erlang expression that shall stop the node %%% onfail : Action to take when erlang fails unexpectedly %%% machine : Full pathname of the erlang machine or start_erl program %%% workdir : The initial working directory of the erlang machine %%% sname | name : The short name of the node %%% priority : The OS priority of the erlang process %%% args : All arguments correctly parsed into a list of strings %%% comment : The service description %%% internalservicename : The windows internal service name %%% env : A list of environment variables and values [{"VAR", "VALUE"}] %%% Example: %%% [{servicename,"kalle_R4A"}, %%% {stopaction,"erlang:halt()."}, %%% {args,["-boot", "nisse","--","-reldir", "c:\myapproot"]} %%% {env,[{"FOO","BAR"},{"VEGETABLE","TOMATO"}]}] get_service(ServiceName) -> get_service(current_version(), ServiceName). get_service(EVer, ServiceName) -> case run_erlsrv(EVer, "list " ++ ServiceName) of failed -> {error, no_such_service}; {port_error, Reason} -> {error, {port_error, Reason}}; {ok, Data} -> Table = [{"Service name",servicename,[]}, {"StopAction",stopaction, []}, {"OnFail",onfail, "ignore"}, {"Machine",machine, []}, {"WorkDir",workdir, []}, {"SName",sname, []}, {"Name",name, []}, {"Priority",priority, "default"}, {"DebugType",debugtype, "none"}, {"Args",args,[]}, {"InternalServiceName",internalservicename,[]}, {"Comment",comment,[]}], %% Env has special treatment... F = fun(X) -> {Name,Value} = splitline(X), case lists:keysearch(Name,1,Table) of {value,{Name,_Atom,Value}} -> []; {value,{Name,Atom,_}} -> {Atom,Value}; _ -> [] end end, %%% First split by Env: {Before, After} = split_by_env(Data), FirstPass = lists:flatten(lists:map(F,Before)), %%% If the arguments are there, split them to SecondPass = split_arglist(FirstPass), %%% And now, if After contains anything, that is vwat to %%% have in the environment list... EnvParts = lists:map( fun(S) -> X = string:trim(S, leading, "$\t"), case hd(string:lexemes(X,"=")) of X -> %% Can this happen? {X,""}; Y -> {Y, lists:sublist(X,length(Y)+2, length(X))} end end, After), case EnvParts of [] -> SecondPass; _ -> lists:append(SecondPass,[{env,EnvParts}]) end end. store_service(Service) -> store_service(current_version(),Service). store_service(EmulatorVersion,Service) -> case lists:keysearch(servicename,1,Service) of false -> {error, no_servicename}; {value, {_,Name}} -> {Action,Service1} = case get_service(EmulatorVersion,Name) of {error, no_such_service} -> {"add",Service}; _ -> {"set", lists:keydelete(internalservicename,1,Service)} end, Commands = [Action | build_commands(Name, Service1)], case run_erlsrv_interactive(EmulatorVersion,Commands) of {ok, _} -> ok; X -> {error, X} end; _ -> {error, malformed_description} end. build_commands(Action, Service) -> [ Action | lists:reverse(build_commands2(Service,[]))]. build_commands2([],A) -> A; build_commands2([{env,[]}|T],A) -> build_commands2(T,A); build_commands2([{env,[{Var,Val}|Et]}|T],A) -> build_commands2([{env,Et}|T],[Var ++ "=" ++ Val, "-env" | A]); build_commands2([{servicename,_}|T],A) -> build_commands2(T,A); build_commands2([{Atom,[]} | T],A) -> build_commands2(T,["-" ++ atom_to_list(Atom) | A]); build_commands2([{args,L}|T],A) -> build_commands2(T,[concat_args(L),"-args"| A]); build_commands2([{Atom,Value} | T],A) -> build_commands2(T,[Value, "-" ++ atom_to_list(Atom) | A]). concat_args([H|T]) -> H ++ concat_args2(T). concat_args2([]) -> ""; concat_args2([H|T]) -> " " ++ H ++ concat_args2(T). new_service(NewServiceName, OldService, Data) -> new_service(NewServiceName, OldService, Data, []). new_service(NewServiceName, OldService, Data, RestartName) -> Tmp0 = lists:keydelete(internalservicename,1,OldService), %Remove when % creating new service from % old. Tmp1 = lists:keyreplace(servicename, 1, Tmp0, {servicename, NewServiceName}), Tmp = case lists:keysearch(env,1,Tmp1) of {value, {env,Env0}} -> Env1 = lists:keydelete("ERLSRV_SERVICE_NAME",1,Env0), lists:keyreplace(env,1,Tmp1, {env, [{"ERLSRV_SERVICE_NAME", RestartName} | Env1]}); _ -> Tmp1 end, ArgsTmp = case lists:keysearch(args, 1, Tmp) of false -> []; {value, {args, OldArgs}} -> OldArgs end, Args = backstrip(ArgsTmp,"++"), %% Remove trailing ++, has no meaning {Found, Tail} = lists:foldr(fun(A,{Flag,AccIn}) -> case {Flag, A} of {true, _} -> {Flag,AccIn}; {false, "++"} -> {true, AccIn}; _ -> {false, [A|AccIn]} end end, {false,[]}, Args), {OtherFlags, _DataDir} = case Found of true -> check_tail(Tail); false -> {[], false} end, NewArgs1 = case Data of [] -> OtherFlags; _ -> ["-data", Data| OtherFlags] end, case Found of false -> A = case NewArgs1 of [] -> []; _ -> ["++" | NewArgs1] end, case {Args,A} of {[],[]} -> Tmp; {[],_} -> Tmp ++ [{args, A}]; {_,_} -> lists:keyreplace(args, 1, Tmp, {args, Args ++ A}) end; true -> StripArgs = backstrip(Args,["++"|Tail]), NewArgs2 = case NewArgs1 of [] -> []; _ -> ["++" |NewArgs1] end, NewArgs = StripArgs ++ NewArgs2, lists:keyreplace(args, 1, Tmp, {args, NewArgs}) end. backstrip(List,Tail) -> lists:reverse(backstrip2(lists:reverse(List),lists:reverse(Tail))). backstrip2([A|T1],[A|T2]) -> backstrip2(T1,T2); backstrip2(L,_) -> L. check_tail(Tail) -> {A,B} = check_tail(Tail, [], false), {lists:reverse(A),B}. check_tail([], OtherFlags, DataDir) -> {OtherFlags, DataDir}; check_tail(["-data", TheDataDir|T], OtherFlags, _DataDir) -> check_tail(T, OtherFlags, TheDataDir); check_tail([H|T],OtherFlags,DataDir) -> check_tail(T,[H|OtherFlags],DataDir). %%% Recursive, The list is small split_arglist([]) -> []; split_arglist([{args,Str}|T]) -> [{args,parse_arglist(Str)}|T]; split_arglist([H|T]) -> [H|split_arglist(T)]. %% Not recursive, may be long... parse_arglist(Str) -> lists:reverse(parse_arglist(Str,[])). parse_arglist(Str,Accum) -> Stripped = string:trim(Str, leading), case length(Stripped) of 0 -> Accum; _ -> {Next, Rest} = pick_argument(Str), parse_arglist(Rest,[Next | Accum]) end. pick_argument(Str) -> {Rev,Rest} = pick_argument(normal,Str,[]), {lists:reverse(Rev),Rest}. pick_argument(_,[],Acc) -> {Acc, ""}; pick_argument(normal,[$ |T],Acc) -> {Acc,T}; pick_argument(normal,[$\\|T],Acc) -> pick_argument(normal_escaped,T,[$\\|Acc]); pick_argument(normal,[$"|T],Acc) -> pick_argument(quoted,T,[$"|Acc]); pick_argument(normal_escaped,[$"|T],Acc) -> pick_argument(bquoted,T,[$"|Acc]); pick_argument(normal_escaped,[A|T],Acc) -> pick_argument(normal,T,[A|Acc]); pick_argument(quoted_escaped,[H|T],Acc) -> pick_argument(quoted,T,[H|Acc]); pick_argument(quoted,[$"|T],Acc) -> pick_argument(normal,T,[$"|Acc]); pick_argument(quoted,[$\\|T],Acc) -> pick_argument(quoted_escaped,T,[$\\|Acc]); pick_argument(quoted,[H|T],Acc) -> pick_argument(quoted,T,[H|Acc]); pick_argument(bquoted_escaped,[$"|T],Acc) -> pick_argument(normal,T,[$"|Acc]); pick_argument(bquoted_escaped,[H|T],Acc) -> pick_argument(bquoted,T,[H|Acc]); pick_argument(bquoted,[$\\|T],Acc) -> pick_argument(bquoted_escaped,T,[$\\|Acc]); pick_argument(bquoted,[H|T],Acc) -> pick_argument(bquoted,T,[H|Acc]); pick_argument(normal,[H|T],Acc) -> pick_argument(normal,T,[H|Acc]). split_helper("Env:",{Where,0}) -> {Where + 1, Where}; split_helper(_, {Where,Pos}) -> {Where + 1, Pos}. split_by_env(Data) -> %%% Find Env... case lists:foldl(fun split_helper/2,{0,0},Data) of {_,0} -> %% Not found, hmmmm.... {Data,[]}; {Len,Pos} -> {lists:sublist(Data,Pos),lists:sublist(Data,Pos+2,Len)} end. splitline(Line) -> case string:split(Line, ":") of [_] -> {Line, ""}; [N, V] -> {N, string:slice(V, 1)} end.