1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2007-2020. 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
21%%%-------------------------------------------------------------------
22%%% File    : erlexec_SUITE.erl
23%%% Author  : Rickard Green <rickard.s.green@ericsson.com>
24%%% Description : Test erlexec's command line parsing
25%%%
26%%% Created : 22 May 2007 by Rickard Green <rickard.s.green@ericsson.com>
27%%%-------------------------------------------------------------------
28-module(erlexec_SUITE).
29
30-export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2]).
31
32-export([args_file/1, evil_args_file/1, env/1, args_file_env/1,
33         otp_7461/1, otp_7461_remote/1, argument_separation/1,
34         zdbbl_dist_buf_busy_limit/1]).
35
36-include_lib("common_test/include/ct.hrl").
37
38init_per_testcase(Case, Config) ->
39    SavedEnv = save_env(),
40    [{testcase, Case},{erl_flags_env, SavedEnv}|Config].
41
42end_per_testcase(_Case, Config) ->
43    SavedEnv = proplists:get_value(erl_flags_env, Config),
44    restore_env(SavedEnv),
45    cleanup_nodes(),
46    ok.
47
48suite() ->
49    [{ct_hooks,[ts_install_cth]},
50     {timetrap, {minutes, 1}}].
51
52all() ->
53    [args_file, evil_args_file, env, args_file_env,
54     otp_7461, argument_separation, zdbbl_dist_buf_busy_limit].
55
56%% Test that plain first argument does not
57%% destroy -home switch [OTP-8209] or interact with environments
58argument_separation(Config) when is_list(Config) ->
59    {ok,[[PName]]} = init:get_argument(progname),
60    SNameS = "erlexec_test_01",
61    SName = list_to_atom(SNameS++"@"++
62			 hd(tl(string:lexemes(atom_to_list(node()),"@")))),
63    Cmd = PName ++ " cmd_param -sname "++SNameS++" -setcookie "++
64	atom_to_list(erlang:get_cookie()) ++ " -cmd_test",
65    open_port({spawn,Cmd},[{env,[{"ERL_AFLAGS","-atest"},
66                                 {"ERL_FLAGS","env_param -test"},
67                                 {"ERL_ZFLAGS","zenv_param"}]}]),
68    pong = loop_ping(SName,40),
69    ct:log("emu_args: ~p",[rpc:call(SName,erlang,system_info,[emu_args])]),
70    {ok,[[_]]} = rpc:call(SName,init,get_argument,[home]),
71    {ok,[[]]} = rpc:call(SName,init,get_argument,[atest]),
72    {ok,[[]]} = rpc:call(SName,init,get_argument,[cmd_test]),
73    {ok,[[]]} = rpc:call(SName,init,get_argument,[test]),
74    error = rpc:call(SName,init,get_argument,[unkown]),
75    ["cmd_param","env_param","zenv_param"] = rpc:call(SName,init,get_plain_arguments,[]),
76    ok = cleanup_nodes(),
77    ok.
78
79cleanup_nodes() ->
80    cleanup_node("erlexec_test_01",20).
81cleanup_node(SNameS,0) ->
82    {error, {would_not_die,list_to_atom(SNameS)}};
83cleanup_node(SNameS,N) ->
84    SName = list_to_atom(SNameS++"@"++
85			 hd(tl(string:lexemes(atom_to_list(node()),"@")))),
86    case rpc:call(SName,init,stop,[]) of
87	{badrpc,_} ->
88	    ok;
89	ok ->
90	    receive after 500 -> ok end,
91	    cleanup_node(SNameS,N-1)
92    end.
93
94loop_ping(_,0) ->
95    flush(),
96    pang;
97loop_ping(Node,N) ->
98    case net_adm:ping(Node) of
99	pang ->
100	    receive
101	    after 500 ->
102		    ok
103	    end,
104	    loop_ping(Node, N-1);
105	pong ->
106	    pong
107    end.
108
109flush() ->
110    receive M ->
111            ct:pal("~p",[M]),
112            flush()
113    after 10 ->
114            ok
115    end.
116
117args_file(Config) when is_list(Config) ->
118    AFN1 = privfile("1", Config),
119    AFN2 = privfile("2", Config),
120    AFN3 = privfile("3", Config),
121    AFN4 = privfile("4", Config),
122    AFN5 = privfile("5", Config),
123    AFN6 = privfile("6", Config),
124    write_file(AFN1, "-MiscArg2~n"
125		     "# a comment +\\#1000~n"
126		     "+\\#200 # another comment~n"
127		     "~n"
128		     "# another config file to read~n"
129		     " -args_file ~s#acomment~n"
130		     "~n"
131		     "-MiscArg7~n"
132		     "#~n"
133		     "+\\#700~n"
134		     "-extra +XtraArg6~n",
135	       [AFN2]),
136    write_file(AFN2,
137		     "-MiscArg3~n"
138		     "+\\#300~n"
139		     "-args_file ~s~n"
140		     "-MiscArg5~n"
141		     "+\\#500#anothercomment -MiscArg10~n"
142		     "-args_file ~s~n"
143		     "-args_file ~s~n"
144		     "-args_file ~s~n"
145		     "-extra +XtraArg5~n",
146		     [AFN3, AFN4, AFN5, AFN6]),
147    write_file(AFN3,
148		     "# comment again~n"
149		     " -MiscArg4 +\\#400 -extra +XtraArg1"),
150    write_file(AFN4,
151		     " -MiscArg6 +\\#600 -extra +XtraArg2~n"
152		     "+XtraArg3~n"
153		     "+XtraArg4~n"
154		     "# comment again~n"),
155    write_file(AFN5, ""),
156    write_file(AFN6, "-extra # +XtraArg10~n"),
157    CmdLine = "+#100 -MiscArg1 "
158	++ "-args_file " ++ AFN1
159	++ " +#800 -MiscArg8 -extra +XtraArg7 +XtraArg8",
160    {Emu, Misc, Extra} = emu_args(CmdLine),
161    verify_args(["-#100", "-#200", "-#300", "-#400",
162		       "-#500", "-#600", "-#700", "-#800"], Emu),
163    verify_args(["-MiscArg1", "-MiscArg2", "-MiscArg3", "-MiscArg4",
164		       "-MiscArg5", "-MiscArg6", "-MiscArg7", "-MiscArg8"],
165		      Misc),
166    verify_args(["+XtraArg1", "+XtraArg2", "+XtraArg3", "+XtraArg4",
167		       "+XtraArg5", "+XtraArg6", "+XtraArg7", "+XtraArg8"],
168		      Extra),
169    verify_not_args(["-MiscArg10", "-#1000", "+XtraArg10",
170			   "-MiscArg1", "-MiscArg2", "-MiscArg3", "-MiscArg4",
171			   "-MiscArg5", "-MiscArg6", "-MiscArg7", "-MiscArg8",
172			   "+XtraArg1", "+XtraArg2", "+XtraArg3", "+XtraArg4",
173			   "+XtraArg5", "+XtraArg6", "+XtraArg7", "+XtraArg8"],
174			  Emu),
175    verify_not_args(["-MiscArg10", "-#1000", "+XtraArg10",
176			   "-#100", "-#200", "-#300", "-#400",
177			   "-#500", "-#600", "-#700", "-#800",
178			   "+XtraArg1", "+XtraArg2", "+XtraArg3", "+XtraArg4",
179			   "+XtraArg5", "+XtraArg6", "+XtraArg7", "+XtraArg8"],
180			  Misc),
181    verify_not_args(["-MiscArg10", "-#1000", "+XtraArg10",
182			   "-#100", "-#200", "-#300", "-#400",
183			   "-#500", "-#600", "-#700", "-#800",
184			   "-MiscArg1", "-MiscArg2", "-MiscArg3", "-MiscArg4",
185			   "-MiscArg5", "-MiscArg6", "-MiscArg7", "-MiscArg8"],
186			  Extra),
187    ok.
188
189evil_args_file(Config) when is_list(Config) ->
190    Lim = 300,
191    FNums = lists:seq(1, Lim),
192    lists:foreach(fun (End) when End == Lim ->
193			  AFN = privfile(integer_to_list(End), Config),
194			  write_file(AFN,
195					   "-MiscArg~p ",
196					   [End]);
197		      (I) ->
198			  AFNX = privfile(integer_to_list(I), Config),
199			  AFNY = privfile(integer_to_list(I+1), Config),
200			  {Frmt, Args} =
201			      case I rem 2 of
202				  0 ->
203				      {"-MiscArg~p -args_file ~s -MiscArg~p",
204				       [I, AFNY, I]};
205				  _ ->
206				      {"-MiscArg~p -args_file ~s",
207				       [I, AFNY]}
208			      end,
209			  write_file(AFNX, Frmt, Args)
210		  end,
211		  FNums),
212    {_Emu, Misc, _Extra} = emu_args("-args_file "
213					  ++ privfile("1", Config)),
214    ANums = FNums
215	++ lists:reverse(lists:filter(fun (I) when I == Lim -> false;
216					  (I) when I rem 2 == 0 -> true;
217					  (_) -> false
218				      end, FNums)),
219    verify_args(lists:map(fun (I) -> "-MiscArg"++integer_to_list(I) end,
220				ANums),
221		      Misc),
222    ok.
223
224
225
226env(Config) when is_list(Config) ->
227    os:putenv("ERL_AFLAGS", "-MiscArg1 +#100 -extra +XtraArg1 +XtraArg2"),
228    CmdLine = "+#200 -MiscArg2 -extra +XtraArg3 +XtraArg4",
229    os:putenv("ERL_FLAGS", "-MiscArg3 +#300 -extra +XtraArg5"),
230    os:putenv("ERL_ZFLAGS", "-MiscArg4 +#400 -extra +XtraArg6"),
231    {Emu, Misc, Extra} = emu_args(CmdLine),
232    verify_args(["-#100", "-#200", "-#300", "-#400"], Emu),
233    verify_args(["-MiscArg1", "-MiscArg2", "-MiscArg3", "-MiscArg4"],
234		      Misc),
235    verify_args(["+XtraArg1", "+XtraArg2", "+XtraArg3", "+XtraArg4",
236		       "+XtraArg5", "+XtraArg6"],
237		      Extra),
238    ok.
239
240args_file_env(Config) when is_list(Config) ->
241    AFN1 = privfile("1", Config),
242    AFN2 = privfile("2", Config),
243    write_file(AFN1, "-MiscArg2 +\\#200 -extra +XtraArg1"),
244    write_file(AFN2, "-MiscArg3 +\\#400 -extra +XtraArg3"),
245    os:putenv("ERL_AFLAGS",
246		    "-MiscArg1 +#100 -args_file "++AFN1++ " -extra +XtraArg2"),
247    CmdLine = "+#300 -args_file "++AFN2++" -MiscArg4 -extra +XtraArg4",
248    os:putenv("ERL_FLAGS", "-MiscArg5 +#500 -extra +XtraArg5"),
249    os:putenv("ERL_ZFLAGS", "-MiscArg6 +#600 -extra +XtraArg6"),
250    {Emu, Misc, Extra} = emu_args(CmdLine),
251    verify_args(["-#100", "-#200", "-#300", "-#400",
252		       "-#500", "-#600"], Emu),
253    verify_args(["-MiscArg1", "-MiscArg2", "-MiscArg3", "-MiscArg4",
254		       "-MiscArg5", "-MiscArg6"],
255		      Misc),
256    verify_args(["+XtraArg1", "+XtraArg2", "+XtraArg3", "+XtraArg4",
257		       "+XtraArg5", "+XtraArg6"],
258		      Extra),
259    ok.
260
261%% Make sure "erl -detached" survives when parent process group gets killed
262otp_7461(Config) when is_list(Config) ->
263    case os:type() of
264    	{unix,_} ->
265	    {NetStarted, _} = net_kernel:start([test_server, shortnames]),
266	    try
267		net_kernel:monitor_nodes(true),
268		register(otp_7461, self()),
269
270		otp_7461_do(Config)
271	    after
272		catch unregister(otp_7461),
273	        catch net_kernel:monitor_nodes(false),
274	        case NetStarted of
275		    ok -> net_kernel:stop();
276		    _ -> ok
277		end
278	    end;
279	_ ->
280	    {skip,"Only on Unix."}
281    end.
282
283otp_7461_do(Config) ->
284    io:format("alive=~p node=~p\n",[is_alive(), node()]),
285    TestProg = filename:join([proplists:get_value(data_dir, Config), "erlexec_tests"]),
286    {ok, [[ErlProg]]} = init:get_argument(progname),
287    Cmd = TestProg ++ " " ++ ErlProg ++
288	" -detached -sname " ++ get_nodename(otp_7461) ++
289	" -setcookie " ++ atom_to_list(erlang:get_cookie()) ++
290	" -pa " ++ filename:dirname(code:which(?MODULE)) ++
291	" -s erlexec_SUITE otp_7461_remote init " ++ atom_to_list(node()),
292
293    %% otp_7461 --------> erlexec_tests.c --------> cerl -detached
294    %%          open_port                 fork+exec
295
296    io:format("spawn port prog ~p\n",[Cmd]),
297    Port = open_port({spawn, Cmd}, [eof]),
298
299    io:format("Wait for node to connect...\n",[]),
300    {nodeup, Slave} = receive Msg -> Msg
301			    after 20*1000 -> timeout end,
302    io:format("Node alive: ~p\n", [Slave]),
303
304    pong = net_adm:ping(Slave),
305    io:format("Ping ok towards ~p\n", [Slave]),
306
307    Port ! { self(), {command, "K"}}, % Kill child process group
308    {Port, {data, "K"}} = receive Msg2 -> Msg2 end,
309    port_close(Port),
310
311    %% Now the actual test. Detached node should still be alive.
312    pong = net_adm:ping(Slave),
313    io:format("Ping still ok towards ~p\n", [Slave]),
314
315    %% Halt node
316    rpc:cast(Slave, ?MODULE, otp_7461_remote, [[halt, self()]]),
317
318    {nodedown, Slave} = receive
319                            Msg3 -> Msg3
320                        after 20*1000 -> timeout
321                        end,
322    io:format("Node dead: ~p\n", [Slave]),
323    ok.
324
325
326%% Executed on slave node
327otp_7461_remote([init, Master]) ->
328    io:format("otp_7461_remote(init,~p) at ~p\n",[Master, node()]),
329    net_kernel:connect_node(Master);
330otp_7461_remote([halt, Pid]) ->
331    io:format("halt order from ~p to node ~p\n",[Pid,node()]),
332    halt().
333
334%% Check +zdbbl flag
335zdbbl_dist_buf_busy_limit(Config) when is_list(Config) ->
336    LimKB = 1122233,
337    LimB = LimKB*1024,
338    {ok,[[PName]]} = init:get_argument(progname),
339    SNameS = "erlexec_test_02",
340    SName = list_to_atom(SNameS++"@"++
341                         hd(tl(string:lexemes(atom_to_list(node()),"@")))),
342    Cmd = PName ++ " -sname "++SNameS++" -setcookie "++
343        atom_to_list(erlang:get_cookie()) ++
344	" +zdbbl " ++ integer_to_list(LimKB),
345    open_port({spawn,Cmd},[]),
346    pong = loop_ping(SName,40),
347    LimB = rpc:call(SName,erlang,system_info,[dist_buf_busy_limit]),
348    ok = cleanup_node(SNameS, 10),
349    ok.
350
351
352%%
353%% Utils
354%%
355
356save_env() ->
357    {erl_flags,
358     os:getenv("ERL_AFLAGS"),
359     os:getenv("ERL_FLAGS"),
360     os:getenv("ERL_"++erlang:system_info(otp_release)++"_FLAGS"),
361     os:getenv("ERL_ZFLAGS")}.
362
363restore_env(EVar, false) when is_list(EVar) ->
364    restore_env(EVar, "");
365restore_env(EVar, "") when is_list(EVar) ->
366    case os:getenv(EVar) of
367	false -> ok;
368	"" -> ok;
369	" " -> ok;
370	_ -> os:putenv(EVar, " ")
371    end;
372restore_env(EVar, Value) when is_list(EVar), is_list(Value) ->
373    case os:getenv(EVar) of
374	Value -> ok;
375	_ -> os:putenv(EVar, Value)
376    end.
377
378restore_env({erl_flags, AFlgs, Flgs, RFlgs, ZFlgs}) ->
379    restore_env("ERL_AFLAGS", AFlgs),
380    restore_env("ERL_FLAGS", Flgs),
381    restore_env("ERL_"++erlang:system_info(otp_release)++"_FLAGS", RFlgs),
382    restore_env("ERL_ZFLAGS", ZFlgs),
383    ok.
384
385privfile(Name, Config) ->
386    filename:join([proplists:get_value(priv_dir, Config),
387		   atom_to_list(proplists:get_value(testcase, Config)) ++ "." ++ Name]).
388
389write_file(FileName, Frmt) ->
390    write_file(FileName, Frmt, []).
391
392write_file(FileName, Frmt, Args) ->
393    {ok, File} = file:open(FileName, [write]),
394    io:format(File, Frmt, Args),
395    ok = file:close(File).
396
397verify_args([], _Ys) ->
398    ok;
399verify_args(Xs, []) ->
400    exit({args_not_found_in_order, Xs});
401verify_args([X|Xs], [X|Ys]) ->
402    verify_args(Xs, Ys);
403verify_args(Xs, [_Y|Ys]) ->
404    verify_args(Xs, Ys).
405
406verify_not_args(Xs, Ys) ->
407    lists:foreach(fun (X) ->
408			  case lists:member(X, Ys) of
409			      true -> exit({arg_present, X});
410			      false -> ok
411			  end
412		  end, Xs).
413
414emu_args(CmdLineArgs) ->
415    io:format("CmdLineArgs = ~ts~n", [CmdLineArgs]),
416    {ok,[[Erl]]} = init:get_argument(progname),
417    EmuCL = os:cmd(Erl ++ " -emu_args_exit " ++ CmdLineArgs),
418    io:format("EmuCL = ~ts", [EmuCL]),
419    split_emu_clt(string:lexemes(EmuCL, [$ ,$\t,$\n,[$\r,$\n]])).
420
421split_emu_clt(EmuCLT) ->
422    split_emu_clt(EmuCLT, [], [], [], emu).
423
424split_emu_clt([], _Emu, _Misc, _Extra, emu) ->
425    exit(bad_cmd_line);
426split_emu_clt([], Emu, Misc, Extra, _Type) ->
427    {lists:reverse(Emu), lists:reverse(Misc), lists:reverse(Extra)};
428
429split_emu_clt(["--"|As], Emu, Misc, Extra, emu) ->
430    split_emu_clt(As, Emu, Misc, Extra, misc);
431split_emu_clt([A|As], Emu, Misc, Extra, emu = Type) ->
432    split_emu_clt(As, [A|Emu], Misc, Extra, Type);
433
434split_emu_clt(["-extra"|As], Emu, Misc, Extra, misc) ->
435    split_emu_clt(As, Emu, Misc, Extra, extra);
436split_emu_clt([A|As], Emu, Misc, Extra, misc = Type) ->
437    split_emu_clt(As, Emu, [A|Misc], Extra, Type);
438
439split_emu_clt([A|As], Emu, Misc, Extra, extra = Type) ->
440    split_emu_clt(As, Emu, Misc, [A|Extra], Type).
441
442
443get_nodename(T) ->
444    atom_to_list(T)
445	++ "-"
446	++ atom_to_list(?MODULE)
447	++ "-"
448	++ integer_to_list(erlang:system_time(seconds))
449	++ "-"
450	++ integer_to_list(erlang:unique_integer([positive])).
451