%%% @copyright 2015-2022 Michael Santos <michael.santos@gmail.com>
%%% Permission to use, copy, modify, and/or distribute this software for any
%%% purpose with or without fee is hereby granted, provided that the above
%%% copyright notice and this permission notice appear in all copies.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(prx).
-behaviour(gen_statem).
-include_lib("alcove/include/alcove.hrl").
-export([
task/3, task/4,
fork/0, fork/1,
clone/2,
execvp/2, execvp/3,
execve/3, execve/4,
fexecve/4,
call/3,
stdin/2,
stop/1,
start_link/1
]).
% Utilities
-export([
controlling_process/2,
stdio/2,
reexec/1, reexec/3,
replace_process_image/1, replace_process_image/3,
sh/2,
cmd/2
]).
% FSM state
-export([
pidof/1,
cpid/2,
eof/1, eof/2, eof/3,
pipeline/1,
drv/1,
execed/1,
atexit/2,
sudo/0, sudo/1
]).
% Call wrappers
-export([
setproctitle/2,
setrlimit/3,
getrlimit/2,
select/5,
cpid/1,
parent/1,
cap_enter/1,
cap_fcntls_get/2,
cap_fcntls_limit/3,
cap_getmode/1,
cap_ioctls_limit/3,
cap_rights_limit/3,
chdir/2,
chmod/3,
chown/4,
chroot/2,
clearenv/1,
close/2,
environ/1,
exit/2,
fcntl/3, fcntl/4,
filter/2, filter/3,
getcpid/2, getcpid/3,
getcwd/1,
getenv/2,
getgid/1,
getgroups/1,
gethostname/1,
getopt/2,
getpgrp/1,
getpid/1,
getpriority/3,
getresgid/1,
getresuid/1,
getsid/2,
getuid/1,
ioctl/4,
jail/2,
kill/3,
lseek/4,
mkdir/3,
mkfifo/3,
mount/6, mount/7,
open/3, open/4,
pivot_root/3,
pledge/3,
prctl/6,
procctl/5,
ptrace/5,
read/3,
readdir/2,
rmdir/2,
seccomp/4,
setcpid/3, setcpid/4,
setenv/4,
setgid/2,
setgroups/2,
sethostname/2,
setns/2, setns/3,
setopt/3,
setpgid/3,
setpriority/4,
setresgid/4,
setresuid/4,
setsid/1,
setuid/2,
sigaction/3,
socket/4,
umount/2,
umount2/3,
unlink/2,
unsetenv/2,
unshare/2,
unveil/3,
waitpid/3,
write/3
]).
% States
-export([
call_state/3,
exec_state/3
]).
% Behaviours
-export([init/1, callback_mode/0, terminate/3, code_change/4]).
-export_type([
call/0,
constant/0,
cpid/0,
cstruct/0,
fd/0,
gid_t/0,
int32_t/0,
int64_t/0,
mode_t/0,
off_t/0,
pid_t/0,
posix/0,
prx_opt/0,
ptr_arg/0,
ptr_val/0,
size_t/0,
ssize_t/0,
task/0,
uid_t/0,
uint32_t/0,
uint64_t/0,
waitstatus/0
]).
-type call() ::
alcove_proto:call()
| reexec
| replace_process_image
| getcpid.
-type task() :: pid().
-type uint32_t() :: 0..16#ffffffff.
-type uint64_t() :: 0..16#ffffffffffffffff.
-type int32_t() :: -16#7fffffff..16#7fffffff.
-type int64_t() :: -16#7fffffffffffffff..16#7fffffffffffffff.
-type pid_t() :: int32_t().
-type fd() :: int32_t().
-type mode_t() :: uint32_t().
-type uid_t() :: uint32_t().
-type gid_t() :: uint32_t().
-type off_t() :: uint64_t().
-type size_t() :: uint64_t().
-type ssize_t() :: int64_t().
-type constant() :: atom() | integer().
-type posix() :: alcove:posix().
-type cstruct() :: nonempty_list(binary() | {ptr, binary() | non_neg_integer()}).
-type ptr_arg() :: binary() | constant() | cstruct().
-type ptr_val() :: binary() | integer() | cstruct().
-type prx_opt() ::
maxchild
| exit_status
| maxforkdepth
| termsig
| flowcontrol
| signaloneof.
-type waitstatus() ::
{exit_status, int32_t()}
| {termsig, atom()}
| {stopsig, atom()}
| continued.
-type cpid() :: #{
pid := pid_t(),
flowcontrol := uint32_t(),
signaloneof := uint32_t(),
exec := boolean(),
fdctl := fd(),
stdin := fd(),
stdout := fd(),
stderr := fd()
}.
-record(state, {
owner :: pid(),
stdio :: pid(),
drv :: pid(),
pipeline :: [pid_t()],
parent = noproc :: task() | noproc,
children = #{} :: #{} | #{pid() => pid_t()},
sigaction = #{} :: #{} | #{atom() => fun((pid(), [pid_t()], atom(), binary()) -> any())},
atexit = fun(Drv, Pipeline, Pid) ->
prx_drv:call(Drv, Pipeline, close, [maps:get(stdout, Pid)]),
prx_drv:call(Drv, Pipeline, close, [maps:get(stdin, Pid)]),
prx_drv:call(Drv, Pipeline, close, [maps:get(stderr, Pid)])
end :: fun((pid(), [pid_t()], cpid()) -> any())
}).
-define(SIGREAD_FILENO, 3).
-define(SIGWRITE_FILENO, 4).
-define(FDCTL_FILENO, 5).
-define(FD_SET, [?SIGREAD_FILENO, ?SIGWRITE_FILENO, ?FDCTL_FILENO]).
-define(PRX_CALL(Task_, Call_, Argv_),
case gen_statem:call(Task_, {Call_, Argv_}, infinity) of
{prx_error, Error_} ->
erlang:error(Error_, [Task_ | Argv_]);
{error, undef} ->
% reply from fork, clone when restricted by filter/1
erlang:error(undef, [Task_ | Argv_]);
Error_ when Error_ =:= badarg; Error_ =:= undef ->
erlang:error(Error_, [Task_ | Argv_]);
Reply_ ->
Reply_
end
).
%%
%% Spawn a new task
%%
%% @doc fork(2): create a new system process
%%
%% The behaviour of the process can be controlled by setting the
%% application environment:
%%
%% ```
%% Option = {exec, string()}
%% | {progname, string()}
%% | {ctldir, string()}
%% '''
%%
%% • `{exec, Exec}'
%%
%% Default: ""
%%
%% Sets a command to run the port under such as sudo or valgrind.
%%
%% For example, to start the process as root using `sudo', allow running
%% `prx' as root:
%%
%% ```
%% sudo visudo -f /etc/sudoers.d/99_prx
%% <user> ALL = NOPASSWD: /path/to/prx/priv/prx
%% Defaults!/path/to/prx/priv/prx !requiretty
%% '''
%%
%% Then:
%%
%% ```
%% application:set_env(prx, options, [{exec, "sudo -n"}])
%% '''
%%
%% • `{progname, Path}'
%%
%% Default: priv/prx
%%
%% Sets the path to the prx executable.
%%
%% • `{ctldir, Path}'
%%
%% Default: priv
%%
%% A control directory writable by the prx port process (the Unix
%% process may be running under a different user than the Erlang VM).
%%
%% The control directory contains a FIFO shared by beam and the port
%% process which is used to notify the Erlang VM that the port process
%% has called exec().
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.187.0>}
%% '''
-spec fork() -> {ok, task()} | {error, posix()}.
fork() ->
start_link(self()).
%% @doc fork(2): create a child process
%%
%% Forks child processes from an existing task. For example:
%%
%% ```
%% {ok, Task} = prx:fork(), % PID 16341
%% {ok, Child1} = prx:fork(Task), % PID 16349
%% {ok, Child2} = prx:fork(Task), % PID 16352
%% {ok, Child2a} = prx:fork(Child2), % PID 16354
%% {ok, Child2aa} = prx:fork(Child2a), % PID 16357
%% {ok, Child2ab} = prx:fork(Child2a). % PID 16482
%% '''
%%
%% Results in a process tree:
%%
%% ```
%% prx(16341)-+-prx(16349)
%% `-prx(16352)---prx(16354)-+-prx(16357)
%% `-prx(16482)
%% '''
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.187.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.191.0>}
%% 3> prx:cpid(Task).
%% [#{exec => false,fdctl => 8,flowcontrol => -1,pid => 8098,
%% signaloneof => 15,stderr => 13,stdin => 10,stdout => 11}]
%% '''
-spec fork(task()) -> {ok, task()} | {error, posix()}.
fork(Task) when is_pid(Task) ->
?PRX_CALL(Task, fork, []).
%% @doc clone(2): create a new process
%%
%% == Support ==
%%
%% • Linux
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 3> {ok, Task1} = prx:clone(Task, [clone_newns, clone_newpid, clone_newipc, clone_newuts, clone_newnet]).
%% {ok,<0.184.0>}
%% 4> prx:getpid(Task1).
%% 1
%% '''
-spec clone(task(), Flags :: [constant()]) -> {ok, task()} | {error, posix()}.
clone(Task, Flags) when is_pid(Task) ->
?PRX_CALL(Task, clone, [Flags]).
%% @doc Fork a subprocess and run a sequence of operations
%%
%% task/3 uses `fork/1' to create a new subprocess and run a sequence
%% of system calls. If an operations fails, the subprocess is sent SIGKILL
%% and exits.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.349.0>}
%% 2> {ok, Task1} = prx:task(Task, [
%% 2> {chdir, ["/"]},
%% 2> {setrlimit, [rlimit_core, #{cur => 0, max => 0}]},
%% 2> {prx, chdir, ["/nonexistent"], [{errexit, false}]}
%% 2> ], []).
%% {ok,<0.382.0>}
%% 3> prx:getrlimit(Task1, rlimit_core).
%% {ok,#{cur => 0,max => 0}}
%% 4> prx:getcwd(Task1).
%% {ok,<<"/">>}
%% '''
%%
%% @see task/4
-spec task(task(), Ops :: [prx_task:op() | [prx_task:op()]], State :: any()) ->
{ok, task()} | {error, posix()}.
task(Task, Ops, State) ->
task(Task, Ops, State, []).
%% @doc Create a subprocess and run a sequence of operations using optional
%% function calls
%%
%% task/4 calls the optional `init' function provided in the `Config'
%% argument to create a new subprocess. The default `init' function uses
%% `fork/1'.
%%
%% The subprocess next performs a list of operations. Operations are
%% tuples consisting of:
%%
%% * the module name: optional if modifier is not present, defaults to `prx'
%%
%% * the module function
%%
%% * function arguments
%%
%% * modifier list
%%
%% ```
%% [
%% % equivalent to prx:setresgid(65534, 65534, 65534)
%% {setresgid, [65534, 65534, 65534]},
%%
%% % equivalent to prx:setresuid(65534, 65534, 65534), error is ignored
%% {prx, setresuid, [65534, 65534, 65534], [{errexit, false}]},
%% ]
%% '''
%%
%% If an operation returns `{error, term()}', the sequence of operations
%% is aborted and the `terminate' function is run. The default `terminate'
%% functions signals the subprocess with SIGKILL.
%%
%% == Examples ==
%%
%% ```
%% 1> Init = fun(Parent) ->
%% 1> prx:clone(Parent, [
%% 1> clone_newnet,
%% 1> clone_newuser
%% 1> ])
%% 1> end.
%% #Fun<erl_eval.44.65746770>
%% 2> Terminate = fun(Parent, Child) ->
%% 2> prx:stop(Child),
%% 2> prx:kill(Parent, prx:pidof(Child), sigterm)
%% 2> end.
%% #Fun<erl_eval.43.65746770>
%% 3> {ok, Task} = prx:fork().
%% 4> {ok, Task1} = prx:task(Task, [
%% 4> {chdir, ["/"]},
%% 4> {setrlimit, [rlimit_core, #{cur => 0, max => 0}]},
%% 4> {prx, chdir, ["/nonexistent"], [{errexit, false}]}
%% 4> ], [], [
%% 4> {init, Init},
%% 4> {terminate, Terminate}
%% 4> ]).
%% {ok,<0.398.0>}
%% 5> prx:execvp(Task1, ["ip", "a"]).
%% ok
%% 27> flush().
%% Shell got {stdout,<0.398.0>,
%% <<"1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000\n link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00\n">>}
%% Shell got {exit_status,<0.398.0>,0}
%% ok
%% '''
%%
%% @see prx_task
-spec task(
task(), Ops :: [prx_task:op() | [prx_task:op()]], State :: any(), Config :: [prx_task:config()]
) ->
{ok, task()} | {error, posix()}.
task(Task, Ops, State, Config) ->
prx_task:do(Task, Ops, State, Config).
%% @doc Terminate the task
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.184.0>}
%% 4> prx:stop(Task1).
%% ok
%% 5> prx:cpid(Task).
%% []
%% '''
-spec stop(task()) -> ok.
stop(Task) ->
catch gen_statem:stop(Task),
ok.
%% @private
-spec start_link(pid()) -> {ok, task()} | {error, posix()}.
start_link(Owner) ->
case gen_statem:start_link(?MODULE, [Owner, init], []) of
{ok, _} = Ok -> Ok;
{error, _} = Error -> Error;
_ -> {error, eagain}
end.
%%
%% call mode: request the task perform operations
%%
%% @doc Make a synchronous call into the port driver
%%
%% The list of available calls and their arguments can be found here:
%%
%% [https://hexdocs.pm/alcove/alcove.html#functions]
%%
%% For example, to directly call `alcove:execve/5':
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> prx:call(Task, execve, ["/bin/ls", ["/bin/ls", "-al"], ["HOME=/home/foo"]]).
%% ok
%% '''
-spec call(task(), call(), [any()]) -> any().
call(_Task, fork, _Argv) ->
{error, eagain};
call(_Task, clone, _Argv) ->
{error, eagain};
call(Task, Call, Argv) ->
?PRX_CALL(Task, Call, Argv).
%%
%% exec mode: replace the process image, stdio is now a stream
%%
%% @doc execvp(2): replace the current process image using the search path
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.194.0>}
%% 3> prx:execvp(Task1, ["cat", "-n"]).
%% ok
%% 4> prx:stdin(Task1, <<"test\n">>).
%% ok
%% 5> flush().
%% Shell got {stdout,<0.194.0>,<<" 1\ttest\n">>}
%% ok
%% '''
-spec execvp(task(), [iodata()]) -> ok | {error, posix()}.
execvp(Task, [Arg0 | _] = Argv) when is_list(Argv) ->
?PRX_CALL(Task, execvp, [Arg0, Argv]).
%% @doc execvp(2): replace the current process image using the search path
%%
%% == Examples ==
%%
%% Set the command name in the process list:
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> prx:execvp(Task, "cat", ["name-in-process-list", "-n"])
%% ok
%% '''
-spec execvp(task(), iodata(), [iodata()]) -> ok | {error, posix()}.
execvp(Task, Arg0, Argv) when is_list(Argv) ->
?PRX_CALL(Task, execvp, [Arg0, Argv]).
%% @doc execve(2): replace process image with environment
%%
%% Replace the process image, specifying the environment for the new
%% process image.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> prx:execvp(Task, "cat", ["name-in-process-list", "-n"])
%% ok
%% '''
-spec execve(task(), [iodata()], [iodata()]) -> ok | {error, posix()}.
execve(Task, [Arg0 | _] = Argv, Env) when is_list(Argv), is_list(Env) ->
?PRX_CALL(Task, execve, [Arg0, Argv, Env]).
%% @doc execve(2): replace process image with environment
%%
%% Replace the process image, specifying the environment for the new
%% process image.
%%
%% == Examples ==
%%
%% Set the command name in the process list:
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> prx:execve(Task, "/bin/cat", ["name-in-process-list", "-n"], ["VAR=1"]).
%% ok
%% '''
-spec execve(task(), iodata(), [iodata()], [iodata()]) -> ok | {error, posix()}.
execve(Task, Arg0, Argv, Env) when is_list(Argv), is_list(Env) ->
?PRX_CALL(Task, execve, [Arg0, Argv, Env]).
%% @doc fexecve(2): replace the process image
%%
%% Replace the process image, specifying the environment for the new process
%% image, using a previously opened file descriptor. The file descriptor
%% can be set to close after exec() by passing the O_CLOEXEC flag to open:
%%
%% ```
%% {ok, FD} = prx:open(Task, "/bin/ls", [o_rdonly,o_cloexec]),
%% ok = prx:fexecve(Task, FD, ["-al"], ["FOO=123"]).
%% '''
%%
%% Linux requires an environment to be set unlike with execve(2). The
%% environment can be empty:
%%
%% ```
%% % Environment required on Linux
%% ok = prx:fexecve(Task, FD, ["ls", "-al"], [""]).
%% '''
%%
%% == Support ==
%%
%% • Linux
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.184.0>}
%% 3> {ok, FD} = prx:open(Task1, "/usr/bin/env", [o_rdonly,o_cloexec], 0).
%% {ok,7}
%% 4> prx:fexecve(Task1, FD, ["env", "-0"], ["FOO=123"]).
%% ok
%% 5> flush().
%% Shell got {stdout,<0.208.0>,<<70,79,79,61,49,50,51,0>>}
%% Shell got {exit_status,<0.208.0>,0}
%% ok
%% '''
-spec fexecve(task(), int32_t(), [iodata()], [iodata()]) -> ok | {error, posix()}.
fexecve(Task, FD, Argv, Env) when is_integer(FD), is_list(Argv), is_list(Env) ->
?PRX_CALL(Task, fexecve, [FD, ["" | Argv], Env]).
%% @equiv reexec/1
-spec replace_process_image(task()) -> ok | {error, posix()}.
replace_process_image(Task) ->
reexec(Task).
%% @equiv reexec/3
-spec replace_process_image(
task(), {fd, int32_t(), [string() | [string()]]} | [string() | [string()]], iodata()
) ->
ok | {error, posix()}.
replace_process_image(Task, Argv, Env) ->
reexec(Task, Argv, Env).
%% @doc Fork+exec prx process.
%%
%% Fork+exec is a way of randomizing the memory space of a process:
%%
%% [https://poolp.org/posts/2016-09-12/opensmtpd-6.0.0-is-released/]
%%
%% prx processes fork recursively:
%%
%% • the calls stack increases in size
%%
%% • the memory space layout is identical to the parent
%%
%% After forking a prx process using fork/1, the controlling process will
%% typically instruct the new prx process to execute a command using one
%% of the exec(3) functions: execvp/2, execve/3.
%%
%% Some "system" or "supervisor" type processes may remain in call mode:
%% these processes can call reexec/1 to exec() the port.
%%
%% On platforms supporting fexecve(2) (FreeBSD, Linux), prx will open a
%% file descriptor to the port binary and use it to re-exec() the port.
%%
%% On other OS'es, execve(2) will be used with the the default path to
%% the port binary.
%%
%% If the binary is not accessible or, on Linux, /proc is not mounted,
%% reexec/1 will fail.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.216.0>}
%% 2> prx:getpid(Task1).
%% 8175
%% 3> prx:reexec(Task1).
%% ok
%% 4> prx:getpid(Task1).
%% 8175
%% '''
-spec reexec(task()) -> ok | {error, posix()}.
reexec(Task) ->
Drv = drv(Task),
FD = gen_server:call(Drv, fdexe, infinity),
Argv = alcove_drv:getopts([
{progname, prx_drv:progname()},
{depth, length(pipeline(Task))},
{maxchild, getopt(Task, maxchild)}
]),
Env = environ(Task),
Opts = getopts(Task),
Result =
case reexec(Task, {fd, FD, Argv}, Env) of
{error, Errno} when Errno =:= enosys; Errno =:= ebadf ->
reexec(Task, Argv, Env);
Errno ->
Errno
end,
case Result of
ok ->
_ = setopts(Task, Opts),
Result;
_ ->
Result
end.
%% @doc Replace the port process image using execve(2)/fexecve(2).
%%
%% Specify the port program path or a file descriptor to the binary and
%% the process environment.
%%
%% @see reexec/1
-spec reexec(task(), {fd, int32_t(), [string() | [string()]]} | [string() | [string()]], iodata()) ->
ok | {error, posix()}.
reexec(_Task, {fd, -1, _Argv}, _Env) ->
{error, ebadf};
reexec(Task, {fd, FD, _} = Argv, Env) ->
case setflag(Task, [FD], fd_cloexec, unset) of
{error, _} = Error ->
Error;
ok ->
Reply = reexec_1(Task, Argv, Env),
ok = setflag(Task, [FD], fd_cloexec, set),
Reply
end;
reexec(Task, Argv, Env) ->
reexec_1(Task, Argv, Env).
reexec_1(Task, Argv, Env) ->
% Temporarily remove the close-on-exec flag: since these fd's are
% part of the operation of the port, any errors are fatal and should
% kill the OS process.
ok = setflag(Task, ?FD_SET, fd_cloexec, unset),
Reply = ?PRX_CALL(Task, reexec, [Argv, Env]),
ok = setflag(Task, ?FD_SET, fd_cloexec, set),
Reply.
%% @doc Send data to the standard input of the process
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.194.0>}
%% 3> prx:execvp(Task1, ["cat", "-n"]).
%% ok
%% 4> prx:stdin(Task1, <<"test\n">>).
%% ok
%% 5> flush().
%% Shell got {stdout,<0.194.0>,<<" 1\ttest\n">>}
%% ok
%% '''
-spec stdin(task(), iodata()) -> ok.
stdin(Task, Buf) ->
stdin_chunk(Task, iolist_to_binary(Buf)).
stdin_chunk(Task, <<Buf:32768/bytes, Rest/binary>>) ->
gen_statem:cast(Task, {stdin, Buf}),
stdin_chunk(Task, Rest);
stdin_chunk(Task, Buf) ->
gen_statem:cast(Task, {stdin, Buf}).
%%
%% Utilities
%%
-spec cmd(task(), [iodata()]) -> binary() | {error, posix()}.
cmd(Task, Cmd) ->
system(Task, Cmd).
-spec sh(task(), iodata()) -> binary() | {error, posix()}.
sh(Task, Cmd) ->
cmd(Task, ["/bin/sh", "-c", Cmd]).
%%
%% Retrieve internal state
%%
%% @doc Assign a new process owner
%%
%% `call mode': the controlling process is allowed to make calls to the
%% prx process.
%%
%% `exec mode': the controlling process receives standard output and
%% standard error from the prx process
-spec controlling_process(task(), pid()) -> ok | {error, badarg}.
controlling_process(Task, Pid) ->
gen_statem:call(Task, {controlling_process, Pid}, infinity).
%% @doc Assign a process to receive stdio
%%
%% Change the process receiving prx standard output and standard error.
%%
%% stdio/2 and controlling_process/2 can be used to transfer a prx process
%% between erlang processes without losing output when exec(3) is called:
%%
%% ```
%% ok = prx:stdio(Owner, NewOwner),
%% ok = prx:execvp(Owner, Argv),
%% ok = prx:controlling_process(Owner, NewOwner).
%% '''
-spec stdio(task(), pid()) -> ok | {error, badarg}.
stdio(Task, Pid) ->
gen_statem:call(Task, {stdio, Pid}, infinity).
%% @doc Get the process pipeline list for the task
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> prx:getpid(Task).
%% 8094
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.208.0>}
%% 4> prx:getpid(Task1).
%% 8175
%% 5> {ok, Task2} = prx:fork(Task1).
%% {ok,<0.3006.0>}
%% 6> prx:getpid(Task2).
%% 27224
%% 7> prx:pipeline(Task2).
%% [8175,27224]
%% '''
-spec pipeline(task()) -> [pid_t()].
pipeline(Task) ->
gen_statem:call(Task, pipeline, infinity).
%% @doc Get the gen_server PID for the task
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% <0.181.0>
%% '''
-spec drv(task()) -> pid().
drv(Task) ->
gen_statem:call(Task, drv, infinity).
%% @doc Get the parent PID for the task
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.180.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.208.0>}
%% 3> prx:parent(Task).
%% noproc
%% 4> prx:parent(Task1).
%% <0.180.0>
%% '''
-spec parent(task()) -> task() | noproc.
parent(Task) ->
try
gen_statem:call(Task, parent, infinity)
catch
exit:_ ->
noproc
end.
%% @doc Retrieve process info for forked processes
%%
%% Retrieve the map for a child process as returned in prx:cpid/1.
%%
%% cpid/2 searches the list of a process' children for a PID (an erlang or
%% a system PID) and returns a map containing the parent's file descriptors
%% towards the child.
%%
%% @see cpid/1
-spec cpid(task(), task() | pid_t()) -> cpid() | error.
cpid(Task, Pid) when is_pid(Pid) ->
case pidof(Pid) of
noproc ->
error;
Proc ->
cpid(Task, Proc)
end;
cpid(Task, Pid) when is_integer(Pid) ->
case [N || N <- prx:cpid(Task), maps:get(pid, N, false) == Pid] of
[] ->
error;
[Cpid] ->
Cpid
end.
%% @doc Close stdin of a task
%%
%% Close the task standard input by sending a request to the parent. The
%% operation may fail if the parent/child are associated with different
%% erlang processes.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.200.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.204.0>}
%% 3> prx:execvp(Task1, ["cat"]).
%% ok
%% 4> prx:eof(Task1).
%% ok
%% 5> flush().
%% Shell got {exit_status,<0.204.0>,0}
%% ok
%% '''
-spec eof(task()) -> ok | {error, posix()}.
eof(Task) when is_pid(Task) ->
eof(Task, stdin).
%% @doc Close task standard I/O file descriptor
%%
%% Close stdin, stdout or stderr for a task.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.176.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.211.0>}
%% 3> prx:execvp(Task1, ["cat"]).
%% ok
%% 4> prx:eof(Task1, stdout).
%% ok
%% 5> flush().
%% Shell got {exit_status,<0.211.0>,0}
%% ok
%% '''
-spec eof(task(), task() | pid_t() | stdin | stdout | stderr) -> ok | {error, posix()}.
eof(Task, Stdio) when is_pid(Task), is_atom(Stdio) ->
case parent(Task) of
noproc ->
% port process or process has exited
{error, ebadf};
Parent when is_pid(Parent) ->
eof(Parent, Task, stdin)
end;
eof(Task, Pid) when is_pid(Task) andalso (is_pid(Pid) orelse is_integer(Pid)) ->
eof(Task, Pid, stdin).
%% @doc Close stdin, stdout or stderr of child process.
%%
%% @see eof/2
-spec eof(task(), task() | pid_t(), stdin | stdout | stderr) -> ok | {error, posix()}.
eof(Task, Pid, Stdio) when Stdio == stdin; Stdio == stderr; Stdio == stdout ->
case cpid(Task, Pid) of
error ->
{error, esrch};
Child ->
Fd = maps:get(Stdio, Child),
close(Task, Fd)
end.
%% @doc Test if the task has called exec(2)
%%
%% Returns `true' if the task is running in exec mode.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.182.0>}
%% 3> prx:execed(Task1).
%% false
%% 4> prx:execvp(Task1, ["cat"]).
%% ok
%% 5> prx:execed(Task1).
%% true
%% '''
-spec execed(task()) -> boolean().
execed(Task) ->
case sys:get_state(Task) of
{exec_state, _} -> true;
_ -> false
end.
%% @doc Retrieves the system PID of the process similar to getpid(2)
%%
%% Returns the cached value for the PID of the system process. Works
%% with tasks in `exec' mode.
%%
%% Warning: `pidof/1' for a task returned by `fork/0' returns the PID
%% of the first child process. If using `sudo/0', the PID for the `sudo'
%% helper process is returned.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.182.0>}
%% 3> {ok, Task2} = prx:fork(Task).
%% {ok,<0.184.0>}
%% 4> prx:execvp(Task1, ["cat"]).
%% ok
%% 5> prx:getpid(Task2).
%% 27810
%% 7> prx:pidof(Task2).
%% 27810
%% 8> prx:pidof(Task1).
%% 27809
%% '''
-spec pidof(task()) -> pid_t() | noproc.
pidof(Task) ->
try pipeline(Task) of
[] ->
Drv = drv(Task),
Port = gen_server:call(Drv, port, infinity),
case erlang:port_info(Port) of
undefined ->
noproc;
Opt ->
proplists:get_value(os_pid, Opt)
end;
Pipeline ->
lists:last(Pipeline)
catch
exit:_ ->
noproc
end.
%% @doc Register a function to be called at task termination.
%%
%% The atexit function runs in the parent of the process. atexit/2 must
%% use prx_drv:call/4 to manipulate the task.
%%
%% == Examples ==
%%
%% The default function closes stdin, stdout and stderr of the system
%% process:
%%
%% ```
%% fun(Drv, Pipeline, Pid) ->
%% prx_drv:call(Drv, Pipeline, close, [maps:get(stdout, Pid)]),
%% prx_drv:call(Drv, Pipeline, close, [maps:get(stdin, Pid)]),
%% prx_drv:call(Drv, Pipeline, close, [maps:get(stderr, Pid)])
%% end
%% '''
-spec atexit(task(), fun((pid(), [pid_t()], cpid()) -> any())) -> ok.
atexit(Task, Fun) when is_function(Fun, 3) ->
gen_statem:call(Task, {atexit, Fun}, infinity).
%% @doc Convenience function to fork a privileged process in the shell.
%%
%% Sets the application environment so prx can fork a privileged
%% process. `sudo' must be configured to run the prx binary.
%%
%% The application environment must be set before prx:fork/0 is called.
%%
%% Equivalent to:
%%
%% ```
%% application:set_env(prx, options, [{exec, "sudo -n"}]),
%% {ok, Task} = prx:fork(),
%% 0 = prx:getuid(Task).
%% '''
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.199.0>}
%% 3> prx:getuid(Task).
%% 0
%% '''
-spec sudo() -> ok.
sudo() ->
case os:type() of
{unix, openbsd} ->
sudo("doas");
{unix, _} ->
sudo("sudo -n")
end.
%% @doc Convenience function to fork a privileged process in the shell.
%%
%% Allows specifying the command.
%%
%% == Examples ==
%%
%% For example, on OpenBSD:
%%
%% ```
%% 1> prx:sudo("doas").
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.199.0>}
%% 3> prx:getuid(Task).
%% 0
%% '''
-spec sudo(iodata()) -> ok.
sudo(Exec) ->
Env = application:get_env(prx, options, []),
Opt = orddict:merge(
fun(_Key, _V1, V2) -> V2 end,
orddict:from_list(Env),
orddict:from_list([{exec, to_charlist(Exec)}])
),
application:set_env(prx, options, Opt).
%%%===================================================================
%%% gen_statem callbacks
%%%===================================================================
%% @private
callback_mode() ->
state_functions.
%% @private
init([Owner, init]) ->
process_flag(trap_exit, true),
case prx_drv:start_link() of
{ok, Drv} ->
gen_server:call(Drv, init, infinity),
{ok, call_state, #state{drv = Drv, pipeline = [], owner = Owner, stdio = Owner}};
Error ->
{stop, Error}
end;
init([Drv, Owner, Parent, Pipeline0, Call, Argv]) when Call == fork; Call == clone ->
process_flag(trap_exit, true),
case prx_drv:call(Drv, Pipeline0, Call, Argv) of
{ok, Pipeline} ->
{ok, call_state, #state{
drv = Drv,
pipeline = Pipeline,
owner = Owner,
stdio = Owner,
parent = Parent
}};
{prx_error, Error} ->
erlang:error(Error, [Argv]);
{error, Error} ->
{stop, Error}
end.
%% @private
handle_info(
{alcove_event, Drv, Pipeline, {exit_status, Status}},
_StateName,
#state{
drv = Drv,
pipeline = Pipeline,
stdio = Stdio
} = State
) ->
Stdio ! {exit_status, self(), Status},
{stop, shutdown, State};
handle_info(
{alcove_event, Drv, Pipeline, {termsig, Sig}},
_StateName,
#state{
drv = Drv,
pipeline = Pipeline,
stdio = Stdio
} = State
) ->
Stdio ! {termsig, self(), Sig},
{stop, shutdown, State};
handle_info(
{alcove_stdout, Drv, Pipeline, Buf},
exec_state,
#state{
drv = Drv,
pipeline = Pipeline,
stdio = Stdio
} = State
) ->
Stdio ! {stdout, self(), Buf},
{next_state, exec_state, State};
handle_info(
{alcove_stderr, Drv, Pipeline, Buf},
exec_state,
#state{
drv = Drv,
pipeline = Pipeline,
stdio = Stdio
} = State
) ->
Stdio ! {stderr, self(), Buf},
{next_state, exec_state, State};
handle_info(
{alcove_pipe, Drv, Pipeline, Bytes},
exec_state,
#state{
drv = Drv,
pipeline = Pipeline,
stdio = Stdio
} = State
) ->
Stdio ! {stdin, self(), {error, {eagain, Bytes}}},
{next_state, exec_state, State};
handle_info(
{alcove_stdout, Drv, Pipeline, Buf},
call_state,
#state{
drv = Drv,
pipeline = Pipeline
} = State
) ->
error_logger:error_report({stdout, Buf}),
{next_state, call_state, State};
handle_info(
{alcove_stderr, Drv, Pipeline, Buf},
call_state,
#state{
drv = Drv,
pipeline = Pipeline
} = State
) ->
error_logger:error_report({stderr, Buf}),
{next_state, call_state, State};
handle_info(
{alcove_pipe, Drv, Pipeline, Bytes},
call_state,
#state{
drv = Drv,
pipeline = Pipeline,
owner = Owner
} = State
) ->
Owner ! {stdin, self(), {error, {eagain, Bytes}}},
{stop, shutdown, State};
% The process control-on-exec fd has unexpectedly closed. The process
% has probably received a signal and been terminated.
handle_info(
{alcove_ctl, Drv, Pipeline, fdctl_closed},
call_state,
#state{
drv = Drv,
pipeline = Pipeline
} = State
) ->
{next_state, call_state, State};
handle_info(
{alcove_ctl, Drv, Pipeline, Buf},
call_state,
#state{
drv = Drv,
pipeline = Pipeline
} = State
) ->
error_logger:error_report({ctl, Buf}),
{next_state, call_state, State};
handle_info(
{alcove_event, Drv, Pipeline, {signal, Signal, Info}},
call_state,
#state{
drv = Drv,
pipeline = Pipeline,
sigaction = Sigaction,
owner = Owner
} = State
) ->
case maps:find(Signal, Sigaction) of
error ->
Owner ! {signal, self(), Signal, Info};
{ok, Fun} ->
Fun(Drv, Pipeline, Signal, Info)
end,
{next_state, call_state, State};
handle_info(
{alcove_event, Drv, Pipeline, Buf},
call_state,
#state{
drv = Drv,
pipeline = Pipeline
} = State
) ->
error_logger:error_report({event, Buf}),
{next_state, call_state, State};
handle_info({'EXIT', Drv, Reason}, _, #state{drv = Drv} = State) ->
error_logger:error_report({'EXIT', Drv, Reason}),
{stop, {shutdown, Reason}, State};
handle_info(
{'EXIT', Task, _Reason},
call_state,
#state{
drv = Drv,
pipeline = Pipeline,
children = Child,
atexit = Atexit
} = State
) ->
_ =
case maps:find(Task, Child) of
error ->
ok;
{ok, Pid} ->
[
Atexit(Drv, Pipeline, cpid_to_map(X))
|| X <- prx_drv:call(Drv, Pipeline, cpid, []), X#alcove_pid.pid =:= Pid
]
end,
{next_state, call_state, State};
handle_info(Info, Cur, State) ->
error_logger:error_report({info, Cur, Info}),
{next_state, Cur, State}.
%% @private
terminate(_Reason, _StateName, #state{drv = Drv, pipeline = []}) ->
catch prx_drv:stop(Drv),
ok;
terminate(_Reason, _StateName, #state{}) ->
ok.
%% @private
code_change(_OldVsn, StateName, State, _Extra) ->
{ok, StateName, State}.
% Stdin sent while the process is in call state is discarded.
%% @private
call_state(cast, _, State) ->
{next_state, call_state, State};
call_state(
{call, {Owner, _Tag} = From},
{Call, Argv},
#state{drv = Drv, pipeline = Pipeline, children = Child} = State
) when Call =:= fork; Call =:= clone ->
case gen_statem:start_link(?MODULE, [Drv, Owner, self(), Pipeline, Call, Argv], []) of
{ok, Task} ->
[Pid | _] = lists:reverse(prx:pipeline(Task)),
{next_state, call_state, State#state{children = maps:put(Task, Pid, Child)}, [
{reply, From, {ok, Task}}
]};
Error ->
{next_state, call_state, State, [{reply, From, Error}]}
end;
call_state(
{call, {Owner, _Tag} = From},
{Call, Argv},
#state{
drv = Drv,
pipeline = Pipeline,
owner = Owner
} = State
) when Call =:= execvp; Call =:= execve; Call =:= fexecve ->
case prx_drv:call(Drv, Pipeline, cpid, []) of
[] ->
case prx_drv:call(Drv, Pipeline, Call, Argv) of
ok ->
{next_state, exec_state, State, [{reply, From, ok}]};
Error ->
{next_state, call_state, State, [{reply, From, Error}]}
end;
[#alcove_pid{} | _] ->
{next_state, call_state, State, [{reply, From, {error, eacces}}]}
end;
call_state(
{call, {Owner, _Tag} = From},
{reexec, [{fd, FD, Argv}, Env]},
#state{
drv = Drv,
pipeline = Pipeline,
owner = Owner
} = State
) ->
case prx_drv:call(Drv, Pipeline, cpid, []) of
[] ->
Reply = prx_drv:call(Drv, Pipeline, fexecve, [FD, Argv, Env]),
{next_state, call_state, State, [{reply, From, Reply}]};
[#alcove_pid{} | _] ->
{next_state, call_state, State, [{reply, From, {error, eacces}}]}
end;
call_state(
{call, {Owner, _Tag} = From},
{reexec, [[Arg0 | _] = Argv, Env]},
#state{
drv = Drv,
pipeline = Pipeline,
owner = Owner
} = State
) ->
case prx_drv:call(Drv, Pipeline, cpid, []) of
[] ->
Reply = prx_drv:call(Drv, Pipeline, execve, [Arg0, Argv, Env]),
{next_state, call_state, State, [{reply, From, Reply}]};
[#alcove_pid{} | _] ->
{next_state, call_state, State, [{reply, From, {error, eacces}}]}
end;
call_state(
{call, {Owner, _Tag} = From},
{controlling_process, Pid},
#state{
owner = Owner
} = State
) ->
Reply =
case is_process_alive(Pid) of
false ->
{error, badarg};
true ->
ok
end,
{next_state, call_state, State#state{owner = Pid, stdio = Pid}, [{reply, From, Reply}]};
call_state(
{call, {Owner, _Tag} = From},
{stdio, Pid},
#state{
owner = Owner
} = State
) ->
Reply =
case is_process_alive(Pid) of
false ->
{error, badarg};
true ->
ok
end,
{next_state, call_state, State#state{stdio = Pid}, [{reply, From, Reply}]};
call_state(
{call, {Owner, _Tag} = From},
drv,
#state{
drv = Drv,
owner = Owner
} = State
) ->
{next_state, call_state, State, [{reply, From, Drv}]};
call_state(
{call, {Owner, _Tag} = From},
parent,
#state{
parent = Parent,
owner = Owner
} = State
) ->
{next_state, call_state, State, [{reply, From, Parent}]};
call_state(
{call, {_Owner, _Tag} = From},
pipeline,
#state{
pipeline = Pipeline
} = State
) ->
{next_state, call_state, State, [{reply, From, Pipeline}]};
%%%
%%% setcpid: handle or forward to parent
%%%
%%%% setcpid: request to port process
call_state(
{call, {Owner, _Tag} = From},
{setcpid, [_Opt, _Val]},
#state{
owner = Owner,
parent = noproc
} = State
) ->
{next_state, call_state, State, [{reply, From, false}]};
%%% setcpid: forward call to parent
call_state(
{call, {Owner, _Tag} = From},
{setcpid, [Opt, Val]},
#state{
owner = Owner,
parent = Parent
} = State
) ->
Reply = prx:setcpid(Parent, Opt, Val),
{next_state, call_state, State, [{reply, From, Reply}]};
%%% setcpid: parent modifies child state
call_state(
{call, {Owner, _Tag} = From},
{setcpid, [Pid, Opt, Val]},
#state{
owner = Owner,
drv = Drv,
pipeline = Pipeline
} = State
) ->
Reply = prx_drv:call(Drv, Pipeline, setcpid, [Pid, Opt, Val]),
{next_state, call_state, State, [{reply, From, Reply}]};
%%% setcpid: handle request to modify child state
call_state(
{call, {Child, _Tag} = From},
{setcpid, [Opt, Val]},
#state{
children = Children,
drv = Drv,
pipeline = Pipeline
} = State
) ->
Reply =
case maps:find(Child, Children) of
error ->
false;
{ok, Pid} ->
prx_drv:call(Drv, Pipeline, setcpid, [Pid, Opt, Val])
end,
{next_state, call_state, State, [{reply, From, Reply}]};
%%%
%%% getcpid: handle or forward to parent
%%%
%%%% getcpid: request to port process
call_state(
{call, {Owner, _Tag} = From},
{getcpid, [_Opt]},
#state{
owner = Owner,
parent = noproc
} = State
) ->
{next_state, call_state, State, [{reply, From, false}]};
%%% getcpid: forward call to parent
call_state(
{call, {Owner, _Tag} = From},
{getcpid, [Opt]},
#state{
owner = Owner,
parent = Parent
} = State
) ->
Reply = prx:getcpid(Parent, Opt),
{next_state, call_state, State, [{reply, From, Reply}]};
%%% getcpid: request from owner for child state
call_state(
{call, {Owner, _Tag} = From},
{getcpid, [Pid, Opt]},
#state{
owner = Owner,
drv = Drv,
pipeline = Pipeline
} = State
) ->
Cpid = [
cpid_to_map(N)
|| N <- prx_drv:call(Drv, Pipeline, cpid, []),
N#alcove_pid.pid == Pid
],
Reply =
case Cpid of
[] -> false;
[X] -> maps:get(Opt, X, false)
end,
{next_state, call_state, State, [{reply, From, Reply}]};
%%% getcpid: parent handles request by child
call_state(
{call, {Child, _Tag} = From},
{getcpid, [Opt]},
#state{
children = Children,
drv = Drv,
pipeline = Pipeline
} = State
) ->
Cpid =
case maps:find(Child, Children) of
error ->
[];
{ok, Pid} ->
[
cpid_to_map(N)
|| N <- prx_drv:call(Drv, Pipeline, cpid, []),
N#alcove_pid.pid == Pid
]
end,
Reply =
case Cpid of
[] -> false;
[X] -> maps:get(Opt, X, false)
end,
{next_state, call_state, State, [{reply, From, Reply}]};
call_state(
{call, {Owner, _Tag} = From},
{atexit, Fun},
#state{
owner = Owner
} = State
) ->
{next_state, call_state, State#state{atexit = Fun}, [{reply, From, ok}]};
call_state(
{call, {Owner, _Tag} = From},
{sigaction, Signal, Fun},
#state{
sigaction = Sigaction,
owner = Owner
} = State
) ->
{next_state, call_state,
State#state{
sigaction = maps:put(Signal, Fun, Sigaction)
},
[{reply, From, ok}]};
% port process calls exit
call_state(
{call, {Owner, _Tag} = From},
{exit, _},
#state{
drv = Drv,
owner = Owner,
pipeline = []
} = State
) ->
case prx_drv:call(Drv, [], cpid, []) of
[] ->
{stop_and_reply, shutdown, [{reply, From, ok}]};
[#alcove_pid{} | _] ->
{next_state, call_state, State, [{reply, From, {error, eacces}}]}
end;
call_state(
{call, {Owner, _Tag} = From},
{Call, Argv},
#state{
drv = Drv,
pipeline = Pipeline,
owner = Owner
} = State
) ->
Reply = prx_drv:call(Drv, Pipeline, Call, Argv),
{next_state, call_state, State, [{reply, From, Reply}]};
call_state({call, From}, _, State) ->
{next_state, call_state, State, [{reply, From, {prx_error, eacces}}]};
call_state(info, Event, State) ->
handle_info(Event, call_state, State).
%% @private
exec_state(cast, {stdin, Buf}, #state{drv = Drv, pipeline = Pipeline} = State) ->
prx_drv:stdin(Drv, Pipeline, Buf),
{next_state, exec_state, State};
exec_state(cast, _, State) ->
{next_state, exec_state, State};
% Any calls received after the process has exec'ed crash the process:
%
% * the process could return an error tuple such as {error,einval} but this
% would extend the type signature of all calls.
%
% For example, getpid(2) cannot fail and returns a uint32_t():
%
% getpid(Task) -> non_neg_integer() | {error,einval}
%
% * throw an exception: allow the caller to control failure by throwing
% an exception. Since the caller expects a reply, the call cannot be
% simply discarded.
%
% Since the exception is sent between processes, erlang:exit/2 must
% be used. Should the owner be killed (like with ports) or the caller?
%
% * stop the fsm
%
% Fail fast: the process is in an unexpected state. There is no way
% for the caller to control failure which makes experimenting in the shell
% more difficult.
%
% * return a tuple and crash in the context of the caller
%%%
%%% setcpid: forward call to parent
%%%
%%% setcpid: forward call to parent
exec_state(
{call, {Owner, _Tag} = From},
{setcpid, [Opt, Val]},
#state{
owner = Owner,
parent = Parent
} = State
) ->
Reply = prx:setcpid(Parent, Opt, Val),
{next_state, exec_state, State, [{reply, From, Reply}]};
%%% getcpid: forward call to parent
exec_state(
{call, {Owner, _Tag} = From},
{getcpid, [Opt]},
#state{
owner = Owner,
parent = Parent
} = State
) ->
Reply = prx:getcpid(Parent, Opt),
{next_state, exec_state, State, [{reply, From, Reply}]};
exec_state(
{call, From},
pipeline,
#state{
pipeline = Pipeline
} = State
) ->
{next_state, exec_state, State, [{reply, From, Pipeline}]};
exec_state(
{call, From},
parent,
#state{
parent = Parent
} = State
) ->
{next_state, exec_state, State, [{reply, From, Parent}]};
exec_state(
{call, {Owner, _Tag} = From},
{controlling_process, Pid},
#state{
owner = Owner
} = State
) ->
Reply =
case is_process_alive(Pid) of
false ->
{error, badarg};
true ->
ok
end,
{next_state, exec_state, State#state{owner = Pid, stdio = Pid}, [{reply, From, Reply}]};
exec_state(
{call, {Owner, _Tag} = From},
{stdio, Pid},
#state{
owner = Owner
} = State
) ->
Reply =
case is_process_alive(Pid) of
false ->
{error, badarg};
true ->
ok
end,
{next_state, exec_state, State#state{stdio = Pid}, [{reply, From, Reply}]};
exec_state({call, From}, _, State) ->
{next_state, exec_state, State, [{reply, From, {prx_error, eacces}}]};
exec_state(info, Event, State) ->
handle_info(Event, exec_state, State).
%%%===================================================================
%%% Internal functions
%%%===================================================================
to_charlist(S) ->
erlang:binary_to_list(erlang:iolist_to_binary(S)).
system(Task, Cmd) ->
process_flag(trap_exit, true),
% Valid errors from sigaction are:
%
% EINVAL: unknown signal, attempt to change SIGSTOP or SIGKILL
% EFAULT
%
% Since these signals are valid, an error means a fault has occurred
% in the driver and the driver state is unknown, so crash hard.
{ok, Int} = sigaction(Task, sigint, sig_ign),
{ok, Quit} = sigaction(Task, sigquit, sig_ign),
Reply = fork(Task),
Stdout =
case Reply of
{ok, Child} ->
% Restore the child's signal handlers before calling exec()
{ok, _} = sigaction(Child, sigint, Int),
{ok, _} = sigaction(Child, sigquit, Quit),
% Disable flowcontrol if enabled
true = prx:setcpid(Child, flowcontrol, -1),
system_exec(Task, Child, Cmd);
Error ->
Error
end,
% Child has returned, restore the parent's signal handlers
_ =
case is_process_alive(Task) of
true ->
{ok, _} = sigaction(Task, sigint, Int),
{ok, _} = sigaction(Task, sigquit, Quit);
false ->
ok
end,
Stdout.
system_exec(Task, Child, Cmd) ->
case prx:execvp(Child, Cmd) of
ok ->
flush_stdio(Task, Child);
Error ->
stop(Child),
Error
end.
flush_stdio(Task, Child) ->
flush_stdio(Task, Child, [], infinity).
flush_stdio(Task, Child, Acc, Timeout) ->
receive
{stdout, Child, Buf} ->
flush_stdio(Task, Child, [Buf | Acc], Timeout);
{stderr, Child, Buf} ->
flush_stdio(Task, Child, [Buf | Acc], Timeout);
{exit_status, Child, _} ->
flush_stdio(Task, Child, Acc, 0);
{termsig, Child, _} ->
flush_stdio(Task, Child, Acc, 0);
{exit_status, Task, _} ->
flush_stdio(Task, Child, Acc, 0);
{termsig, Task, _} ->
flush_stdio(Task, Child, Acc, 0)
after Timeout -> list_to_binary(lists:reverse(Acc))
end.
setflag(_Task, [], _Flag, _Status) ->
ok;
setflag(Task, [FD | FDSet], Flag, Status) ->
Constant = ?PRX_CALL(Task, fcntl_constant, [Flag]),
case fcntl(Task, FD, f_getfd) of
{ok, Flags} ->
case fcntl(Task, FD, f_setfd, fdstatus(Flags, Constant, Status)) of
{ok, _NewFlags} ->
setflag(Task, FDSet, Flag, Status);
Error1 ->
Error1
end;
Error ->
Error
end.
fdstatus(Flags, Constant, set) -> Flags bor Constant;
fdstatus(Flags, Constant, unset) -> Flags band (bnot Constant).
getopts(Task) ->
% Required for prx so reset to defaults: stdin_closed, stdout_closed,
% stderr_closed
Opts = [exit_status, flowcontrol, maxforkdepth, termsig, signaloneof],
[{N, prx:getopt(Task, N)} || N <- Opts].
setopts(Task, Opts) ->
[true = prx:setopt(Task, Key, Val) || {Key, Val} <- Opts].
%%%===================================================================
%%% Exported functions
%%%===================================================================
%% @doc setproctitle(3): set the process title
%%
%% Set the process title displayed in utilities like ps(1) by overwriting
%% the command's arg0.
%%
%% Linux systems may also want to set the command name using `prctl/6':
%%
%% ```
%% prx:prctl(Task, pr_set_name, <<"newname">>, 0, 0, 0)
%% '''
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.177.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,28210}
%% 3> prx:setproctitle(Task1, "new process name").
%% ok
%% '''
%%
%% @see prctl/6
-spec setproctitle(task(), iodata()) -> ok.
setproctitle(Task, Name) ->
case os:type() of
{unix, sunos} ->
ok;
{unix, OS} when
OS =:= linux; OS =:= freebsd; OS =:= openbsd; OS =:= netbsd; OS =:= darwin
->
?PRX_CALL(Task, setproctitle, [Name]);
_ ->
ok
end.
%% @doc Returns the list of child PIDs for this process.
%%
%% Each child task is a map composed of:
%%
%% • pid: system pid
%%
%% • exec: true if the child has called exec()
%%
%% • fdctl: parent end of CLOEXEC file descriptor used to monitor if the child process has called exec()
%%
%% • stdin: parent end of the child process' standard input
%%
%% • stdout: parent end of the child process' standard output
%%
%% • stderr: parent end of the child process' standard error
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.182.0>}
%% 3> {ok, Task2} = prx:fork(Task).
%% {ok,<0.184.0>}
%% 4> prx:cpid(Task).
%% [#{exec => true,fdctl => -2,flowcontrol => -1,pid => 27809,
%% signaloneof => 15,stderr => 13,stdin => 10,stdout => 11},
%% #{exec => false,fdctl => 9,flowcontrol => -1,pid => 27810,
%% signaloneof => 15,stderr => 17,stdin => 14,stdout => 15}]
%% '''
-spec cpid(task()) -> [cpid()].
cpid(Task) ->
[cpid_to_map(Pid) || Pid <- ?PRX_CALL(Task, cpid, [])].
cpid_to_map(#alcove_pid{
pid = Pid,
flowcontrol = Flowcontrol,
signaloneof = Signaloneof,
fdctl = Ctl,
stdin = In,
stdout = Out,
stderr = Err
}) ->
#{
pid => Pid,
exec => Ctl =:= -2,
flowcontrol => Flowcontrol,
signaloneof => Signaloneof,
fdctl => Ctl,
stdin => In,
stdout => Out,
stderr => Err
}.
%% @doc getrlimit(2): retrieve the resource limits for a process
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 2> prx:getrlimit(Task, rlimit_nofile).
%% {ok,#{cur => 1024,max => 1048576}}
%% '''
-spec getrlimit(task(), constant()) ->
{ok, #{cur => uint64_t(), max => uint64_t()}} | {error, posix()}.
getrlimit(Task, Resource) ->
case ?PRX_CALL(Task, getrlimit, [Resource]) of
{ok, #alcove_rlimit{cur = Cur, max = Max}} ->
{ok, #{cur => Cur, max => Max}};
Error ->
Error
end.
%% @doc setrlimit(2): set a resource limit
%%
%% Note on `rlimit_nofile':
%%
%% The control process requires a fixed number of file descriptors for
%% each subprocess. Reducing the number of file descriptors will reduce
%% the limit on child processes.
%%
%% If the file descriptor limit is below the number of file descriptors
%% currently used, setrlimit/4,5 will return `{error, einval}'.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.162.0>}
%% 3> prx:getrlimit(Task1, rlimit_nofile).
%% {ok,#{cur => 1048576,max => 1048576}}
%% 4> prx:setrlimit(Task1, rlimit_nofile, #{cur => 64, max => 64}).
%% ok
%% 5> prx:getrlimit(Task1, rlimit_nofile).
%% {ok,#{cur => 64,max => 64}}
%% 6> prx:getrlimit(Task, rlimit_nofile).
%% {ok,#{cur => 1048576,max => 1048576}}
%% '''
-spec setrlimit(task(), constant(), #{cur => uint64_t(), max => uint64_t()}) ->
ok | {error, posix()}.
setrlimit(Task, Resource, Limit) ->
#{cur := Cur, max := Max} = Limit,
?PRX_CALL(Task, setrlimit, [Resource, #alcove_rlimit{cur = Cur, max = Max}]).
%% @doc select(2): poll a list of file descriptor for events
%%
%% select/5 will block until an event occurs on a file descriptor, a timeout
%% is reached or interrupted by a signal.
%%
%% The Timeout value may be:
%%
%% • an empty list ([]): causes select to block indefinitely (no timeout)
%%
%% • a map indicating the timeout
%%
%% The map contains these fields:
%%
%% • sec : number of seconds to wait
%%
%% • usec : number of microseconds to wait
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% 2> {ok, FD} = prx:open(Task, "/dev/null", [o_rdwr], 0).
%% {ok,7}
%% 3> prx:select(Task, [FD], [FD], [FD], []).
%% {ok,[7],[7],[]}
%% 4> prx:select(Task, [FD], [FD], [FD], #{sec => 1, usec => 1}).
%% {ok,[7],[7],[]}
%% '''
-spec select(
task(),
Readfds :: [fd()],
Writefds :: [fd()],
Exceptfds :: [fd()],
Timeval :: [] | #{sec => int64_t(), usec => int64_t()}
) -> {ok, [fd()], [fd()], [fd()]} | {error, posix()}.
select(Task, Readfds, Writefds, Exceptfds, Timeout) when is_map(Timeout) ->
Sec = maps:get(sec, Timeout, 0),
Usec = maps:get(usec, Timeout, 0),
?PRX_CALL(Task, select, [Readfds, Writefds, Exceptfds, #alcove_timeval{sec = Sec, usec = Usec}]);
select(Task, Readfds, Writefds, Exceptfds, Timeout) ->
?PRX_CALL(Task, select, [Readfds, Writefds, Exceptfds, Timeout]).
%% @doc cap_enter(2): place process into capability mode
%%
%% == Support ==
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.158.0>}
%% 3> prx:cap_enter(Task1).
%% ok
%% 4> prx:kill(Task1, 0, 0).
%% {error,ecapmode}
%% 5> prx:kill(Task, 0, 0).
%% ok
%% '''
-spec cap_enter(task()) -> ok | {error, posix()}.
cap_enter(Task) ->
?PRX_CALL(Task, cap_enter, []).
%% @doc cap_fcntls_get(2): get allowed fcntl commands in capability mode
%%
%% == Support ==
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.165.0>}
%% 3> {ok, FD} = prx:open(Task1, "/etc/passwd", [o_rdonly]).
%% {ok,7}
%% 4> prx:cap_enter(Task1).
%% ok
%% 5> prx:cap_fcntls_get(Task1, FD).
%% {ok,120}
%% '''
-spec cap_fcntls_get(task(), fd()) -> {ok, int32_t()} | {error, posix()}.
cap_fcntls_get(Task, FD) ->
?PRX_CALL(Task, cap_fcntls_get, [FD]).
%% @doc cap_fcntls_limit(2): manage fcntl commands in capability mode
%%
%% == Support ==
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.165.0>}
%% 3> {ok, FD} = prx:open(Task1, "/etc/passwd", [o_rdonly]).
%% {ok,7}
%% 4> prx:cap_enter(Task1).
%% ok
%% 5> prx:cap_fcntls_get(Task1, FD).
%% {ok,120}
%% 6> prx:cap_fcntls_limit(Task1, FD, [cap_fcntl_setfl]).
%% ok
%% 7> prx:cap_fcntls_get(Task1, FD).
%% {ok,16}
%% '''
-spec cap_fcntls_limit(task(), fd(), [constant()]) -> ok | {error, posix()}.
cap_fcntls_limit(Task, FD, Rights) ->
?PRX_CALL(Task, cap_fcntls_limit, [FD, Rights]).
%% @doc cap_getmode(2): check if capability mode is enabled
%%
%% • `0' : false
%%
%% • `1' : true
%%
%% == Support ==
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.165.0>}
%% 3> prx:cap_enter(Task1).
%% ok
%% 4> prx:cap_getmode(Task).
%% {ok,0}
%% 5> prx:cap_getmode(Task1).
%% {ok,1}
%% '''
-spec cap_getmode(task()) -> {ok, 0 | 1} | {error, posix()}.
cap_getmode(Task) ->
?PRX_CALL(Task, cap_getmode, []).
%% @doc cap_ioctls_limit(2): manage allowed ioctl commands
%%
%% == Support ==
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.158.0>}
%% 3> {ok, FD} = prx:open(Task1, "/dev/pts/1", [o_rdwr, o_nonblock]).
%% {ok,7}
%% 4> prx:cap_enter(Task1).
%% ok
%% 5> prx:cap_ioctls_limit(Task1, FD, [tiocmget, tiocgwinsz]).
%% ok
%% 6> prx:ioctl(Task1, FD, tiocmset, <<>>).
%% {error,enotcapable}
%% 7> prx:ioctl(Task1, FD, tiocmget, <<>>).
%% {ok,#{arg => <<>>,return_value => 0}}
%% '''
-spec cap_ioctls_limit(task(), fd(), [constant()]) -> ok | {error, posix()}.
cap_ioctls_limit(Task, FD, Rights) ->
?PRX_CALL(Task, cap_ioctls_limit, [FD, Rights]).
%% @doc cap_rights_limit(2): manage process capabilities
%%
%% == Support ==
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.168.0>}
%% 3> {ok, FD} = prx:open(Task1, "/etc/passwd", [o_rdonly]).
%% {ok,7}
%% 4> prx:cap_enter(Task1).
%% ok
%% 5> prx:cap_rights_limit(Task1, FD, [cap_read]).
%% ok
%% 6> prx:read(Task1, FD, 64).
%% {ok,<<"# $FreeBSD$\n#\nroot:*:0:0:Charlie &:/root:/bin/csh\ntoor:*:0:0:Bou">>}
%% 7> prx:lseek(Task1, FD, 0, 0).
%% {error,enotcapable}
%% 8> prx:open(Task1, "/etc/passwd", [o_rdonly]).
%% {error,ecapmode}
%% '''
-spec cap_rights_limit(task(), fd(), [constant()]) -> ok | {error, posix()}.
cap_rights_limit(Task, FD, Rights) ->
?PRX_CALL(Task, cap_rights_limit, [FD, Rights]).
%% @doc chdir(2): change process current working directory
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.178.0>}
%% 3> prx:chdir(Task, "/").
%% ok
%% 4> prx:chdir(Task1, "/tmp").
%% ok
%% 5> prx:getcwd(Task).
%% {ok,<<"/">>}
%% 6> prx:getcwd(Task1).
%% {ok,<<"/tmp">>}
%% '''
-spec chdir(task(), iodata()) -> ok | {error, posix()}.
chdir(Task, Path) ->
?PRX_CALL(Task, chdir, [Path]).
%% @doc chmod(2): change file permissions
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.178.0>}
%% 3> {ok, FD} = prx:open(Task1, "/tmp/testfile.txt", [o_wronly, o_creat], 8#644).
%% {ok,7}
%% 4> prx:chmod(Task1, "/tmp/testfile.txt", 8#400).
%% ok
%% '''
-spec chmod(task(), iodata(), mode_t()) -> ok | {error, posix()}.
chmod(Task, Path, Mode) ->
?PRX_CALL(Task, chmod, [Path, Mode]).
%% @doc chown(2): change file ownership
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.155.0>}
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.159.0>}
%% 4> {ok, FD} = prx:open(Task1, "/tmp/testfile.txt", [o_wronly, o_creat], 8#644).
%% {ok,7}
%% 5> prx:chown(Task1, "/tmp/testfile.txt", 0, 0).
%% ok
%% '''
-spec chown(task(), iodata(), uid_t(), gid_t()) -> ok | {error, posix()}.
chown(Task, Path, Owner, Group) ->
?PRX_CALL(Task, chown, [Path, Owner, Group]).
%% @doc chroot(2): change root directory
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.155.0>}
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.159.0>}
%% 4> prx:chroot(Task1, "/tmp").
%% ok
%% 5> prx:chdir(Task1, "/").
%% ok
%% 6> prx:getcwd(Task1).
%% {ok,<<"/">>}
%% '''
-spec chroot(task(), iodata()) -> ok | {error, posix()}.
chroot(Task, Path) ->
?PRX_CALL(Task, chroot, [Path]).
%% @doc clearenv(3): zero process environment
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.155.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.159.0>}
%% 3> prx:clearenv(Task1).
%% ok
%% 4> prx:environ(Task1).
%% []
%% '''
-spec clearenv(task()) -> ok | {error, posix()}.
clearenv(Task) ->
?PRX_CALL(Task, clearenv, []).
%% @doc close(2): close a file descriptor
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.178.0>}
%% 3> {ok, FD} = prx:open(Task1, "/tmp/testfile.txt", [o_wronly, o_creat], 8#644).
%% {ok,7}
%% 4> prx:close(Task1, FD).
%% ok
%% '''
-spec close(task(), fd()) -> ok | {error, posix()}.
close(Task, FD) ->
?PRX_CALL(Task, close, [FD]).
%% @doc environ(7): return the process environment variables
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> prx:environ(Task).
%% [<<"LANG=C.UTF-8">>,
%% <<"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin">>,
%% <<"TERM=screen">>, <<"SHELL=/bin/bash">>]
%% '''
-spec environ(task()) -> [binary()].
environ(Task) ->
?PRX_CALL(Task, environ, []).
%% @doc exit(3): cause an prx control process to exit
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.178.0>}
%% 3> prx:exit(Task1, 111).
%% ok
%% 4> flush().
%% Shell got {exit_status,<0.159.0>,111}
%% ok
%% '''
-spec exit(task(), int32_t()) -> ok.
exit(Task, Status) ->
?PRX_CALL(Task, exit, [Status]).
%% @doc fcntl(2) : perform operation on a file descriptor
%%
%% @see fcntl/4
-spec fcntl(task(), fd(), constant()) -> {ok, int64_t()} | {error, posix()}.
fcntl(Task, FD, Cmd) ->
?PRX_CALL(Task, fcntl, [FD, Cmd, 0]).
%% @doc fcntl(2): perform operations on a file descriptor with argument
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% 2> Stdin = 0.
%% 0
%% 3> prx:fcntl(Task, Stdin, f_getfd, 0).
%% {ok,0}
%% '''
-spec fcntl(task(), fd(), constant(), int64_t()) -> {ok, int64_t()} | {error, posix()}.
fcntl(Task, FD, Cmd, Arg) ->
?PRX_CALL(Task, fcntl, [FD, Cmd, Arg]).
%% @doc filter/2 : restrict control process calls
%%
%% Restricts the set of calls available to a prx control process. If fork
%% is allowed, any subsequently forked control processes inherit the set
%% of filtered calls:
%%
%% ```
%% {ok, Ctrl} = prx:fork(),
%% ok = prx:filter(Ctrl, [getpid]),
%% {ok, Task} = prx:fork(Ctrl),
%%
%% {'EXIT', {undef, _}} = (catch prx:getpid(Task)).
%% '''
%%
%% @see filter/3
-spec filter(task(), [call()] | {allow, [call()]} | {deny, [call()]}) -> ok.
filter(Task, Calls) ->
filter(Task, Calls, Calls).
%% @doc filter/3 : restrict control process and subprocess calls
%%
%% filter/3 specifies the set of calls available to a prx control process
%% and any subsequently forked control processes. Control processes continue
%% to proxy data and monitor and reap subprocesses.
%%
%% Invoking a filtered call will crash the process with 'undef'.
%%
%% If the filter/3 call is filtered, subsequent calls to filter/3
%% will fail.
%%
%% Calls can be either allowed or denied. If a call is allowed, all
%% other calls are filtered.
%%
%% Once a filter for a call is added, the call cannot be removed from
%% the filter set. Passing an empty list ([]) specifies the current filter
%% set should not be modified.
%%
%% ```
%% % the set of calls to filter, any forked control subprocesses
%% % are unrestricted
%% prx:filter(Task, {deny, [getpid, execve, execvp]}, [])
%%
%% % equivalent to {deny, [getpid, execve, execvp]}
%% prx:filter(Task, [getpid, execve, execvp], [])
%%
%% % all other calls are filtered including filter
%% prx:filter(Task, {allow, [fork, clone, kill]}, [])
%%
%% % init: control process can fork, subprocesses can exec a data process
%% prx:filter(Task, {allow, [fork, clone, kill]}, {allow, [execve, execvp]})
%% '''
%%
%% == Examples ==
%%
%% ```
%% 1> catch_exception(true).
%% false
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% %% % Control process: restricted to: fork, filter, getcwd
%% %% % Any forked control subprocess: restricted to: getpid, gethostname
%% 2> prx:filter(Task, {allow, [fork, filter, getcwd]}, {allow, [getpid, gethosname]}).
%% ok
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.190.0>}
%% 4> prx:getpid(Task).
%% * exception error: undefined function prx:getpid/1
%% 5> prx:getcwd(Task1).
%% * exception error: undefined function prx:getcwd/1
%% 6> prx:getcwd(Task).
%% {ok,<<"/">>}
%% '''
-spec filter(
task(),
[call()] | {allow, [call()]} | {deny, [call()]},
[call()] | {allow, [call()]} | {deny, [call()]}
) -> ok.
filter(Task, Calls, SubprocessCalls) ->
?PRX_CALL(Task, filter, [to_filter(Calls), to_filter(SubprocessCalls)]).
-spec to_filter([call()] | {allow, [call()]} | {deny, [call()]}) -> binary().
to_filter(Calls) when is_list(Calls) ->
alcove:filter(Calls);
to_filter({allow, Calls}) ->
alcove:filter({allow, substitute_calls(Calls)});
to_filter({deny, Calls}) ->
alcove:filter({deny, Calls}).
substitute_calls(Calls) ->
proplists:normalize(Calls, [
{aliases, [
{replace_process_image, reexec}
]},
{expand, [
{reexec, [
cpid,
environ,
execve,
fcntl,
fcntl_constant,
fexecve,
getopt,
setcpid,
setopt
]},
{fork, [cpid, fork, close]},
{clone, [cpid, clone, close]},
{execve, [cpid, execve]},
{fexecve, [cpid, fexecve]},
{execvp, [cpid, execvp]},
{getcpid, []}
]}
]).
%% @doc Get control process attributes
%%
%% Retrieve attributes set by the prx control process %% for a child
%% process.
%%
%% @see getcpid/3
-spec getcpid(task(), atom()) -> int32_t() | false.
getcpid(Task, Opt) ->
try
?PRX_CALL(Task, getcpid, [Opt])
catch
exit:_ ->
false
end.
%% @doc Get control process attributes
%%
%% Retrieves attributes set by the prx control process for a
%% child process.
%%
%% • flowcontrol
%%
%% Number of messages allowed from process:
%%
%% -1 : flowcontrol disabled
%%
%% 0 : stdout/stderr for process is not read
%%
%% 1+ : read this many messages from the process
%%
%% • signaloneof
%%
%% Signal sent to child process on shutdown.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.178.0>}
%% 3> prx:getcpid(Task, Task1, flowcontrol).
%% -1
%% '''
-spec getcpid(task(), task() | cpid() | pid_t(), atom()) -> int32_t() | false.
getcpid(Task, Pid, Opt) when is_pid(Pid) ->
case pidof(Pid) of
noproc ->
false;
Proc ->
getcpid(Task, Proc, Opt)
end;
getcpid(Task, Pid, Opt) when is_integer(Pid) ->
?PRX_CALL(Task, getcpid, [Pid, Opt]).
%% @doc getcwd(3): return the current working directory
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:chdir(Task, "/").
%% ok
%% 3> prx:getcwd(Task).
%% {ok,<<"/">>}
%% '''
-spec getcwd(task()) -> {ok, binary()} | {error, posix()}.
getcwd(Task) ->
?PRX_CALL(Task, getcwd, []).
%% @doc getenv(3): retrieve an environment variable
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:chdir(Task, "/").
%% ok
%% 6> prx:getenv(Task, "TERM").
%% <<"screen">>
%% '''
-spec getenv(task(), iodata()) -> binary() | 'false'.
getenv(Task, Name) ->
?PRX_CALL(Task, getenv, [Name]).
%% @doc getgid(2): retrieve the process group ID
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:getgid(Task).
%% 1000
%% '''
-spec getgid(task()) -> gid_t().
getgid(Task) ->
?PRX_CALL(Task, getgid, []).
%% @doc getgroups(2): retrieve the list of supplementary groups
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:getgroups(Task).
%% {ok,[24,20,1000]}
%% '''
-spec getgroups(task()) -> {ok, [gid_t()]} | {error, posix()}.
getgroups(Task) ->
?PRX_CALL(Task, getgroups, []).
%% @doc gethostname(2): retrieve the system hostname
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:gethostname(Task).
%% {ok,<<"host1">>}
%% '''
-spec gethostname(task()) -> {ok, binary()} | {error, posix()}.
gethostname(Task) ->
?PRX_CALL(Task, gethostname, []).
%% @doc Retrieve port options for event loop
%%
%% Options are configurable per process, with the default settings inherited
%% from the parent.
%%
%% • maxchild : non_neg_integer() : 64
%%
%% Number of child processes allowed for this control process. The value
%% can be modified using setopt/4,5. Additionally, reducing RLIMIT_NOFILE
%% for the process may result in a reduced maxchild value.
%%
%% • exit_status : 1 | 0 : 1
%%
%% Controls whether the controlling Erlang process is informed of a
%% process exit value.
%%
%% • maxforkdepth : non_neg_integer() : 16
%%
%% Sets the maximum length of the prx process pipeline.
%%
%% • termsig : 1 | 0 : 1
%%
%% If a child process exits because of a signal, notify the controlling
%% Erlang process.
%%
%% • flowcontrol : int32_t() : -1 (disabled)
%%
%% Sets the default flow control behaviour for a newly forked process. Flow
%% control is applied after the child process calls exec().
%%
%% See setcpid/5.
%%
%% • signaloneof : 0-255 : 15
%%
%% Send a signal to a child process on shutdown (stdin of the prx
%% control process is closed).
%%
%% See setcpid/5.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:getopt(Task, maxchild).
%% 64
%% '''
-spec getopt(task(), prx_opt()) -> 'false' | int32_t().
getopt(Task, Opt) ->
?PRX_CALL(Task, getopt, [Opt]).
%% @doc getpgrp(2): retrieve the process group
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:getpgrp(Task).
%% 3924
%% '''
-spec getpgrp(task()) -> pid_t().
getpgrp(Task) ->
?PRX_CALL(Task, getpgrp, []).
%% @doc getpid(2): retrieve the system PID of the process
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:getpid(Task).
%% 3924
%% '''
-spec getpid(task()) -> pid_t().
getpid(Task) ->
?PRX_CALL(Task, getpid, []).
%% @doc getpriority(2): retrieve scheduling priority of process, process group or user
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:getpriority(Task).
%% {ok,0}
%% '''
-spec getpriority(task(), constant(), int32_t()) -> {ok, int32_t()} | {error, posix()}.
getpriority(Task, Which, Who) ->
?PRX_CALL(Task, getpriority, [Which, Who]).
%% @doc getresgid(2): get real, effective and saved group ID
%%
%% == Support ==
%%
%% • Linux
%%
%% • OpenBSD
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:getresgid(Task).
%% {ok,1000,1000,1000}
%% '''
-spec getresgid(task()) -> {ok, gid_t(), gid_t(), gid_t()} | {error, posix()}.
getresgid(Task) ->
?PRX_CALL(Task, getresgid, []).
%% @doc getresuid(2): get real, effective and saved user ID
%%
%% == Support ==
%%
%% • Linux
%%
%% • OpenBSD
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:getresuid(Task).
%% {ok,1000,1000,1000}
%% '''
-spec getresuid(task()) -> {ok, uid_t(), uid_t(), uid_t()} | {error, posix()}.
getresuid(Task) ->
?PRX_CALL(Task, getresuid, []).
%% @doc getsid(2): retrieve the session ID
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:getsid(Task).
%% {ok,3924}
%% '''
-spec getsid(task(), pid_t()) -> {ok, pid_t()} | {error, posix()}.
getsid(Task, OSPid) ->
?PRX_CALL(Task, getsid, [OSPid]).
%% @doc getuid(2): returns the process user ID
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 2> prx:getuid(Task).
%% 1000
%% '''
-spec getuid(task()) -> uid_t().
getuid(Task) ->
?PRX_CALL(Task, getuid, []).
%% @doc ioctl(2) : control device
%%
%% Controls a device using a file descriptor previously obtained
%% using open/4.
%%
%% Argp can be either a binary or a list representation of a C
%% struct. See prctl/6 below for a description of the list elements.
%%
%% On success, ioctl/4 returns a 2-tuple containing a map. The map keys are:
%%
%% • return_value: an integer equal to the return value of the ioctl.
%%
%% Usually 0, however some ioctl's on Linux use the return
%% value as the output parameter.
%%
%% • arg: the value depends on the type of the input parameter Argp.
%%
%% • cstruct: contains the contents of the memory pointed to by Argp
%%
%% • integer/binary: an empty binary
%%
%% == Examples ==
%%
%% An example of creating a tap device in a net namespace on Linux:
%%
%% ```
%% {ok, Child} = prx:clone(Task, [clone_newnet]),
%% {ok, FD} = prx:open(Child, "/dev/net/tun", [o_rdwr], 0),
%% {ok, #{return_value = 0, arg = <<"tap", N, _/binary>>}} = prx:ioctl(Child, FD,
%% tunsetiff, <<
%% 0:(16*8), % generate a tuntap device name
%% (16#0002 bor 16#1000):2/native-unsigned-integer-unit:8, % IFF_TAP, IFF_NO_PI
%% 0:(14*8)
%% >>),
%% {ok, <<"tap", N>>}.
%% '''
-spec ioctl(task(), fd(), constant(), cstruct()) ->
{ok, #{return_value := integer(), arg := iodata()}} | {error, posix()}.
ioctl(Task, FD, Request, Argp) ->
case ?PRX_CALL(Task, ioctl, [FD, Request, Argp]) of
{ok, ReturnValue, Arg} ->
{ok, #{return_value => ReturnValue, arg => Arg}};
Error ->
Error
end.
%% @doc jail(2): restrict the current process in a system jail
%%
%% == Support ==
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.155.0>}
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.159.0>}
%% 4> prx:jail(Task1, #{path => "/rescue", hostname => "test0", jailname => "test0"}).
%% {ok,23223}
%% 5> prx:gethostname(Task1).
%% {ok,<<"test0">>}
%% '''
-spec jail(
task(),
#{
version => alcove:uint32_t(),
path => iodata(),
hostname => iodata(),
jailname => iodata(),
ip4 => [inet:ip4_address()],
ip6 => [inet:ip6_address()]
}
| cstruct()
) -> {ok, int32_t()} | {error, posix()}.
jail(Task, Jail) when is_map(Jail) ->
Cstruct = alcove_cstruct:jail(map_to_jail(Jail)),
?PRX_CALL(Task, jail, [Cstruct]);
jail(Task, Jail) when is_list(Jail) ->
?PRX_CALL(Task, jail, [Jail]).
jail_to_map(#alcove_jail{
version = Version,
path = Path,
hostname = Hostname,
jailname = Jailname,
ip4 = IP4,
ip6 = IP6
}) ->
#{
version => Version,
path => Path,
hostname => Hostname,
jailname => Jailname,
ip4 => IP4,
ip6 => IP6
}.
map_to_jail(Map0) ->
#{
version := Version,
path := Path,
hostname := Hostname,
jailname := Jailname,
ip4 := IP4,
ip6 := IP6
} = maps:merge(jail_to_map(#alcove_jail{}), Map0),
#alcove_jail{
version = Version,
path = Path,
hostname = Hostname,
jailname = Jailname,
ip4 = IP4,
ip6 = IP6
}.
%% @doc kill(2): terminate a process
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.158.0>}
%% 3> Pid = prx:getpid(Task1).
%% 70524
%% 4> prx:kill(Task, 0, 0).
%% ok
%% 5> prx:kill(Task, 12345, 0).
%% {error,esrch}
%% 6> prx:kill(Task, Pid, 0).
%% ok
%% 7> prx:kill(Task, Pid, sigkill).
%% ok
%% 8> prx:kill(Task, Pid, 0).
%% {error,esrch}
%% 9> flush().
%% Shell got {termsig,<0.158.0>,sigkill}
%% ok
%% '''
-spec kill(task(), pid_t(), constant()) -> ok | {error, posix()}.
kill(Task, OSPid, Signal) ->
?PRX_CALL(Task, kill, [OSPid, Signal]).
%% @doc lseek(2): set file offset for read/write
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.154.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.169.0>}
%% 3> {ok, FD} = prx:open(Task1, "/etc/passwd", [o_rdonly]).
%% {ok,7}
%% 4> prx:lseek(Task1, FD, 0, 0).
%% ok
%% '''
-spec lseek(task(), fd(), off_t(), int32_t()) -> ok | {error, posix()}.
lseek(Task, FD, Offset, Whence) ->
?PRX_CALL(Task, lseek, [FD, Offset, Whence]).
%% @doc mkdir(2) : create a directory
-spec mkdir(task(), iodata(), mode_t()) -> ok | {error, posix()}.
mkdir(Task, Path, Mode) ->
?PRX_CALL(Task, mkdir, [Path, Mode]).
%% @doc mkfifo(3) : create a named pipe
-spec mkfifo(task(), iodata(), mode_t()) -> ok | {error, posix()}.
mkfifo(Task, Path, Mode) ->
?PRX_CALL(Task, mkfifo, [Path, Mode]).
%% @doc mount(2) : mount a filesystem, Linux style
%%
%% The arguments are:
%%
%% • source
%%
%% • target
%%
%% • filesystem type
%%
%% • flags
%%
%% • data
%%
%% An empty list may be used to specify NULL.
%%
%% For example, filesystems mounted in a Linux mount namespace may be
%% visible in the global mount namespace. To avoid this, first remount the
%% root filesystem within mount namespace using the `MS_REC|MS_PRIVATE'
%% flags:
%%
%% ```
%% {ok, Task} = prx:clone(Parent, [clone_newns]),
%% ok = prx:mount(Task, "none", "/", "", [ms_rec, ms_private], "").
%% '''
%%
%% On BSD systems, the `Source' argument is ignored and passed to
%% the system mount call as:
%%
%% mount(FSType, Target, Flags, Data);
%%
%% == Examples ==
%%
%% An example of bind mounting a directory within a linux mount namespace:
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.192.0>}
%% 3> {ok, Task1} = prx:clone(Task, [clone_newns]).
%% {ok,<0.196.0>}
%% 3> prx:mount(Task1, "/tmp", "/mnt", "", [ms_bind, ms_rdonly, ms_noexec], "").
%% ok
%% 4> prx:umount(Task1, "/mnt").
%% ok
%% '''
-spec mount(task(), iodata(), iodata(), iodata(), uint64_t() | [constant()], iodata()) ->
ok | {error, posix()}.
mount(Task, Source, Target, FSType, Flags, Data) ->
mount(Task, Source, Target, FSType, Flags, Data, <<>>).
%% @doc (Solaris) mount(2) : mount a filesystem
%%
%% On Solaris, some mount options are passed in the `Options' argument
%% as a string of comma separated values terminated by a NULL.
%% Other platforms ignore the Options parameter.
%%
%% @see mount/6
-spec mount(task(), iodata(), iodata(), iodata(), uint64_t() | [constant()], iodata(), iodata()) ->
ok | {error, posix()}.
mount(Task, Source, Target, FSType, Flags, Data, Options) ->
?PRX_CALL(Task, mount, [Source, Target, FSType, Flags, Data, Options]).
%% @doc open(2): returns a file descriptor associated with a file
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.192.0>}
%% 2> prx:open(Task, "/etc/hosts", [o_rdonly]).
%% {ok,7}
%% '''
-spec open(task(), iodata(), int32_t() | [constant()]) -> {ok, fd()} | {error, posix()}.
open(Task, Path, Flags) ->
open(Task, Path, Flags, 0).
%% @doc open(2) : open a file specifying permissions
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.192.0>}
%% 2> prx:open(Task, "/tmp/prx-open-test", [o_wronly,o_creat], 8#644).
%% {ok,7}
%% '''
-spec open(task(), iodata(), int32_t() | [constant()], mode_t()) ->
{ok, fd()} | {error, posix()}.
open(Task, Path, Flags, Mode) ->
?PRX_CALL(Task, open, [Path, Flags, Mode]).
%% @doc pivot_root(2): change the root mount
%%
%% Use pivot_root(2) in a Linux mount namespace to change the root
%% filesystem.
%%
%% Warning: using pivot_root(2) in the global namespace may have unexpected
%% effects.
%%
%% To use an arbitrary directory as a mount point:
%%
%% • mark the mount namespace as private
%%
%% • create a mount point by bind mounting the new root directory over
%% itself
%%
%% • change the current working directory to the new root directory
%%
%% • call pivot_root(2) with new and old root set to the current working
%% directory
%%
%% • unmount the current working directory
%%
%% == Support ==
%%
%% • Linux
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.192.0>}
%% 3> {ok, Task1} = prx:clone(Task, [clone_newns]).
%% {ok,<0.196.0>}
%% 4> prx:mkdir(Task, "/tmp/prx-root", 8#755).
%% ok
%% 5> {ok, Task1} = prx:clone(Task, [clone_newns]).
%% {ok,<0.210.0>}
%% 6> prx:mount(Task1, "none", "/", [], [ms_rec, ms_private], []).
%% ok
%% 7> prx:mount(Task1, "/tmp/prx-root", "/tmp/prx-root", [], [ms_bind], []).
%% ok
%% 8> prx:chdir(Task1, "/tmp/prx-root").
%% ok
%% 9> prx:pivot_root(Task1, ".", ".").
%% ok
%% 10> prx:umount2(Task1, ".", [mnt_detach]).
%% ok
%% '''
-spec pivot_root(task(), iodata(), iodata()) -> ok | {error, posix()}.
pivot_root(Task, NewRoot, PutOld) ->
?PRX_CALL(Task, pivot_root, [NewRoot, PutOld]).
%% @doc pledge(2): restrict system operations
%%
%% An empty list ([]) specifies promises should not be changed. Warning:
%% an empty string ("") is equivalent to an empty list.
%%
%% To specify no capabilities, use an empty binary: `<<>>>' or `<<"">>'
%%
%% == Support ==
%%
%% • OpenBSD
%%
%% == Examples ==
%%
%% Fork a control process:
%%
%% • restricted to stdio, proc and exec capabilities
%%
%% • unrestricted after calling exec
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.152.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.156.0>}
%% 3> prx:pledge(Task, <<"stdio proc exec">>, []).
%% ok
%% '''
-spec pledge(task(), iodata(), iodata()) -> ok | {error, posix()}.
pledge(Task, Promises, ExecPromises) ->
?PRX_CALL(Task, pledge, [Promises, ExecPromises]).
%% @doc prctl(2) : operations on a process
%%
%% This function can be used to set BPF syscall filters on processes
%% (seccomp mode).
%%
%% A list can be used for prctl operations requiring a C structure
%% as an argument. List elements are used to contiguously populate
%% a buffer (it is up to the caller to add padding):
%%
%% • `binary()': the element is copied directly into the buffer
%%
%% On return, the contents of the binary is returned to the
%% caller.
%%
%% • `{ptr, N}': N bytes of zero'ed memory is allocated. The pointer
%% is placed in the buffer.
%%
%% On return, the contents of the memory is returned to the
%% caller.
%%
%% • `{ptr, binary()}'
%%
%% Memory equal to the size of the binary is allocated and
%% initialized with the contents of the binary.
%%
%% On return, the contents of the memory is returned to the
%% caller.
%%
%% == Support ==
%%
%% • Linux
%%
%% == Examples ==
%%
%%The prx process requires the following syscalls to run:
%%
%% ```
%% sys_exit
%% sys_exit_group
%% sys_getrlimit
%% sys_poll
%% sys_read
%% sys_restart_syscall
%% sys_rt_sigreturn
%% sys_setrlimit
%% sys_sigreturn
%% sys_ugetrlimit
%% sys_write
%% sys_writev
%% '''
%%
%% To enforce a seccomp filter:
%%
%% ```
%% -module(seccomp).
%%
%% -include_lib("alcove/include/alcove_seccomp.hrl").
%%
%% -export([run/1, run/2, filter/2]).
%%
%% -define(DENY_SYSCALL(Syscall), [
%% ?BPF_JUMP(?BPF_JMP + ?BPF_JEQ + ?BPF_K, (Syscall), 0, 1),
%% ?BPF_STMT(?BPF_RET + ?BPF_K, ?SECCOMP_RET_KILL)
%% ]).
%%
%% filter(Task, Syscall) ->
%% Arch = prx:call(Task, syscall_constant, [alcove:audit_arch()]),
%% NR = prx:call(Task, syscall_constant, [Syscall]),
%%
%% [
%% ?VALIDATE_ARCHITECTURE(Arch),
%% ?EXAMINE_SYSCALL,
%% ?DENY_SYSCALL(NR),
%% ?BPF_STMT(?BPF_RET + ?BPF_K, ?SECCOMP_RET_ALLOW)
%% ].
%%
%% run(Task) ->
%% run(Task, sys_getcwd).
%%
%% run(Task, Syscall) ->
%% Filter = filter(Task, Syscall),
%%
%% {ok, _, _, _, _, _} = prx:prctl(Task, pr_set_no_new_privs, 1, 0, 0, 0),
%% Pad = (erlang:system_info({wordsize, external}) - 2) * 8,
%%
%% Prog = [
%% <<(iolist_size(Filter) div 8):2/native-unsigned-integer-unit:8>>,
%% <<0:Pad>>,
%% {ptr, list_to_binary(Filter)}
%% ],
%% %% prx:seccomp(Task, seccomp_set_mode_filter, 0, Prog)
%% prx:prctl(Task, pr_set_seccomp, seccomp_mode_filter, Prog, 0, 0).
%% '''
%%
%% To enforce the filter:
%%
%% ```
%% 1> catch_exception(true).
%% false
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.186.0>}
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.191.0>}
%% 4> seccomp:run(Task1, sys_getcwd).
%% {ok,0,2,
%% [<<7,0>>,
%% <<0,0,0,0,0,0>>,
%% {ptr,<<32,0,0,0,4,0,0,0,21,0,1,0,62,0,0,192,6,0,0,0,...>>}],
%% 0,0}
%% 5> prx:getcwd(Task1).
%% '''
%%
%% @see seccomp/4
-spec prctl(task(), constant(), ptr_arg(), ptr_arg(), ptr_arg(), ptr_arg()) ->
{ok, integer(), ptr_val(), ptr_val(), ptr_val(), ptr_val()} | {error, posix()}.
prctl(Task, Arg1, Arg2, Arg3, Arg4, Arg5) ->
?PRX_CALL(Task, prctl, [Arg1, Arg2, Arg3, Arg4, Arg5]).
%% @doc procctl(2): control processes
%%
%% == Support ==
%%
%% • FreeBSD
%%
%% == Examples ==
%%
%% ```
%% Pid = prx:pidof(Task),
%% prx:procctl(Task, 0, Pid, 'PROC_REAP_ACQUIRE', []),
%% prx:procctl(Task, p_pid, Pid, 'PROC_REAP_STATUS', [
%% <<0,0,0,0>>, % rs_flags
%% <<0,0,0,0>>, % rs_children
%% <<0,0,0,0>>, % rs_descendants
%% <<0,0,0,0>>, % rs_reaper
%% <<0,0,0,0>> % rs_pid
%% ]).
%% '''
-spec procctl(task(), constant(), pid_t(), constant(), [] | cstruct()) ->
{ok, binary(), cstruct()} | {error, posix()}.
procctl(Task, IDType, ID, Cmd, Data) ->
?PRX_CALL(Task, procctl, [IDType, ID, Cmd, Data]).
%% @doc ptrace(2): process trace
%%
%% == Examples ==
%%
%% ```
%% -module(ptrace).
%%
%% -export([run/0]).
%%
%% run() ->
%% {ok, Task} = prx:fork(),
%% {ok, Task1} = prx:fork(Task),
%% {ok, Task2} = prx:fork(Task1),
%%
%% Pid2 = prx:pidof(Task2),
%%
%% % disable the prx event loop: child process must be managed by
%% % the caller
%% {ok, sig_dfl} = prx:sigaction(Task1, sigchld, sig_info),
%%
%% % enable ptracing in the child process and exec() a command
%% {ok, 0, <<>>, <<>>} = prx:ptrace(Task2, ptrace_traceme, 0, 0, 0),
%% ok = prx:execvp(Task2, "cat", ["cat"]),
%%
%% % the parent is notified
%% ok =
%% receive
%% {signal, Task1, sigchld, _} ->
%% ok
%% after 5000 ->
%% timeout
%% end,
%%
%% {ok, Pid2, _, [{stopsig, sigtrap}]} = prx:waitpid(Task1, -1, [wnohang]),
%%
%% % should be no other events
%% {ok, 0, 0, []} = prx:waitpid(Task1, -1, [wnohang]),
%%
%% % allow the process to continue
%% {ok, 0, <<>>, <<>>} = prx:ptrace(Task1, ptrace_cont, Pid2, 0, 0),
%%
%% ok = prx:stdin(Task2, "test\n"),
%%
%% ok =
%% receive
%% {stdout, Task2, <<"test\n">>} ->
%% ok
%% after 5000 -> timeout
%% end,
%%
%% % Send a SIGTERM and re-write it to a harmless SIGWINCH
%% ok = prx:kill(Task1, Pid2, sigterm),
%% ok =
%% receive
%% {signal, Task1, sigchld, _} ->
%% ok
%% after 5000 ->
%% timeout
%% end,
%%
%% {ok, Pid2, _, [{stopsig, sigterm}]} = prx:waitpid(Task1, -1, [wnohang]),
%%
%% {ok, 0, <<>>, <<>>} = prx:ptrace(Task1, ptrace_cont, Pid2, 0, 28),
%%
%% % Convert a SIGWINCH to SIGTERM
%% ok = prx:kill(Task1, Pid2, sigwinch),
%% ok =
%% receive
%% {signal, Task1, sigchld, _} ->
%% ok
%% after 5000 ->
%% timeout
%% end,
%%
%% {ok, 0, <<>>, <<>>} = prx:ptrace(Task1, ptrace_cont, Pid2, 0, 15),
%% {ok, Pid2, _, [{termsig, sigterm}]} = prx:waitpid(Task1, -1, []).
%% '''
-spec ptrace(task(), constant(), pid_t(), ptr_arg(), ptr_arg()) ->
{ok, integer(), ptr_val(), ptr_val()} | {error, posix()}.
ptrace(Task, Request, OSPid, Addr, Data) ->
?PRX_CALL(Task, ptrace, [Request, OSPid, Addr, Data]).
%% @doc read(2): read bytes from a file descriptor
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.212.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.216.0>}
%% 3> {ok, FD} = prx:open(Task1, "/etc/hosts", [o_rdonly]).
%% {ok,7}
%% 4> prx:read(Task1, FD, 64).
%% {ok,<<"127.0.0.1 localhost\n\n# The following lines are desirable for IPv">>}
%% '''
-spec read(task(), fd(), size_t()) -> {ok, binary()} | {error, posix()}.
read(Task, FD, Count) ->
?PRX_CALL(Task, read, [FD, Count]).
%% @doc readdir(3): retrieve list of objects in a directory
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.212.0>}
%% 2> prx:readdir(Task, "/dev/pts").
%% {ok,[<<".">>,<<"..">>,<<"66">>,<<"63">>,<<"67">>,<<"64">>,
%% <<"62">>,<<"61">>,<<"60">>,<<"59">>,<<"58">>,<<"57">>,
%% <<"56">>,<<"55">>,<<"54">>,<<"53">>,<<"52">>,<<"51">>,
%% <<"50">>,<<"49">>,<<"48">>,<<"47">>,<<"46">>,<<"45">>,
%% <<"44">>,<<"43">>,<<...>>|...]}
%% '''
-spec readdir(task(), iodata()) -> {ok, [binary()]} | {error, posix()}.
readdir(Task, Path) ->
?PRX_CALL(Task, readdir, [Path]).
%% @doc rmdir(2): delete a directory
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.212.0>}
%% 2> prx:mkdir(Task, "/tmp/prx-rmdir-test", 8#755).
%% ok
%% 3> prx:rmdir(Task, "/tmp/prx-rmdir-test").
%% ok
%% '''
-spec rmdir(task(), iodata()) -> ok | {error, posix()}.
rmdir(Task, Path) ->
?PRX_CALL(Task, rmdir, [Path]).
%% @doc seccomp(2) : restrict system operations
%%
%% == Support ==
%%
%% • Linux
%%
%% == Examples ==
%%
%% ```
%% %% Equivalent to:
%% %% prx:prctl(Task, pr_set_seccomp, seccomp_mode_filter, Prog, 0, 0).
%% prx:seccomp(Task, seccomp_set_mode_filter, 0, Prog)
%% '''
%%
%% @see prctl/6
-spec seccomp(task(), constant(), constant(), cstruct()) -> ok | {error, posix()}.
seccomp(Task, Operation, Flags, Prog) ->
?PRX_CALL(Task, seccomp, [Operation, Flags, Prog]).
%% @doc setcpid() : Set options for child process of prx control process
%%
%% Control behaviour of an exec()'ed process.
%%
%% @see setcpid/4
-spec setcpid(task(), atom(), int32_t()) -> boolean().
setcpid(Task, Opt, Val) when is_pid(Task) ->
try
?PRX_CALL(Task, setcpid, [Opt, Val])
catch
exit:_ ->
false
end.
%% @doc setcpid() : Set options for child process of prx control process
%%
%% `flowcontrol' enables rate limiting of the stdout and stderr of a child
%% process. stdin is not rate limited (default: -1 (disabled))
%%
%% • 0: stdout/stderr for process is not read
%%
%% • 1-2147483646: read this many messages from the process
%%
%% • -1: disable flow control
%%
%% NOTE: the limit applies to stdout and stderr. If the limit is set to 1,
%% it is possible to get:
%%
%% • 1 message from stdout
%%
%% • 1 message from stderr
%%
%% • 1 message from stdout and stderr
%%
%% `signaloneof' delivers a signal to any subprocesses when the alcove
%% control process shuts down (default: 15 (SIGTERM))
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.162.0>}
%% 3> prx:setcpid(Task1, flowcontrol, 0).
%% true
%% 4> prx:getcpid(Task1, flowcontrol).
%% 0
%% '''
-spec setcpid(task(), task() | cpid() | pid_t(), atom(), int32_t()) -> boolean().
setcpid(Task, Pid, Opt, Val) when is_pid(Pid) ->
case pidof(Pid) of
noproc ->
false;
Proc ->
setcpid(Task, Proc, Opt, Val)
end;
setcpid(Task, CPid, Opt, Val) when is_map(CPid) ->
#{pid := Pid} = CPid,
setcpid(Task, Pid, Opt, Val);
setcpid(Task, CPid, Opt, Val) when is_integer(CPid) ->
?PRX_CALL(Task, setcpid, [CPid, Opt, Val]).
%% @doc setenv(3): set an environment variable
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 2> prx:setenv(Task, "TEST", "foo", 0).
%% ok
%% 3> prx:getenv(Task, "TEST").
%% <<"foo">>
%% 4> prx:setenv(Task, "TEST", "bar", 0).
%% ok
%% 5> prx:getenv(Task, "TEST").
%% <<"foo">>
%% 6> prx:setenv(Task, "TEST", "bar", 1).
%% ok
%% 7> prx:getenv(Task, "TEST").
%% <<"bar">>
%% '''
-spec setenv(task(), iodata(), iodata(), int32_t()) -> ok | {error, posix()}.
setenv(Task, Name, Value, Overwrite) ->
?PRX_CALL(Task, setenv, [Name, Value, Overwrite]).
%% @doc setgid(2): set the GID of the process
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.159.0>}
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.163.0>}
%% 4> prx:setgid(Task1, 123).
%% ok
%% 5> prx:getgid(Task1).
%% 123
%% '''
-spec setgid(task(), gid_t()) -> ok | {error, posix()}.
setgid(Task, Gid) ->
?PRX_CALL(Task, setgid, [Gid]).
%% @doc setgroups(2): set the supplementary groups of the process
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.160.0>}
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.164.0>}
%% 4> prx:setgroups(Task1, []).
%% ok
%% 5> prx:getgroups(Task1).
%% {ok,[]}
%% '''
-spec setgroups(task(), [gid_t()]) -> ok | {error, posix()}.
setgroups(Task, Groups) ->
?PRX_CALL(Task, setgroups, [Groups]).
%% @doc sethostname(2): set the system hostname
%%
%% This function is probably only useful if running in a uts namespace or
%% a jail.
%%
%% == Examples ==
%%
%% ```
%% {ok, Child} = prx:clone(Task, [clone_newuts]),
%% ok = prx:sethostname(Child, "test"),
%% Hostname1 = prx:gethostname(Task),
%% Hostname2 = prx:gethostname(Child),
%% Hostname1 =/= Hostname2.
%% '''
-spec sethostname(task(), iodata()) -> ok | {error, posix()}.
sethostname(Task, Hostname) ->
?PRX_CALL(Task, sethostname, [Hostname]).
%% @doc setns(2): attach to a namespace
%%
%% A process namespace is represented as a path in the /proc filesystem. The
%% path is `/proc/<pid>/ns/<ns>', where:
%%
%% • pid: the system PID
%%
%% • ns: a file representing the namespace
%%
%% The available namespaces is dependent on the kernel version. You can
%% see which are supported by running:
%%
%% ```
%% ls -al /proc/$$/ns
%% '''
%%
%% == Support ==
%%
%% • Linux
%%
%% == Examples ==
%%
%% Attach a process to an existing network namespace:
%%
%% ```
%% {ok, Child1} = prx:clone(Task, [clone_newnet]),
%% {ok, Child2} = prx:fork(Task),
%%
%% % Move Child2 into the Child1 network namespace
%% {ok, FD} = prx:open(
%% Child2,
%% ["/proc/", integer_to_list(Child1), "/ns/net"],
%% [o_rdonly],
%% 0
%% ),
%% ok = prx:setns(Child2, FD),
%% ok = prx:close(Child2, FD).
%% '''
-spec setns(task(), fd()) -> ok | {error, posix()}.
setns(Task, FD) ->
setns(Task, FD, 0).
%% @doc (Linux) setns(2) : attach to a namespace, specifying
%% namespace type
%%
%% ```
%% ok = prx:setns(Task, FD, clone_newnet)
%% '''
%%
%% @see setns/2
-spec setns(task(), fd(), constant()) -> ok | {error, posix()}.
setns(Task, FD, NSType) ->
?PRX_CALL(Task, setns, [FD, NSType]).
%% @doc Set port options
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.160.0>}
%% 2> prx:setopt(Task, maxforkdepth, 128).
%% true
%% '''
%%
%% @see getopt/2
-spec setopt(task(), prx_opt(), int32_t()) -> boolean().
setopt(Task, Opt, Val) ->
?PRX_CALL(Task, setopt, [Opt, Val]).
%% @doc setpgid(2): set process group
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.162.0>}
%% 3> prx:setpgid(Task1, 0, 0).
%% ok
%% '''
-spec setpgid(task(), pid_t(), pid_t()) -> ok | {error, posix()}.
setpgid(Task, OSPid, Pgid) ->
?PRX_CALL(Task, setpgid, [OSPid, Pgid]).
%% @doc setpriority(2): set scheduling priority of process, process group or user
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.162.0>}
%% 3> prx:setpriority(Task1, prio_process, 0, 10).
%% ok
%% 4> prx:getpriority(Task1, prio_process, 0).
%% {ok,10}
%% '''
-spec setpriority(task(), constant(), int32_t(), int32_t()) -> ok | {error, posix()}.
setpriority(Task, Which, Who, Prio) ->
?PRX_CALL(Task, setpriority, [Which, Who, Prio]).
%% @doc setresgid(2): set real, effective and saved group ID
%%
%% == Support ==
%%
%% • Linux
%%
%% • FreeBSD
%%
%% • OpenBSD
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.162.0>}
%% 4> prx:setresgid(Task1, 123, 123, 123).
%% ok
%% '''
-spec setresgid(task(), gid_t(), gid_t(), gid_t()) -> ok | {error, posix()}.
setresgid(Task, Real, Effective, Saved) ->
?PRX_CALL(Task, setresgid, [Real, Effective, Saved]).
%% @doc setresuid(2): set real, effective and saved user ID
%%
%% == Support ==
%%
%% • Linux
%%
%% • FreeBSD
%%
%% • OpenBSD
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.162.0>}
%% 3> prx:setresuid(Task1, 123, 123, 123).
%% ok
%% '''
-spec setresuid(task(), uid_t(), uid_t(), uid_t()) -> ok | {error, posix()}.
setresuid(Task, Real, Effective, Saved) ->
?PRX_CALL(Task, setresuid, [Real, Effective, Saved]).
%% @doc setsid(2): create a new session
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.162.0>}
%% 3> prx:setsid(Task1).
%% {ok,32141}
%% '''
-spec setsid(task()) -> {ok, pid_t()} | {error, posix()}.
setsid(Task) ->
?PRX_CALL(Task, setsid, []).
%% @doc setuid(2): change UID
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.162.0>}
%% 3> prx:setuid(Task1, 123).
%% ok
%% 3> prx:getuid(Task1).
%% 123
%% '''
%%
%% @see setresuid/4
-spec setuid(task(), uid_t()) -> ok | {error, posix()}.
setuid(Task, User) ->
?PRX_CALL(Task, setuid, [User]).
%% @doc sigaction(2): set process behaviour for signals
%%
%% • sig_dfl
%%
%% Uses the default behaviour for the signal
%%
%% • sig_ign
%%
%% Ignores the signal
%%
%% • sig_info
%%
%% Catches the signal and sends the controlling Erlang process an event:
%%
%% ```
%% {signal, atom(), Info}
%% '''
%%
%% Info is a binary containing the siginfo_t structure. See sigaction(2)
%% for details.
%%
%% • []
%%
%% Returns the current handler for the signal.
%%
%% Multiple caught signals of the same type may be reported as one event.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.182.0>}
%% 3> prx:kill(Task, prx:pidof(Task1), sigterm).
%% ok
%% 4> flush().
%% Shell got {signal,<0.182.0>,sigterm,
%% <<15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,156,126,0,0,232,3,0,0,0,
%% 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
%% 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
%% 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
%% 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>}
%% ok
%% 5> prx:sigaction(Task1, sigterm, sig_ign).
%% {ok,sig_info}
%% 6> prx:kill(Task, prx:pidof(Task1), sigterm).
%% ok
%% 7> flush().
%% ok
%% 8> prx:sigaction(Task1, sigterm, sig_info).
%% {ok,sig_ign}
%% 9> prx:kill(Task, prx:pidof(Task1), sigterm).
%% ok
%% 10> flush().
%% Shell got {signal,<0.182.0>,sigterm,
%% <<15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,156,126,0,0,232,3,0,0,0,
%% 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
%% 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
%% 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
%% 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>}
%% ok
%% '''
-spec sigaction(
task(),
constant(),
atom() | {sig_info, fun((pid(), [pid_t()], atom(), binary()) -> any())}
) -> {ok, atom()} | {error, posix()}.
sigaction(Task, Signal, {sig_info, Fun}) when is_atom(Signal), is_function(Fun, 4) ->
case gen_statem:call(Task, {sigaction, Signal, Fun}, infinity) of
ok ->
sigaction(Task, Signal, sig_info);
_ ->
{error, einval}
end;
sigaction(Task, Signal, Handler) ->
?PRX_CALL(Task, sigaction, [Signal, Handler]).
%% @doc socket(2): returns a file descriptor for a communication endpoint
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% 2> {ok, FD} = prx:socket(Task, af_inet, sock_stream, 0).
%% {ok,7}
%% '''
-spec socket(task(), constant(), constant(), int32_t()) -> {ok, fd()} | {error, posix()}.
socket(Task, Domain, Type, Protocol) ->
?PRX_CALL(Task, socket, [Domain, Type, Protocol]).
%% @doc umount(2): unmount a filesystem
%%
%% On BSD systems, calls unmount(2).
%%
%% == Examples ==
%%
%% An example of bind mounting a directory within a linux mount namespace:
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.192.0>}
%% 3> {ok, Task1} = prx:clone(Task, [clone_newns]).
%% {ok,<0.196.0>}
%% 3> prx:mount(Task1, "/tmp", "/mnt", "", [ms_bind, ms_rdonly, ms_noexec], "").
%% ok
%% 4> prx:umount(Task1, "/mnt").
%% ok
%% '''
-spec umount(task(), iodata()) -> ok | {error, posix()}.
umount(Task, Path) ->
?PRX_CALL(Task, umount, [Path]).
%% @doc umount2(2) : unmount a filesystem
%%
%% On BSD systems, calls unmount(2).
%%
%% == Examples ==
%%
%% @see pivot_root/3
-spec umount2(task(), iodata(), [constant()]) -> ok | {error, posix()}.
umount2(Task, Path, Flags) ->
?PRX_CALL(Task, umount2, [Path, Flags]).
%% @doc unlink(2): delete a name from the filesystem
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% 2> prx:open(Task, "/tmp/testfile", [o_wronly, o_creat], 8#644).
%% {ok,8}
%% 3> prx:unlink(Task, "/tmp/testfile").
%% ok
%% '''
-spec unlink(task(), iodata()) -> ok | {error, posix()}.
unlink(Task, Path) ->
?PRX_CALL(Task, unlink, [Path]).
%% @doc unsetenv(3): remove an environment variable
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% 2> prx:setenv(Task, "TEST", "foo", 0).
%% ok
%% 3> prx:getenv(Task, "TEST").
%% <<"foo">>
%% 4> prx:unsetenv(Task, "TEST").
%% ok
%% 5> prx:getenv(Task, "TEST").
%% false
%% '''
-spec unsetenv(task(), iodata()) -> ok | {error, posix()}.
unsetenv(Task, Name) ->
?PRX_CALL(Task, unsetenv, [Name]).
%% @doc unshare(2): create a new namespace in the current process
%%
%% Make a new namespace without calling clone(2):
%%
%% ```
%% % The port is now running in a namespace without network access.
%% ok = prx:unshare(Task, [clone_newnet]).
%% '''
%%
%% == Examples ==
%%
%% ```
%% 1> prx:sudo().
%% ok
%% 2> {ok, Task} = prx:fork().
%% {ok,<0.179.0>}
%% 3> {ok, Task1} = prx:fork(Task).
%% {ok,<0.183.0>}
%% 4> prx:unshare(Task1, [clone_newuts]).
%% ok
%% 5> prx:sethostname(Task1, "unshare").
%% ok
%% 6> prx:gethostname(Task1).
%% {ok,<<"unshare">>}
%% 7> prx:gethostname(Task).
%% {ok,<<"host1">>}
%% '''
-spec unshare(task(), int32_t() | [constant()]) -> ok | {error, posix()}.
unshare(Task, Flags) ->
?PRX_CALL(Task, unshare, [Flags]).
%% @doc unveil(2): restrict filesystem view
%%
%% To disable unveil calls, use an empty list ([]) or, equivalently, an
%% empty string ("").
%%
%% ```
%% prx:unveil(Task, <<"/etc">>, <<"r">>),
%% prx:unveil(Task, [], []).
%% '''
%%
%% == Support ==
%%
%% • OpenBSD
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.152.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.156.0>}
%% 3> prx:unveil(Task1, <<"/etc">>, <<"r">>).
%% ok
%% 4> prx:unveil(Task1, [], []).
%% ok
%% 5> prx:readdir(Task1, "/etc").
%% {ok,[<<".">>,<<"..">>,<<"acme">>,<<"amd">>,<<"authpf">>,
%% <<"daily">>,<<"disktab">>,<<"examples">>,<<"firmware">>,
%% <<"hotplug">>,<<"iked">>,<<"isakmpd">>,<<"ldap">>,
%% <<"magic">>,<<"mail">>,<<"moduli">>,<<"monthly">>,
%% <<"mtree">>,<<"netstart">>,<<"npppd">>,<<"pf.os">>,
%% <<"ppp">>,<<"protocols">>,<<"rc">>,<<"rc.conf">>,<<"rc.d">>,
%% <<...>>|...]}
%% 6> prx:readdir(Task1, "/tmp").
%% {error,enoent}
%% '''
-spec unveil(task(), iodata(), iodata()) -> ok | {error, posix()}.
unveil(Task, Path, Permissions) ->
?PRX_CALL(Task, unveil, [Path, Permissions]).
%% @doc waitpid(2): wait for process to change state
%%
%% Process state changes are handled by the alcove SIGCHLD event handler
%% by default. To use waitpid/4,5, disable the signal handler:
%%
%% ```
%% {ok, sig_dfl} = prx:sigaction(Task, sigchld, sig_info),
%% {ok, Child} = prx:fork(Task),
%% Pid = prx:getpid(Child),
%% ok = prx:exit(Child, 2),
%% {ok, Pid, _, [{exit_status, 2}]} = prx:waitpid(Task, Pid, [wnohang]).
%% '''
%%
%% Note: if the default SIGCHLD handler is disabled, waitpid/4,5 should be
%% called to reap zombie processes:
%%
%% ```
%% prx:waitpid(Task, -1, [wnohang])
%% '''
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.158.0>}
%% 2> {ok, Task1} = prx:fork(Task).
%% {ok,<0.162.0>}
%% 3> prx:sigaction(Task1, sigchld, sig_info).
%% {ok,sig_dfl}
%% 4> prx:execvp(Task1, ["sleep", "20"]).
%% ok
%% 5> prx:waitpid(Task, -1, []).
%% {ok,30958,0,[{exit_status,0}]}
%% '''
-spec waitpid(task(), pid_t(), int32_t() | [constant()]) ->
{ok, pid_t(), int32_t(), [waitstatus()]} | {error, posix()}.
waitpid(Task, OSPid, Options) ->
?PRX_CALL(Task, waitpid, [OSPid, Options]).
%% @doc write(2): write to a file descriptor
%%
%% Writes a buffer to a file descriptor and returns the number of bytes
%% written.
%%
%% == Examples ==
%%
%% ```
%% 1> {ok, Task} = prx:fork().
%% {ok,<0.178.0>}
%% 2> {ok, FD} = prx:open(Task, "/tmp/testfile", [o_wronly, o_creat], 8#644).
%% {ok,7}
%% 3> prx:write(Task, FD, <<"test">>).
%% {ok,4}
%% '''
%% -spec write(task(), fd(), iodata()) -> {ok, ssize_t()} | {error, posix()}.
write(Task, FD, Buf) ->
?PRX_CALL(Task, write, [FD, Buf]).