From b6df9b6babb5002c5eb940e27474855abf4c5930 Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Wed, 13 Dec 2017 11:22:45 +0100 Subject: kernel: Add os:cmd/2 with max_size option git cherry-pick 55e929c4ed5cd854038c18697123ea94948ebf35 --- lib/kernel/doc/src/os.xml | 43 ++++++++++++++++++++++++++++++++++++++----- lib/kernel/src/os.erl | 37 ++++++++++++++++++++++++++----------- lib/kernel/test/os_SUITE.erl | 18 ++++++++++++++++-- 3 files changed, 80 insertions(+), 18 deletions(-) diff --git a/lib/kernel/doc/src/os.xml b/lib/kernel/doc/src/os.xml index 739ac35d2a..a6a63448b7 100644 --- a/lib/kernel/doc/src/os.xml +++ b/lib/kernel/doc/src/os.xml @@ -38,17 +38,35 @@ most platforms.

+ + + + + + + +

Options for os:cmd/2

+ + max_size + +

The maximum size of the data returned by the os:cmd call. + See the os:cmd/2 + documentation for more details.

+
+
+
+
+
+ + Execute a command in a shell of the target OS.

Executes Command in a command shell of the - target OS, - captures the standard output of the command, and returns this - result as a string. This function is a replacement of - the previous function unix:cmd/1; they are equivalent on a - Unix platform.

+ target OS, captures the standard output of the command, + and returns this result as a string.

Examples:

LsOut = os:cmd("ls"), % on unix platform @@ -57,6 +75,21 @@ DirOut = os:cmd("dir"), % on Win32 platform called from another program (for example, os:cmd/1) can differ, compared with the standard output of the command when called directly from an OS command shell.

+

os:cmd/2 was added in kernel-5.5 (OTP-20.2.1). It makes it + possible to pass an options map as the second argument in order to + control the behaviour of os:cmd. The possible options are: +

+ + max_size + +

The maximum size of the data returned by the os:cmd call. + This option is a safety feature that should be used when the command + executed can return a very large, possibly infinite, result.

+ +> os:cmd("cat /dev/zero", #{ max_size => 20 }). +[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] +
+
diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl index a90a6dcca7..dbb28c1210 100644 --- a/lib/kernel/src/os.erl +++ b/lib/kernel/src/os.erl @@ -21,7 +21,7 @@ %% Provides a common operating system interface. --export([type/0, version/0, cmd/1, find_executable/1, find_executable/2]). +-export([type/0, version/0, cmd/1, cmd/2, find_executable/1, find_executable/2]). -include("file.hrl"). @@ -32,6 +32,11 @@ putenv/2, system_time/0, system_time/1, timestamp/0, unsetenv/1]). +-type os_command() :: atom() | io_lib:chars(). +-type os_command_opts() :: #{ max_size => non_neg_integer() | infinity }. + +-export_type([os_command/0, os_command_opts/0]). + -spec getenv() -> [string()]. getenv() -> erlang:nif_error(undef). @@ -223,15 +228,21 @@ extensions() -> %% Executes the given command in the default shell for the operating system. -spec cmd(Command) -> string() when - Command :: atom() | io_lib:chars(). + Command :: os_command(). cmd(Cmd) -> + cmd(Cmd, #{ }). + +-spec cmd(Command, Options) -> string() when + Command :: os_command(), + Options :: os_command_opts(). +cmd(Cmd, Opts) -> validate(Cmd), {SpawnCmd, SpawnOpts, SpawnInput, Eot} = mk_cmd(os:type(), Cmd), Port = open_port({spawn, SpawnCmd}, [binary, stderr_to_stdout, stream, in, hide | SpawnOpts]), MonRef = erlang:monitor(port, Port), true = port_command(Port, SpawnInput), - Bytes = get_data(Port, MonRef, Eot, []), + Bytes = get_data(Port, MonRef, Eot, [], 0, maps:get(max_size, Opts, infinity)), demonitor(MonRef, [flush]), String = unicode:characters_to_list(Bytes), if %% Convert to unicode list if possible otherwise return bytes @@ -282,12 +293,13 @@ validate1([List|Rest]) when is_list(List) -> validate1([]) -> ok. -get_data(Port, MonRef, Eot, Sofar) -> +get_data(Port, MonRef, Eot, Sofar, Size, Max) -> receive {Port, {data, Bytes}} -> - case eot(Bytes, Eot) of + case eot(Bytes, Eot, Size, Max) of more -> - get_data(Port, MonRef, Eot, [Sofar,Bytes]); + get_data(Port, MonRef, Eot, [Sofar, Bytes], + Size + byte_size(Bytes), Max); Last -> catch port_close(Port), flush_until_down(Port, MonRef), @@ -298,13 +310,16 @@ get_data(Port, MonRef, Eot, Sofar) -> iolist_to_binary(Sofar) end. -eot(_Bs, <<>>) -> +eot(_Bs, <<>>, _Size, _Max) -> more; -eot(Bs, Eot) -> +eot(Bs, Eot, Size, Max) -> case binary:match(Bs, Eot) of - nomatch -> more; - {Pos, _} -> - binary:part(Bs,{0, Pos}) + nomatch when Size + byte_size(Bs) < Max -> + more; + {Pos, _} when Size + Pos < Max -> + binary:part(Bs,{0, Pos}); + _ -> + binary:part(Bs,{0, Max - Size}) end. %% When port_close returns we know that all the diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl index e76d6ec482..70752e6ee6 100644 --- a/lib/kernel/test/os_SUITE.erl +++ b/lib/kernel/test/os_SUITE.erl @@ -25,7 +25,8 @@ -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, background_command/0, background_command/1, - message_leak/1, close_stdin/0, close_stdin/1, perf_counter_api/1]). + message_leak/1, close_stdin/0, close_stdin/1, max_size_command/1, + perf_counter_api/1]). -include_lib("common_test/include/ct.hrl"). @@ -37,7 +38,7 @@ all() -> [space_in_cwd, quoting, cmd_unicode, space_in_name, bad_command, find_executable, unix_comment_in_command, deep_list_command, large_output_command, background_command, message_leak, - close_stdin, perf_counter_api]. + close_stdin, max_size_command, perf_counter_api]. groups() -> []. @@ -312,6 +313,19 @@ close_stdin(Config) -> "-1" = os:cmd(Fds). +max_size_command(_Config) -> + + Res20 = os:cmd("cat /dev/zero", #{ max_size => 20 }), + 20 = length(Res20), + + Res0 = os:cmd("cat /dev/zero", #{ max_size => 0 }), + 0 = length(Res0), + + Res32768 = os:cmd("cat /dev/zero", #{ max_size => 32768 }), + 32768 = length(Res32768), + + ResHello = os:cmd("echo hello", #{ max_size => 20 }), + 6 = length(ResHello). %% Test that the os:perf_counter api works as expected perf_counter_api(_Config) -> -- cgit v1.2.3 From 5a583d0e58a97038f5fa3bdfa3c35694f0a18f34 Mon Sep 17 00:00:00 2001 From: Lukas Larsson Date: Wed, 28 Feb 2018 10:04:53 +0100 Subject: kernel: Fix handling of os:cmd option max_size in win git cherry-pick 75b0f73f72e1783d4ace976cdd2b5f23bdc3ebae --- lib/kernel/src/os.erl | 8 ++++---- lib/kernel/test/os_SUITE.erl | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl index dbb28c1210..92ac401b72 100644 --- a/lib/kernel/src/os.erl +++ b/lib/kernel/src/os.erl @@ -310,16 +310,16 @@ get_data(Port, MonRef, Eot, Sofar, Size, Max) -> iolist_to_binary(Sofar) end. -eot(_Bs, <<>>, _Size, _Max) -> +eot(Bs, <<>>, Size, Max) when Size + byte_size(Bs) < Max -> more; +eot(Bs, <<>>, Size, Max) -> + binary:part(Bs, {0, Max - Size}); eot(Bs, Eot, Size, Max) -> case binary:match(Bs, Eot) of - nomatch when Size + byte_size(Bs) < Max -> - more; {Pos, _} when Size + Pos < Max -> binary:part(Bs,{0, Pos}); _ -> - binary:part(Bs,{0, Max - Size}) + eot(Bs, <<>>, Size, Max) end. %% When port_close returns we know that all the diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl index 70752e6ee6..cfd475becf 100644 --- a/lib/kernel/test/os_SUITE.erl +++ b/lib/kernel/test/os_SUITE.erl @@ -324,8 +324,8 @@ max_size_command(_Config) -> Res32768 = os:cmd("cat /dev/zero", #{ max_size => 32768 }), 32768 = length(Res32768), - ResHello = os:cmd("echo hello", #{ max_size => 20 }), - 6 = length(ResHello). + ResHello = string:trim(os:cmd("echo hello", #{ max_size => 20 })), + 5 = length(ResHello). %% Test that the os:perf_counter api works as expected perf_counter_api(_Config) -> -- cgit v1.2.3 From bf4418b4bb9166a6a27937b53710d2b4d30925af Mon Sep 17 00:00:00 2001 From: Sverker Eriksson Date: Wed, 21 Mar 2018 17:51:39 +0100 Subject: kernel: Fix os_SUITE:max_size_command for OTP-19 where string:trim does not exist. --- lib/kernel/test/os_SUITE.erl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl index cfd475becf..d7f0463289 100644 --- a/lib/kernel/test/os_SUITE.erl +++ b/lib/kernel/test/os_SUITE.erl @@ -324,9 +324,17 @@ max_size_command(_Config) -> Res32768 = os:cmd("cat /dev/zero", #{ max_size => 32768 }), 32768 = length(Res32768), - ResHello = string:trim(os:cmd("echo hello", #{ max_size => 20 })), + ResHello = string_trim(os:cmd("echo hello", #{ max_size => 20 })), 5 = length(ResHello). +string_trim(S) -> + lists:reverse(string_trim_left(lists:reverse(string_trim_left(S)))). + +string_trim_left([C | T]) when C =:= $\s; C =:= $\n; C =:= $\t; C =:= $\r -> + string_trim_left(T); +string_trim_left(S) -> + S. + %% Test that the os:perf_counter api works as expected perf_counter_api(_Config) -> -- cgit v1.2.3