From 2b8bacb4ee11c737aeb2db51859a0da47d2399b2 Mon Sep 17 00:00:00 2001 From: Michael Santos Date: Wed, 18 Apr 2018 08:01:11 -0400 Subject: [PATCH] stdout/2,3, stderr/2,3: return list of binaries Change stdout/2,3 and stderr/2,3 to return a list of values as discussed in: https://github.com/msantos/alcove/issues/3 The spec is changed from: % from -spec stdout(alcove_drv:ref(),[pid_t()]) -> 'false' | binary(). % to: [] indicates no stdout -spec stdout(alcove_drv:ref(),[pid_t()]) -> [binary()]. In the case where an unreponsive child causes the pipe buffer between the alcove parent process and the child to fill up, stdout/2,3 and stderr/2,3 will now both crash. If the caller wants to handle this situation, e.g., to wait and retry, the `alcove_pipe` message can be processed directly from the mailbox. Thanks @neeraj9! --- README.md | 14 +++++++------- bin/alcove.escript | 32 +++++++++++++++++++++++--------- src/alcove.app.src | 2 +- test/alcove_SUITE.erl | 18 +++++++++--------- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 2758421..37648b1 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ We can interact with the process via stdin, stdout and stderr: ```erlang alcove:stdin(Drv, [Child1,Child2], "hello process\n"), -<<"hello process\n">> = alcove:stdout(Drv, [Child1,Child2]). +[<<"hello process\n">>] = alcove:stdout(Drv, [Child1,Child2]). ``` Setting Up Privileges @@ -215,7 +215,7 @@ rebar shell 3> alcove:stdin(Drv, [Cat], "test test\n"). 4> alcove:stdout(Drv, [Cat]). -<<"test test\n">> +[<<"test test\n">>] ``` We can test the limits of the sandbox by using a shell instead of @@ -229,18 +229,18 @@ herding cat's: 6> alcove:stdin(P, [Sh], "echo hello\n"). ok 7> alcove:stdout(P, [Sh]). -<<"hello\n">> +[<<"hello\n">>] % Attempt to create a file 6> alcove:stdin(Drv, [Sh], "> foo\n"). ok 7> alcove:stderr(P, [Sh]). -<<"sh: can't create foo: Too many open files\n">> +[<<"sh: can't create foo: Too many open files\n">>] % Try to fork a new process 8> alcove:stdin(Drv, [Sh], "ls\n"). 9> alcove:stderr(P, [Sh]). -<<"sh: can't fork\n">> +[<<"sh: can't fork\n">>] % If we check the parent for events, we can see the child has exited 10> alcove:event(P, []). @@ -1126,11 +1126,11 @@ exec(3) has been called. Send data to stdin of the process. - stdout(Drv, ForkChain) -> binary() | false + stdout(Drv, ForkChain) -> [binary()] Read stdout from the process. - stderr(Drv, ForkChain) -> binary() | false + stderr(Drv, ForkChain) -> [binary()] Read stderr from the process. diff --git a/bin/alcove.escript b/bin/alcove.escript index 9270cac..3b59cb9 100755 --- a/bin/alcove.escript +++ b/bin/alcove.escript @@ -257,7 +257,9 @@ stdin(Drv, Pids, Data) -> ok -> ok; {alcove_error, Error} -> - erlang:error(Error, [Drv, Pids, Data]) + erlang:error(Error, [Drv, Pids, Data]); + {alcove_pipe, Error} -> + {error, Error} end. "; @@ -269,12 +271,18 @@ stdout(Drv, Pids) -> static({stdout,3}) -> " stdout(Drv, Pids, Timeout) -> + stdout_1(Drv, Pids, Timeout, []). + +stdout_1(Drv, Pids, Timeout, Acc) -> case alcove_drv:stdout(Drv, Pids, Timeout) of + false -> + lists:reverse(Acc); {alcove_error, Error} -> erlang:error(Error, [Drv, Pids, Timeout]); {alcove_pipe, Error} -> - {error, Error}; - Reply -> Reply + erlang:error(Error, [Drv, Pids, Timeout]); + Reply -> + stdout_1(Drv, Pids, Timeout, [Reply|Acc]) end. "; @@ -286,12 +294,18 @@ stderr(Drv, Pids) -> static({stderr,3}) -> " stderr(Drv, Pids, Timeout) -> + stderr_1(Drv, Pids, Timeout, []). + +stderr_1(Drv, Pids, Timeout, Acc) -> case alcove_drv:stderr(Drv, Pids, Timeout) of + false -> + lists:reverse(Acc); {alcove_error, Error} -> erlang:error(Error, [Drv, Pids, Timeout]); {alcove_pipe, Error} -> - {error, Error}; - Reply -> Reply + erlang:error(Error, [Drv, Pids, Timeout]); + Reply -> + stderr_1(Drv, Pids, Timeout, [Reply|Acc]) end. "; @@ -706,13 +720,13 @@ specs() -> -spec syscall_constant(alcove_drv:ref(),[pid_t()],atom()) -> 'unknown' | non_neg_integer(). -spec syscall_constant(alcove_drv:ref(),[pid_t()],atom(),timeout()) -> 'unknown' | non_neg_integer(). --spec stderr(alcove_drv:ref(),[pid_t()]) -> 'false' | binary(). --spec stderr(alcove_drv:ref(),[pid_t()],timeout()) -> 'false' | binary(). +-spec stderr(alcove_drv:ref(),[pid_t()]) -> [binary()]. +-spec stderr(alcove_drv:ref(),[pid_t()],timeout()) -> [binary()]. -spec stdin(alcove_drv:ref(),[pid_t()],iodata()) -> 'ok'. --spec stdout(alcove_drv:ref(),[pid_t()]) -> 'false' | binary(). --spec stdout(alcove_drv:ref(),[pid_t()],timeout()) -> 'false' | binary(). +-spec stdout(alcove_drv:ref(),[pid_t()]) -> [binary()]. +-spec stdout(alcove_drv:ref(),[pid_t()],timeout()) -> [binary()]. -spec symlink(alcove_drv:ref(),[pid_t()],iodata(),iodata()) -> 'ok' | {error, posix()}. -spec symlink(alcove_drv:ref(),[pid_t()],iodata(),iodata(),timeout()) -> 'ok' | {error, posix()}. diff --git a/src/alcove.app.src b/src/alcove.app.src index c5cc1fb..1054455 100644 --- a/src/alcove.app.src +++ b/src/alcove.app.src @@ -1,6 +1,6 @@ {application,alcove, [{description,"Unix process containers and sandboxes"}, - {vsn,"0.23.0"}, + {vsn,"0.24.0"}, {registered,[]}, {applications,[kernel,stdlib,sasl]}, {env,[]}, diff --git a/test/alcove_SUITE.erl b/test/alcove_SUITE.erl index c1856bc..9dd264a 100644 --- a/test/alcove_SUITE.erl +++ b/test/alcove_SUITE.erl @@ -846,8 +846,8 @@ execve(Config) -> ok = alcove:execve(Drv, [Child1], "/usr/bin/env", ["/usr/bin/env"], []), - <<"FOO=bar\nBAR=1234567\n">> = alcove:stdout(Drv, [Child0], 5000), - false = alcove:stdout(Drv, [Child1], 2000). + [<<"FOO=bar\nBAR=1234567\n">>] = alcove:stdout(Drv, [Child0], 5000), + [] = alcove:stdout(Drv, [Child1], 2000). execvp_with_signal(Config) -> Drv = ?config(drv, Config), @@ -1048,7 +1048,7 @@ execvp_mid_chain(Config) -> ok = alcove:execvp(Drv, Pids, "/bin/cat", ["/bin/cat"]), alcove:stdin(Drv, Pids, "test\n"), - <<"test\n">> = alcove:stdout(Drv, Pids, 5000), + [<<"test\n">>] = alcove:stdout(Drv, Pids, 5000), false = alcove:event(Drv, Chain, 2000), Reply = [ alcove:kill(Drv, [], Pid, 0) || Pid <- Rest ], @@ -1072,7 +1072,7 @@ stdout(Config) -> ok = alcove:execvp(Drv, [Child], "/bin/sh", ["/bin/sh"]), ok = alcove:stdin(Drv, [Child], "echo 0123456789\n"), - <<"0123456789\n">> = alcove:stdout(Drv, [Child], 5000). + [<<"0123456789\n">>] = alcove:stdout(Drv, [Child], 5000). stderr(Config) -> Drv = ?config(drv, Config), @@ -1086,9 +1086,9 @@ stderr(Config) -> case OS of N when N =:= linux; N =:= openbsd; N =:= freebsd; N =:= darwin -> - <<"/bin/sh: ", _/binary>> = Reply; + [<<"/bin/sh: ", _/binary>>] = Reply; netbsd -> - <<"nonexistent: not found\n">> = Reply; + [<<"nonexistent: not found\n">>] = Reply; _ -> {skip, "stderr test not supported on this platform"} end. @@ -1113,11 +1113,11 @@ pipe_buf(Config) -> ok = alcove:stdin(Drv, [Child], Stdin), timer:sleep(1000), % XXX allow time for the message to be received - {error, {eagain,_}} = alcove:stdout(Drv, [Child]), + {'EXIT',{{eagain,0},_}} = (catch alcove:stdout(Drv, [Child])), ok = alcove:stdin(Drv, [Child], Stdin), timer:sleep(1000), % XXX allow time for the message to be received - {error, {eagain,_}} = alcove:stderr(Drv, [Child]), + {'EXIT',{{eagain,0},_}} = (catch alcove:stderr(Drv, [Child])), alcove:kill(Drv, [], Child, 9), @@ -1135,7 +1135,7 @@ fexecve(Config) -> {ok, FD} = alcove:open(Drv, [Child], "/usr/bin/env", [o_rdonly,o_cloexec], 0), ok = alcove:fexecve(Drv, [Child], FD, [""], ["FOO=bar", "BAR=1234567"]), - <<"FOO=bar\nBAR=1234567\n">> = alcove:stdout(Drv, [Child], 5000). + [<<"FOO=bar\nBAR=1234567\n">>] = alcove:stdout(Drv, [Child], 5000). %% %% Linux