File 0514-kernel-Fix-os-cmd-error-report-for-open_port-errors.patch of Package erlang
From 10a928195c26a59249cb36a8f3e556fc39593f8e Mon Sep 17 00:00:00 2001
From: Lukas Larsson <lukas@erlang.org>
Date: Wed, 19 Oct 2022 14:11:06 +0200
Subject: [PATCH] kernel: Fix os:cmd error report for open_port errors
The error_info code for os:cmd inadvertenly hid errors
when open_port failed to start due to some reason, for
example out of file descriptors.
So we fix os:cmd and add tests to make sure that it works.
---
lib/kernel/src/erl_kernel_errors.erl | 16 +++-
lib/kernel/src/os.erl | 56 ++++++++-----
lib/kernel/test/Makefile | 10 ++-
lib/kernel/test/error_info_lib.erl | 115 ---------------------------
lib/kernel/test/os_SUITE.erl | 46 ++++++++++-
lib/stdlib/test/error_info_lib.erl | 18 ++++-
6 files changed, 115 insertions(+), 146 deletions(-)
delete mode 100644 lib/kernel/test/error_info_lib.erl
diff --git a/lib/kernel/src/erl_kernel_errors.erl b/lib/kernel/src/erl_kernel_errors.erl
index 376ce31f16..6c03047323 100644
--- a/lib/kernel/src/erl_kernel_errors.erl
+++ b/lib/kernel/src/erl_kernel_errors.erl
@@ -42,6 +42,8 @@ format_error(_Reason, [{M,F,As,Info}|_]) ->
format_erl_ddll_error(_, _, _) ->
[].
+format_os_error(cmd, _, {open_port, Reason}) ->
+ [{general, maybe_posix_message(Reason)}];
format_os_error(cmd, [_], _) ->
[not_charlist];
format_os_error(cmd, [_, _], Cause) ->
@@ -71,6 +73,14 @@ format_os_error(unsetenv, [Name], _) ->
format_os_error(_, _, _) ->
[].
+maybe_posix_message(Reason) ->
+ case erl_posix_msg:message(Reason) of
+ "unknown POSIX error" ->
+ io_lib:format("open_port failed with reason: ~tp",[Reason]);
+ PosixStr ->
+ io_lib:format("~ts (~tp)",[PosixStr, Reason])
+ end.
+
must_be_atom(Term, Default) when is_atom(Term) -> Default;
must_be_atom(_, _) -> not_atom.
@@ -129,6 +139,8 @@ must_be_env_charlist(_) ->
format_error_map([""|Es], ArgNum, Map) ->
format_error_map(Es, ArgNum + 1, Map);
+format_error_map([{general, E}|Es], ArgNum, Map) ->
+ format_error_map(Es, ArgNum, Map#{ general => expand_error(E)});
format_error_map([E|Es], ArgNum, Map) ->
format_error_map(Es, ArgNum + 1, Map#{ArgNum => expand_error(E)});
format_error_map([], _, Map) ->
@@ -155,4 +167,6 @@ expand_error(not_list) ->
expand_error(not_map) ->
<<"not a map">>;
expand_error(not_proper_list) ->
- <<"not a proper list">>.
+ <<"not a proper list">>;
+expand_error(Other) ->
+ Other.
diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl
index f4c5d01b05..dc73ca1556 100644
--- a/lib/kernel/src/os.erl
+++ b/lib/kernel/src/os.erl
@@ -258,9 +258,11 @@ extensions() ->
Command :: os_command().
cmd(Cmd) ->
try
- cmd(Cmd, #{ })
+ do_cmd(Cmd, #{ })
catch
- error:_ ->
+ throw:{open_port, Reason} ->
+ badarg_with_cause([Cmd], {open_port, Reason});
+ throw:badarg ->
badarg_with_info([Cmd])
end.
@@ -273,15 +275,20 @@ cmd(Cmd, Opts) ->
catch
throw:badopt ->
badarg_with_cause([Cmd, Opts], badopt);
- error:_ ->
+ throw:{open_port, Reason} ->
+ badarg_with_cause([Cmd, Opts], {open_port, Reason});
+ throw:badarg ->
badarg_with_info([Cmd, Opts])
end.
do_cmd(Cmd, Opts) ->
MaxSize = get_option(max_size, Opts, infinity),
{SpawnCmd, SpawnOpts, SpawnInput, Eot} = mk_cmd(os:type(), validate(Cmd)),
- Port = open_port({spawn, SpawnCmd}, [binary, stderr_to_stdout,
- stream, in, hide | SpawnOpts]),
+ Port = try open_port({spawn, SpawnCmd}, [binary, stderr_to_stdout,
+ stream, in, hide | SpawnOpts])
+ catch error:Reason ->
+ throw({open_port, Reason})
+ end,
MonRef = erlang:monitor(port, Port),
true = port_command(Port, SpawnInput),
Bytes = get_data(Port, MonRef, Eot, [], 0, MaxSize),
@@ -346,34 +353,39 @@ mk_cmd(_,Cmd) ->
["(", unicode:characters_to_binary(Cmd), "\n) </dev/null; echo \"\^D\"\n"],
<<$\^D>>}.
-validate(Atom) when is_atom(Atom) ->
- validate(atom_to_list(Atom));
-validate(List) when is_list(List) ->
- case validate1(List) of
+validate(Term) ->
+ try validate1(Term)
+ catch error:_ -> throw(badarg)
+ end.
+
+validate1(Atom) when is_atom(Atom) ->
+ validate1(atom_to_list(Atom));
+validate1(List) when is_list(List) ->
+ case validate2(List) of
false ->
List;
- true ->
+ true ->
%% Had zeros at end; remove them...
string:trim(List, trailing, [0])
end.
-validate1([0|Rest]) ->
+validate2([0|Rest]) ->
+ validate3(Rest);
+validate2([C|Rest]) when is_integer(C), C > 0 ->
validate2(Rest);
-validate1([C|Rest]) when is_integer(C), C > 0 ->
- validate1(Rest);
-validate1([List|Rest]) when is_list(List) ->
- validate1(List) or validate1(Rest);
-validate1([]) ->
+validate2([List|Rest]) when is_list(List) ->
+ validate2(List) or validate2(Rest);
+validate2([]) ->
false.
%% Ensure that the rest is zero only...
-validate2([]) ->
+validate3([]) ->
true;
-validate2([0|Rest]) ->
- validate2(Rest);
-validate2([List|Rest]) when is_list(List) ->
- validate2(List),
- validate2(Rest).
+validate3([0|Rest]) ->
+ validate3(Rest);
+validate3([List|Rest]) when is_list(List) ->
+ validate3(List),
+ validate3(Rest).
get_data(Port, MonRef, Eot, Sofar, Size, Max) ->
receive
diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile
index 130e626b56..f7776f44fe 100644
--- a/lib/kernel/test/Makefile
+++ b/lib/kernel/test/Makefile
@@ -70,7 +70,6 @@ MODULES= \
erl_prim_loader_SUITE \
erl_uds_dist \
error_handler_SUITE \
- error_info_lib \
error_logger_SUITE \
error_logger_warn_SUITE \
file_SUITE \
@@ -132,7 +132,11 @@ APP_FILES = \
topApp2.app \
topApp3.app
-ERL_FILES= $(MODULES:%=%.erl) code_a_test.erl
+STDLIB_MODULES= error_info_lib
+
+ERL_FILES= $(MODULES:%=%.erl) code_a_test.erl \
+ $(STDLIB_MODULES:%=$(ERL_TOP)/lib/stdlib/test/%.erl)
+
HRL_FILES= \
kernel_test_lib.hrl \
socket_test_evaluator.hrl \
@@ -176,7 +180,8 @@ erl_uds_dist.erl: ../examples/erl_uds_di
make_emakefile: $(ERL_FILES)
$(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) '*_SUITE_make' \
> $(EMAKEFILE)
- $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \
+ $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) \
+ $(MODULES) $(STDLIB_MODULES) \
>> $(EMAKEFILE)
tests debug opt: make_emakefile
diff --git a/lib/kernel/test/error_info_lib.erl b/lib/kernel/test/error_info_lib.erl
deleted file mode 100644
index d1afaf4589..0000000000
--- a/lib/kernel/test/error_info_lib.erl
+++ /dev/null
@@ -1,115 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2021. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
--module(error_info_lib).
--export([test_error_info/2, test_error_info/3]).
-
-test_error_info(Module, List) ->
- test_error_info(Module, List, []).
-
-test_error_info(Module, L0, Options) ->
- L1 = lists:foldl(fun({_,A}, Acc) when is_integer(A) -> Acc;
- ({F,A}, Acc) -> [{F,A,[]}|Acc];
- ({F,A,Opts}, Acc) -> [{F,A,Opts}|Acc]
- end, [], L0),
- Tests = ordsets:from_list([{F,length(A)} || {F,A,_} <- L1] ++
- [{F,A} || {F,A} <- L0, is_integer(A)]),
- Bifs0 = get_bifs(Module, Options),
- Bifs = ordsets:from_list(Bifs0),
- NYI = [{F,lists:duplicate(A, '*'),nyi} || {F,A} <- Bifs -- Tests],
- L = lists:sort(NYI ++ L1),
- do_error_info(L, Module, []).
-
-get_bifs(Module, Options) ->
- case lists:member(snifs_only, Options) of
- true ->
- [{F,A} || {M,F,A} <- erlang:system_info(snifs),
- M =:= Module,
- A =/= 0];
- false ->
- [{F,A} || {F,A} <- Module:module_info(exports),
- A =/= 0,
- F =/= module_info]
- end.
-
-do_error_info([{_,Args,nyi}=H|T], Module, Errors) ->
- case lists:all(fun(A) -> A =:= '*' end, Args) of
- true ->
- do_error_info(T, Module, [{nyi,H}|Errors]);
- false ->
- do_error_info(T, Module, [{bad_nyi,H}|Errors])
- end;
-do_error_info([{F,Args,Opts}|T], Module, Errors) ->
- eval_bif_error(F, Args, Opts, T, Module, Errors);
-do_error_info([], _Module, Errors0) ->
- case lists:sort(Errors0) of
- [] ->
- ok;
- [_|_]=Errors ->
- io:format("\n~p\n", [Errors]),
- ct:fail({length(Errors),errors})
- end.
-
-eval_bif_error(F, Args, Opts, T, Module, Errors0) ->
- try apply(Module, F, Args) of
- Result ->
- case lists:member(no_fail, Opts) of
- true ->
- do_error_info(T, Module, Errors0);
- false ->
- do_error_info(T, Module, [{should_fail,{F,Args},Result}|Errors0])
- end
- catch
- error:Reason:Stk ->
- SF = fun(Mod, _, _) -> Mod =:= test_server end,
- Str = erl_error:format_exception(error, Reason, Stk, #{stack_trim_fun => SF}),
- BinStr = iolist_to_binary(Str),
- ArgStr = lists:join(", ", [io_lib:format("~p", [A]) || A <- Args]),
- io:format("\n~p:~p(~s)\n~ts", [Module,F,ArgStr,BinStr]),
-
- case Stk of
- [{Module,ActualF,ActualArgs,Info}|_] ->
- RE = <<"[*][*][*] argument \\d+:">>,
- Errors1 = case re:run(BinStr, RE, [{capture, none}]) of
- match ->
- Errors0;
- nomatch when Reason =:= system_limit ->
- Errors0;
- nomatch ->
- case lists:member(unexplained, Opts) of
- true ->
- Errors0;
- false ->
- [{no_explanation,{F,Args},Info}|Errors0]
- end
- end,
-
- Errors = case {ActualF,ActualArgs} of
- {F,Args} ->
- Errors1;
- _ ->
- [{renamed,{F,length(Args)},{ActualF,ActualArgs}}|Errors1]
- end,
-
- do_error_info(T, Module, Errors);
- _ ->
- Errors = [{renamed,{F,length(Args)},hd(Stk)}|Errors0],
- do_error_info(T, Module, Errors)
- end
- end.
diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl
index a84acf3b34..fbae6892f6 100644
--- a/lib/kernel/test/os_SUITE.erl
+++ b/lib/kernel/test/os_SUITE.erl
@@ -396,10 +396,48 @@ do_perf_counter_test(CntArgs, Conv, Upper, Lower, Iters) ->
do_perf_counter_test(CntArgs, Conv, Upper, Lower, Iters-1)
end.
-error_info(_Config) ->
- L = [{cmd, [{no,string}]},
+error_info(Config) ->
+
+
+ ExhaustFDs =
+ fun(M,F,A) ->
+ case os:type() of
+ {unix, _} ->
+ {ok, Peer, Node} = ?CT_PEER(),
+ FN = filename:join(
+ proplists:get_value(priv_dir, Config),
+ "error_info"),
+ try
+ erpc:call(
+ Node,
+ fun() ->
+ io:format("Starting to open files..."),
+ (fun FDs(N) ->
+ case file:open(FN, [write]) of
+ {ok, _ } -> FDs(N+1);
+ {error, _} ->
+ io:format("Opened ~p files",[N])
+ end
+ end)(0),
+ apply(M,F,A)
+ end)
+ catch error:{exception, ErrorReason, StackTrace} ->
+ erlang:raise(error, ErrorReason, StackTrace)
+ after
+ peer:stop(Peer)
+ end;
+ _ ->
+ apply(M,F,A)
+ end
+ end,
+
+ L = [{cmd, [{no, string}]},
+ {cmd, [["echo 1",0,0,0,1]]},
{cmd, [{no, string}, #{}]},
{cmd, [{no, string}, no_map]},
+ {cmd, ["echo 1"], [{general, "too many open files \\(emfile\\)"},
+ {wrapper, ExhaustFDs}] ++
+ [no_fail || win32 =:= element(1, os:type())]},
{find_executable, 1}, %Not a BIF.
{find_executable, 2}, %Not a BIF.
@@ -416,7 +454,7 @@ error_info(_Config) ->
{perf_counter,[bad_time_unit]},
- {putenv, [<<"bad_key">>, <<"bad_value">>]},
+ {putenv, [<<"bad_key">>, <<"bad_value">>],[{1,".*"},{2,".*"}]},
{putenv, ["key", <<"bad_value">>]},
{putenv, [<<"bad_key">>, "value"]},
{putenv, ["abc=", "xyz"]},
@@ -424,7 +462,7 @@ error_info(_Config) ->
{set_signal, [{bad,signal}, ignore]},
{set_signal, [{bad,signal}, ignore]},
{set_signal, [bad_signal, bad_handling]},
- {set_signal, [{bad,signal}, bad_handling]},
+ {set_signal, [{bad,signal}, bad_handling],[{1,".*"},{2,".*"}]},
{system_time, [bad_time_unit]},
{unsetenv, [{bad,key}]}
diff --git a/lib/stdlib/test/error_info_lib.erl b/lib/stdlib/test/error_info_lib.erl
index 716fa47eb9..33b7ae4729 100644
--- a/lib/stdlib/test/error_info_lib.erl
+++ b/lib/stdlib/test/error_info_lib.erl
@@ -20,6 +20,21 @@
-module(error_info_lib).
-export([test_error_info/2, test_error_info/3]).
+%% The wrapper fun should behave as if it was apply/3.
+%% See os_SUITE for an example usage.
+-type wrapper() :: fun((module(),function(),list(term)) -> term()).
+
+%% Options that can be given to testcases
+-type option() :: {integer(), Regexp :: string()} | %% Match argument #1 against RegExp
+ {general, Regexp :: string()} | %% Match general info against RegExp
+ {wrapper, wrapper()} | %% Wrap the test call using this fun
+ {gl, pid()} | %% Use this group leader for the test
+ no_fail | %% The test will not fail
+ allow_rename | %% Allow the exception to not originate from Func
+ unexplained. %% Allow the test to not provide any explanation
+-type test() :: {Func :: function(), Args :: [term()]} |
+ {Func :: function(), Args :: [term()], Opts :: list(option())}.
+-spec test_error_info(module(), list(test())) -> ok.
test_error_info(Module, List) ->
test_error_info(Module, List, []).
@@ -75,7 +90,8 @@ do_error_info([], _Module, Errors0) ->
eval_bif_error(F, Args, Opts, T, Module, Errors0) ->
OldGl = group_leader(),
group_leader(proplists:get_value(gl, Opts, OldGl), self()),
- try apply(Module, F, Args) of
+ Wrapper = proplists:get_value(wrapper, Opts, fun(M, Fun, A) -> apply(M, Fun, A) end),
+ try Wrapper(Module, F, Args) of
Result ->
group_leader(OldGl, self()),
case lists:member(no_fail, Opts) of
--
2.35.3