aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel
diff options
context:
space:
mode:
authorLukas Larsson <[email protected]>2016-07-14 12:24:49 +0200
committerLukas Larsson <[email protected]>2016-08-08 16:34:18 +0200
commitf1ca806498bc7f7dad96f2c7e188fdc55e0124cb (patch)
tree6e7a01ebb9b908ec5a29a875fa54250004175793 /lib/kernel
parentb490fb8664ec6e5ceaadc1c74350dc666f5406d2 (diff)
downloadotp-f1ca806498bc7f7dad96f2c7e188fdc55e0124cb.tar.gz
otp-f1ca806498bc7f7dad96f2c7e188fdc55e0124cb.tar.bz2
otp-f1ca806498bc7f7dad96f2c7e188fdc55e0124cb.zip
kernel: Use ^D as eot for os:cmd on unix platforms
This is needed as doing only an 'exit' will only exit the program, but any children started in the program that have stdout/stderr still open will keep the port open until they terminate. i.e. os:cmd("while true; do echo sleep 1; sleep 1; done&"). would block os:cmd forever because the while loop keeps its copies of stdout/stderr open forever. It could be argued that this is correct behaviour, and it is the way it works on windows, but changing this breaks backwards compatability for os:cmd which is not acceptable.
Diffstat (limited to 'lib/kernel')
-rw-r--r--lib/kernel/src/os.erl48
-rw-r--r--lib/kernel/test/os_SUITE.erl22
2 files changed, 54 insertions, 16 deletions
diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl
index f0ad26b1f2..81b70a7fee 100644
--- a/lib/kernel/src/os.erl
+++ b/lib/kernel/src/os.erl
@@ -226,11 +226,13 @@ extensions() ->
Command :: atom() | io_lib:chars().
cmd(Cmd) ->
validate(Cmd),
- {SpawnCmd, SpawnOpts, SpawnInput} = mk_cmd(os:type(), Cmd),
+ {SpawnCmd, SpawnOpts, SpawnInput, Eot} = mk_cmd(os:type(), Cmd),
Port = open_port({spawn, SpawnCmd}, [binary, stderr_to_stdout,
- stream, in, eof, hide | SpawnOpts]),
+ stream, in, hide | SpawnOpts]),
+ MonRef = erlang:monitor(port, Port),
true = port_command(Port, SpawnInput),
- Bytes = get_data(Port, []),
+ Bytes = get_data(Port, MonRef, Eot, []),
+ demonitor(MonRef, [flush]),
String = unicode:characters_to_list(Bytes),
if %% Convert to unicode list if possible otherwise return bytes
is_list(String) -> String;
@@ -243,7 +245,7 @@ mk_cmd({win32,Wtype}, Cmd) ->
{false,_} -> lists:concat(["cmd /c", Cmd]);
{Cspec,_} -> lists:concat([Cspec," /c",Cmd])
end,
- {Command, [], []};
+ {Command, [], [], <<>>};
mk_cmd(OsType,Cmd) when is_atom(Cmd) ->
mk_cmd(OsType, atom_to_list(Cmd));
mk_cmd(_,Cmd) ->
@@ -252,7 +254,8 @@ mk_cmd(_,Cmd) ->
{"/bin/sh -s unix:cmd", [out],
%% We insert a new line after the command, in case the command
%% contains a comment character.
- ["(", unicode:characters_to_binary(Cmd), "\n); exit\n"]}.
+ ["(", unicode:characters_to_binary(Cmd), "\n); echo \"\^D\"\n"],
+ <<$\^D>>}.
validate(Atom) when is_atom(Atom) ->
ok;
@@ -267,16 +270,18 @@ validate1([List|Rest]) when is_list(List) ->
validate1([]) ->
ok.
-get_data(Port, Sofar) ->
+get_data(Port, MonRef, Eot, Sofar) ->
receive
{Port, {data, Bytes}} ->
- get_data(Port, [Sofar,Bytes]);
- {Port, eof} ->
- Port ! {self(), close},
- receive
- {Port, closed} ->
- true
- end,
+ case eot(Bytes, Eot) of
+ more ->
+ get_data(Port, MonRef, Eot, [Sofar,Bytes]);
+ Last ->
+ Port ! {self(), close},
+ flush_until_closed(Port),
+ iolist_to_binary([Sofar, Last])
+ end;
+ {'DOWN', MonRef, _, _ , _} ->
receive
{'EXIT', Port, _} ->
ok
@@ -285,3 +290,20 @@ get_data(Port, Sofar) ->
end,
iolist_to_binary(Sofar)
end.
+
+eot(_Bs, <<>>) ->
+ more;
+eot(Bs, Eot) ->
+ case binary:match(Bs, Eot) of
+ nomatch -> more;
+ {Pos, _} ->
+ binary:part(Bs,{0, Pos})
+ end.
+
+flush_until_closed(Port) ->
+ receive
+ {Port, {data, _Bytes}} ->
+ flush_until_closed(Port);
+ {Port, closed} ->
+ true
+ end.
diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl
index 2a1e5016ec..8f3e511941 100644
--- a/lib/kernel/test/os_SUITE.erl
+++ b/lib/kernel/test/os_SUITE.erl
@@ -24,7 +24,8 @@
init_per_testcase/2,end_per_testcase/2]).
-export([space_in_cwd/1, quoting/1, cmd_unicode/1, space_in_name/1, bad_command/1,
find_executable/1, unix_comment_in_command/1, deep_list_command/1,
- large_output_command/1, perf_counter_api/1]).
+ large_output_command/1, background_command/0, background_command/1,
+ perf_counter_api/1]).
-include_lib("common_test/include/ct.hrl").
@@ -35,7 +36,7 @@ suite() ->
all() ->
[space_in_cwd, quoting, cmd_unicode, space_in_name, bad_command,
find_executable, unix_comment_in_command, deep_list_command,
- large_output_command, perf_counter_api].
+ large_output_command, background_command, perf_counter_api].
groups() ->
[].
@@ -52,6 +53,13 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
+init_per_testcase(background_command, Config) ->
+ case os:type() of
+ {win32, _} ->
+ {skip,"Should not work on windows"};
+ _ ->
+ Config
+ end;
init_per_testcase(_TC,Config) ->
Config.
@@ -261,13 +269,21 @@ deep_list_command(Config) when is_list(Config) ->
%% FYI: [$e, $c, "ho"] =:= io_lib:format("ec~s", ["ho"])
ok.
-%% Test to take sure that the correct data is
+%% Test to make sure that the correct data is
%% received when doing large commands.
large_output_command(Config) when is_list(Config) ->
%% Maximum allowed on windows is 8192, so we test well below that
AAA = lists:duplicate(7000, $a),
comp(AAA,os:cmd("echo " ++ AAA)).
+%% Test that it is possible on unix to start a background task using os:cmd.
+background_command() ->
+ [{timetrap, {seconds, 5}}].
+background_command(_Config) ->
+ %% This testcase fails when the os:cmd takes
+ %% longer then the 5 second timeout
+ os:cmd("sleep 10&").
+
%% Test that the os:perf_counter api works as expected
perf_counter_api(_Config) ->