diff options
author | Lukas Larsson <[email protected]> | 2016-07-14 12:24:49 +0200 |
---|---|---|
committer | Lukas Larsson <[email protected]> | 2016-08-08 16:34:18 +0200 |
commit | f1ca806498bc7f7dad96f2c7e188fdc55e0124cb (patch) | |
tree | 6e7a01ebb9b908ec5a29a875fa54250004175793 /lib/kernel/src/os.erl | |
parent | b490fb8664ec6e5ceaadc1c74350dc666f5406d2 (diff) | |
download | otp-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/src/os.erl')
-rw-r--r-- | lib/kernel/src/os.erl | 48 |
1 files changed, 35 insertions, 13 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. |