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