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