Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ERL_SYS_SHELL env variable to point to system shell for os:cmd() #8972

Merged
merged 3 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/kernel/doc/kernel_app.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,10 @@ For more information about configuration parameters, see file
[Escripts and non-interactive I/O in Unicode Usage in Erlang](`e:stdlib:unicode_usage.md#escripts-and-non-interactive-i-o`)
for more details.

- **`os_cmd_shell = string()`{: #os_cmd_shell }** - Specifies which shell to
use when invoking system commands via `os:cmd/2`. By default the shell is detected
automatically.

## Deprecated Configuration Parameters

In Erlang/OTP 21.0, a new API for logging was added. The old `error_logger`
Expand Down
2 changes: 2 additions & 0 deletions lib/kernel/src/kernel.erl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
start(_, []) ->
%% Setup the logger and configure the kernel logger environment
ok = logger:internal_init_logger(),
ok = os:internal_init_cmd_shell(),
case supervisor:start_link({local, kernel_sup}, kernel, []) of
{ok, Pid} ->
ok = erl_signal_handler:start(),
Expand All @@ -48,6 +49,7 @@ stop(_State) ->
%% Some configuration parameters for kernel are changed
%%-------------------------------------------------------------------
config_change(Changed, New, Removed) ->
ok = os:internal_init_cmd_shell(),
do_distribution_change(Changed, New, Removed),
do_global_groups_change(Changed, New, Removed),
ok.
Expand Down
73 changes: 48 additions & 25 deletions lib/kernel/src/os.erl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ a program to run on most platforms.

-export([type/0, version/0, cmd/1, cmd/2, find_executable/1, find_executable/2]).

-export([internal_init_cmd_shell/0]).

-include("file.hrl").

-export_type([env_var_name/0, env_var_value/0, env_var_name_value/0]).
Expand Down Expand Up @@ -518,6 +520,10 @@ cmd(Cmd) ->
Executes `Command` in a command shell of the target OS, captures the standard
output and standard error of the command, and returns this result as a string.
yarisx marked this conversation as resolved.
Show resolved Hide resolved

The command shell can be set using the
[kernel configuration parameter](kernel_app.md#os_cmd_shell), by default the
shell is detected upon system startup.

_Examples:_

```erlang
Expand Down Expand Up @@ -581,35 +587,16 @@ get_option(Opt, Options, Default) ->
_ -> throw(badopt)
end.

mk_cmd({win32,Wtype}, Cmd) ->
Command = case {os:getenv("COMSPEC"),Wtype} of
{false,windows} -> lists:concat(["command.com /c", Cmd]);
{false,_} -> lists:concat(["cmd /c", Cmd]);
{Cspec,_} -> lists:concat([Cspec," /c",Cmd])
end,
-define(KERNEL_OS_CMD_SHELL_KEY, kernel_os_cmd_shell).

mk_cmd({win32,_}, Cmd) ->
Shell = persistent_term:get(?KERNEL_OS_CMD_SHELL_KEY),
Command = lists:concat([Shell, " /c", Cmd]),
{Command, [], [], <<>>};
mk_cmd(_,Cmd) ->
%% Have to send command in like this in order to make sh commands like
%% cd and ulimit available.
%%
%% We use an absolute path here because we do not want the path to be
%% searched in case a stale NFS handle is somewhere in the path before
%% the sh command.
%%
%% Check if the default shell is located in /bin/sh as expected usually
%% or in /system/bin/sh as implemented on Android. The raw option is
%% used to bypass the file server and speed up the file access.
Shell = case file:read_file_info("/bin/sh",[raw]) of
{ok,#file_info{type=regular}} ->
"/bin/sh";
_ ->
case file:read_file_info("/system/bin/sh",[raw]) of
{ok,#file_info{type=regular}} ->
"/system/bin/sh";
_ ->
"/bin/sh"
end
end,
Shell = persistent_term:get(?KERNEL_OS_CMD_SHELL_KEY),
{Shell ++ " -s unix:cmd", [out],
%% We insert a new line after the command, in case the command
%% contains a comment character.
Expand All @@ -628,6 +615,42 @@ mk_cmd(_,Cmd) ->
["(", unicode:characters_to_binary(Cmd), "\n) </dev/null; echo \"\^D\"\n"],
<<$\^D>>}.

-doc false.
internal_init_cmd_shell() ->
Shell =
case application:get_env(kernel, os_cmd_shell) of
undefined ->
internal_init_cmd_shell(os:type());
{ok, Val} ->
Val
end,
persistent_term:put(?KERNEL_OS_CMD_SHELL_KEY, Shell).
internal_init_cmd_shell({win32,Wtype}) ->
case {os:getenv("COMSPEC"),Wtype} of
{false,windows} -> "command.com";
{false,_} -> "cmd";
{Cspec,_} -> Cspec
end;
internal_init_cmd_shell(_) ->
%% We use an absolute path here because we do not want the path to be
%% searched in case a stale NFS handle is somewhere in the path before
%% the sh command.
%%
%% Check if the default shell is located in /bin/sh as expected usually
%% or in /system/bin/sh as implemented on Android. The raw option is
%% used to bypass the file server.
case file:read_file_info("/bin/sh",[raw]) of
{ok,#file_info{type=regular}} ->
"/bin/sh";
_ ->
case file:read_file_info("/system/bin/sh",[raw]) of
{ok,#file_info{type=regular}} ->
"/system/bin/sh";
_ ->
"/bin/sh"
end
end.

validate(Term) ->
try validate1(Term)
catch error:_ -> throw(badarg)
Expand Down
28 changes: 26 additions & 2 deletions lib/kernel/test/os_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
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, max_size_command/1,
perf_counter_api/1, error_info/1]).
perf_counter_api/1, error_info/1, os_cmd_shell/1,os_cmd_shell_peer/1]).

-include_lib("common_test/include/ct.hrl").

Expand All @@ -43,7 +43,7 @@ all() ->
find_executable, unix_comment_in_command, deep_list_command,
large_output_command, background_command, message_leak,
close_stdin, max_size_command, perf_counter_api,
error_info].
error_info, os_cmd_shell, os_cmd_shell_peer].

groups() ->
[].
Expand Down Expand Up @@ -469,6 +469,30 @@ error_info(Config) ->
],
error_info_lib:test_error_info(os, L).

%% Check that is *not* possible to change shell after startup
os_cmd_shell(_Config) ->

application:set_env(kernel, os_cmd_shell, "broken shell"),

%% os:cmd should continue to work as normal
comp("hello", os:cmd("echo hello")).

%% When started with os_cmd_shell set, we make sure that it is used.
os_cmd_shell_peer(Config) ->
DataDir = proplists:get_value(data_dir, Config),
SysShell = "\"" ++ filename:join(DataDir, "sys_shell") ++ "\"",
{ok, Peer, Node} = ?CT_PEER(["-kernel","os_cmd_shell", SysShell]),
try erpc:call(Node, os, cmd, ["ls"], rtnode:timeout(normal)) of
"sys_shell" -> ok;
Other -> ct:fail({unexpected, Other})
catch
C:R:Stk ->
io:format("~p\n~p\n~p\n", [C,R,Stk]),
ct:fail(failed)
after
peer:stop(Peer)
end.

no_limit_for_opened_files() ->
case os:type() of
{unix, freebsd} ->
Expand Down
8 changes: 7 additions & 1 deletion lib/kernel/test/os_SUITE_data/Makefile.src
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ LD = @LD@
CFLAGS = @CFLAGS@ -I@erl_include@ @DEFS@
CROSSLDFLAGS = @CROSSLDFLAGS@

PROGS = my_echo@exe@ my_fds@exe@
PROGS = my_echo@exe@ my_fds@exe@ sys_shell@exe@

all: $(PROGS)

Expand All @@ -18,3 +18,9 @@ my_fds@exe@: my_fds@obj@

my_fds@obj@: my_fds.c
$(CC) -c -o my_fds@obj@ $(CFLAGS) my_fds.c

sys_shell@exe@: sys_shell@obj@
$(LD) $(CROSSLDFLAGS) -o sys_shell sys_shell@obj@ @LIBS@

sys_shell@obj@: sys_shell.c
$(CC) -c -o sys_shell@obj@ $(CFLAGS) sys_shell.c
6 changes: 6 additions & 0 deletions lib/kernel/test/os_SUITE_data/sys_shell.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include <stdio.h>
int main(void)
{
printf("sys_shell");
return 0;
}
Loading