View Source prx (prx v1.0.0)

Summary

Functions

Register a function to be called at task termination.

Make a synchronous call into the port driver

cap_enter(2): place process into capability mode

cap_fcntls_get(2): get allowed fcntl commands in capability mode

cap_fcntls_limit(2): manage fcntl commands in capability mode

cap_getmode(2): check if capability mode is enabled

cap_ioctls_limit(2): manage allowed ioctl commands

cap_rights_limit(2): manage process capabilities

chdir(2): change process current working directory

chmod(2): change file permissions

chown(2): change file ownership

chroot(2): change root directory

clearenv(3): zero process environment

clone(2): create a new process

close(2): close a file descriptor

Run and return command output

Assign a new process owner

Returns the list of child PIDs for this process.

Retrieve process info for forked processes

Get the gen_server PID for the task

environ(7): return the process environment variables

Close stdin of a task

Close task standard I/O file descriptor

Close stdin, stdout or stderr of child process.

Test if the task has called exec(2)

execve(2): replace process image with environment

execve(2): replace process image with environment and set process name

execvp(2): replace the current process image using the search path

execvp(2): replace the current process image using the search path and set process name

exit(3): cause an prx control process to exit

fcntl(2) : perform operation on a file descriptor

fcntl(2): perform operations on a file descriptor with argument

fexecve(2): replace the process image

filter/2 : restrict control process calls

filter/3 : restrict control process and subprocess calls

fork(2): create a new system process

fork(2): create a child process

Get control process attributes

Get control process attributes

getcwd(3): return the current working directory

getenv(3): retrieve an environment variable

getgid(2): retrieve the process group ID

getgroups(2): retrieve the list of supplementary groups

gethostname(2): retrieve the system hostname

Retrieve port options for event loop

getpgrp(2): retrieve the process group

getpid(2): retrieve the system PID of the process

getpriority(2): retrieve scheduling priority of process, process group or user

getresgid(2): get real, effective and saved group ID

getresuid(2): get real, effective and saved user ID

getrlimit(2): retrieve the resource limits for a process

getsid(2): retrieve the session ID

getuid(2): returns the process user ID

ioctl(2) : control device

jail(2): restrict the current process in a system jail

kill(2): terminate a process

lseek(2): set file offset for read/write

mkdir(2) : create a directory

mkfifo(3) : create a named pipe

mount(2) : mount a filesystem, Linux style

(Solaris) mount(2) : mount a filesystem

open(2): returns a file descriptor associated with a file

open(2) : open a file specifying permissions

Get the parent PID for the task

Retrieves the system PID of the process similar to getpid(2)

Get the process pipeline list for the task

pivot_root(2): change the root mount

pledge(2): restrict system operations

prctl(2) : operations on a process

procctl(2): control processes

ptrace(2): process trace

read(2): read bytes from a file descriptor

readdir(3): retrieve list of objects in a directory

Fork+exec prx process.

Replace the port process image using execve(2)/fexecve(2).

Equivalent to reexec / 1.

Equivalent to reexec / 3.

rmdir(2): delete a directory

seccomp(2) : restrict system operations

select(2): poll a list of file descriptor for events

setcpid() : Set options for child process of prx control process

setcpid() : Set options for child process of prx control process

setenv(3): set an environment variable

setgid(2): set the GID of the process

setgroups(2): set the supplementary groups of the process

sethostname(2): set the system hostname

setns(2): attach to a namespace

(Linux) setns(2) : attach to a namespace, specifying namespace type

Set port options

setpgid(2): set process group

setpriority(2): set scheduling priority of process, process group or user

setproctitle(3): set the process title

setresgid(2): set real, effective and saved group ID

setresuid(2): set real, effective and saved user ID

setrlimit(2): set a resource limit

setsid(2): create a new session

setuid(2): change UID

Run a command using the shell

sigaction(2): set process behaviour for signals

socket(2): returns a file descriptor for a communication endpoint

Send data to the standard input of the process

Assign a process to receive stdio

Terminate the task

Convenience function to fork a privileged process in the shell.

Convenience function to fork a privileged process in the shell.

Fork a subprocess and run a sequence of operations

Create a subprocess and run a sequence of operations using optional function calls

umount2(2) : unmount a filesystem

umount(2): unmount a filesystem

unlink(2): delete a name from the filesystem

unsetenv(3): remove an environment variable

unshare(2): create a new namespace in the current process

unveil(2): restrict filesystem view

waitpid(2): wait for process to change state

write(2): write to a file descriptor

Types

call/0

-type call() :: alcove_proto:call() | reexec | replace_process_image | getcpid.

constant/0

-type constant() :: atom() | integer().

cpid/0

-type cpid() ::
          #{pid := pid_t(),
            flowcontrol := uint32_t(),
            signaloneof := uint32_t(),
            exec := boolean(),
            fdctl := fd(),
            stdin := fd(),
            stdout := fd(),
            stderr := fd()}.

cstruct/0

-type cstruct() :: [binary() | {ptr, binary() | non_neg_integer()}, ...].

fd/0

-type fd() :: int32_t().

gid_t/0

-type gid_t() :: uint32_t().

int32_t/0

-type int32_t() :: -2147483647..2147483647.

int64_t/0

-type int64_t() :: -9223372036854775807..9223372036854775807.

mode_t/0

-type mode_t() :: uint32_t().

off_t/0

-type off_t() :: uint64_t().

pid_t/0

-type pid_t() :: int32_t().

posix/0

-type posix() :: alcove:posix().

prx_opt/0

-type prx_opt() :: maxchild | exit_status | maxforkdepth | termsig | flowcontrol | signaloneof.

ptr_arg/0

-type ptr_arg() :: binary() | constant() | cstruct().

ptr_val/0

-type ptr_val() :: binary() | integer() | cstruct().

size_t/0

-type size_t() :: uint64_t().

ssize_t/0

-type ssize_t() :: int64_t().

task/0

-type task() :: pid().

uid_t/0

-type uid_t() :: uint32_t().

uint32_t/0

-type uint32_t() :: 0..4294967295.

uint64_t/0

-type uint64_t() :: 0..18446744073709551615.

waitstatus/0

-type waitstatus() :: {exit_status, int32_t()} | {termsig, atom()} | {stopsig, atom()} | continued.

Functions

atexit(Task, Fun)

-spec atexit(task(), fun((pid(), [pid_t()], cpid()) -> any())) -> ok.

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

call(Task, Call, Argv)

-spec call(task(), call(), [any()]) -> any().

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

cap_enter(Task)

-spec cap_enter(task()) -> ok | {error, posix()}.

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

cap_fcntls_get(Task, FD)

-spec cap_fcntls_get(task(), fd()) -> {ok, int32_t()} | {error, posix()}.

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}

cap_fcntls_limit(Task, FD, Rights)

-spec cap_fcntls_limit(task(), fd(), [constant()]) -> ok | {error, posix()}.

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}

cap_getmode(Task)

-spec cap_getmode(task()) -> {ok, 0 | 1} | {error, posix()}.

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}

cap_ioctls_limit(Task, FD, Rights)

-spec cap_ioctls_limit(task(), fd(), [constant()]) -> ok | {error, posix()}.

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}}

cap_rights_limit(Task, FD, Rights)

-spec cap_rights_limit(task(), fd(), [constant()]) -> ok | {error, posix()}.

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}

chdir(Task, Path)

-spec chdir(task(), iodata()) -> ok | {error, posix()}.

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">>}

chmod(Task, Path, Mode)

-spec chmod(task(), iodata(), mode_t()) -> ok | {error, posix()}.

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

chown(Task, Path, Owner, Group)

-spec chown(task(), iodata(), uid_t(), gid_t()) -> ok | {error, posix()}.

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

chroot(Task, Path)

-spec chroot(task(), iodata()) -> ok | {error, posix()}.

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,<<"/">>}

clearenv(Task)

-spec clearenv(task()) -> ok | {error, posix()}.

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).
  []

clone(Task, Flags)

-spec clone(task(), Flags :: [constant()]) -> {ok, task()} | {error, posix()}.

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

close(Task, FD)

-spec close(task(), fd()) -> ok | {error, posix()}.

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

cmd(Task, Cmd)

-spec cmd(task(), [iodata()]) -> binary() | {error, posix()}.

Run and return command output

Run a command synchronously. The command output is accumulated and returned to the caller when the process exits.

The command executable is located from the $PATH.

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.271.0>}
  2> {ok, Child} = prx:fork(Task).
  {ok,<0.276.0>}
  3> prx:cmd(Child, ["/bin/sh", "-c", "echo test"]).
  <<"test\n">>
  4> prx:cmd(Child, ["/bin/echo", "test"]).
  <<"test\n">>
  5> prx:cmd(Child, ["echo", "test"]).
  <<"test\n">>

controlling_process(Task, Pid)

-spec controlling_process(task(), pid()) -> ok | {error, badarg}.

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

cpid(Task)

-spec cpid(task()) -> [cpid()].

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}]

cpid(Task, Pid)

-spec cpid(task(), task() | pid_t()) -> cpid() | error.

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.

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.271.0>}
  2> {ok, Child} = prx:fork(Task).
  {ok,<0.276.0>}
  3> prx:cpid(Task, Child).
  #{pid => 14672,stdout => 11,stderr => 13,exec => false,
    stdin => 10,flowcontrol => 1,signaloneof => 15,fdctl => 8}

See also: cpid/1.

drv(Task)

-spec drv(task()) -> pid().

Get the gen_server PID for the task

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.180.0>}
  <0.181.0>

environ(Task)

-spec environ(task()) -> [binary()].

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">>]

eof(Task)

-spec eof(task()) -> ok | {error, posix()}.

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 owned by 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

eof(Task, Stdio)

-spec eof(task(), task() | pid_t() | stdin | stdout | stderr) -> ok | {error, posix()}.

Close task standard I/O file descriptor

The behaviour of eof/2 depends on the second argument which may be either a process (task(), pid_t()) or an atom (stdin, stdout, stderr).

* process: request the parent of the specified task close the task stdin, stdout or stderr

  eof(task(), stdin | stdout | stderr)

* atom: request a task close stdin of the child process specified in the second argument

  eof(task(), task() | pid_t())

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

eof(Task, Pid, Stdio)

-spec eof(task(), task() | pid_t(), stdin | stdout | stderr) -> ok | {error, posix()}.

Close stdin, stdout or stderr of child process.

See also: eof/2.

execed(Task)

-spec execed(task()) -> boolean().

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

execve(Task, Argv, Env)

-spec execve(task(), [iodata()], [iodata()]) -> ok | {error, posix()}.

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.286.0>}
  2> {ok, Child} = prx:fork(Task).
  {ok,<0.291.0>}
  3> prx:execve(Child, ["/usr/bin/env"],  ["TEST=123"]).
  ok
  4> flush().
  Shell got {stdout,<0.291.0>,<<"TEST=123\n">>}
  Shell got {exit_status,<0.291.0>,0}
  ok

execve(Task, Arg0, Argv, Env)

-spec execve(task(), iodata(), [iodata()], [iodata()]) -> ok | {error, posix()}.

execve(2): replace process image with environment and set process name

Replace the process image, specifying the environment for the new process image and the process name.

Examples

Set the command name in the process list:

  1> {ok, Task} = prx:fork().
  {ok,<0.286.0>}
  2> {ok, Child} = prx:fork(Task).
  {ok,<0.309.0>}
  3> prx:execve(Child, "/usr/bin/env", ["process-name", "-0"],  ["TEST=123"]).
  ok
  4> flush().
  Shell got {stdout,<0.309.0>,<<84,69,83,84,61,49,50,51,0>>}
  Shell got {exit_status,<0.309.0>,0}
  ok

execvp(Task, Argv)

-spec execvp(task(), [iodata()]) -> ok | {error, posix()}.

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

execvp(Task, Arg0, Argv)

-spec execvp(task(), iodata(), [iodata()]) -> ok | {error, posix()}.

execvp(2): replace the current process image using the search path and set process name

Examples

Set the command name in the process list:

  1> {ok, Task} = prx:fork().
  {ok,<0.180.0>}
  2> prx:execvp(Task, "cat", ["process-name", "-n"])
  ok
  3> prx:stdin(Task1, <<"test\n">>).
  ok
  4> flush().
  Shell got {stdout,<0.180.0>,<<"     1\ttest\n">>}
  ok

exit(Task, Status)

-spec exit(task(), int32_t()) -> ok.

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

fcntl(Task, FD, Cmd)

-spec fcntl(task(), fd(), constant()) -> {ok, int64_t()} | {error, posix()}.

fcntl(2) : perform operation on a file descriptor

  1> {ok, Task} = prx:fork().
  {ok,<0.178.0>}
  2> Stdin = 0.
  0
  3> prx:fcntl(Task, Stdin, f_getfd).
  {ok,0}

See also: fcntl/4.

fcntl(Task, FD, Cmd, Arg)

-spec fcntl(task(), fd(), constant(), int64_t()) -> {ok, int64_t()} | {error, posix()}.

fcntl(2): perform operations on a file descriptor with argument

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.749.0>}
  2> FD_CLOEXEC = prx:call(Task, fcntl_constant, ['FD_CLOEXEC']).
  1
  3> prx:fcntl(Task, 1, f_setfd, FD_CLOEXEC).
  {ok,0}
  4> prx:fcntl(Task, 1, f_getfd).
  {ok,1}

fexecve(Task, FD, Argv, Env)

-spec fexecve(task(), int32_t(), [iodata()], [iodata()]) -> ok | {error, posix()}.

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

filter(Task, Calls)

-spec filter(task(), [call()] | {allow, [call()]} | {deny, [call()]}) -> ok.

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 also: filter/3.

filter(Task, Calls, SubprocessCalls)

-spec filter(task(),
             [call()] | {allow, [call()]} | {deny, [call()]},
             [call()] | {allow, [call()]} | {deny, [call()]}) ->
                ok.

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,<<"/">>}

fork()

-spec fork() -> {ok, task()} | {error, posix()}.

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>}

fork(Task)

-spec fork(task()) -> {ok, task()} | {error, posix()}.

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}]

getcpid(Task, Opt)

-spec getcpid(task(), atom()) -> int32_t() | false.

Get control process attributes

Retrieve attributes set by the prx control process for a child process.

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.271.0>}
  2> {ok, Child} = prx:fork(Task).
  {ok,<0.276.0>}
  3> prx:getcpid(Child, flowcontrol).
  -1
  4> prx:getcpid(Child, signaloneof).
  15

See also: getcpid/3.

getcpid(Task, Pid, Opt)

-spec getcpid(task(), task() | cpid() | pid_t(), atom()) -> int32_t() | false.

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

getcwd(Task)

-spec getcwd(task()) -> {ok, binary()} | {error, posix()}.

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,<<"/">>}

getenv(Task, Name)

-spec getenv(task(), iodata()) -> binary() | false.

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">>

getgid(Task)

-spec getgid(task()) -> gid_t().

getgid(2): retrieve the process group ID

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.179.0>}
  2> prx:getgid(Task).
  1000

getgroups(Task)

-spec getgroups(task()) -> {ok, [gid_t()]} | {error, posix()}.

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]}

gethostname(Task)

-spec gethostname(task()) -> {ok, binary()} | {error, posix()}.

gethostname(2): retrieve the system hostname

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.179.0>}
  2> prx:gethostname(Task).
  {ok,<<"host1">>}

getopt(Task, Opt)

-spec getopt(task(), prx_opt()) -> false | int32_t().

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

getpgrp(Task)

-spec getpgrp(task()) -> pid_t().

getpgrp(2): retrieve the process group

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.179.0>}
  2> prx:getpgrp(Task).
  3924

getpid(Task)

-spec getpid(task()) -> pid_t().

getpid(2): retrieve the system PID of the process

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.179.0>}
  2> prx:getpid(Task).
  3924

getpriority(Task, Which, Who)

-spec getpriority(task(), constant(), int32_t()) -> {ok, int32_t()} | {error, posix()}.

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}

getresgid(Task)

-spec getresgid(task()) -> {ok, gid_t(), gid_t(), gid_t()} | {error, posix()}.

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}

getresuid(Task)

-spec getresuid(task()) -> {ok, uid_t(), uid_t(), uid_t()} | {error, posix()}.

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}

getrlimit(Task, Resource)

-spec getrlimit(task(), constant()) -> {ok, #{cur => uint64_t(), max => uint64_t()}} | {error, posix()}.

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}}

getsid(Task, OSPid)

-spec getsid(task(), pid_t()) -> {ok, pid_t()} | {error, posix()}.

getsid(2): retrieve the session ID

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.179.0>}
  2> prx:getsid(Task).
  {ok,3924}

getuid(Task)

-spec getuid(task()) -> uid_t().

getuid(2): returns the process user ID

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.179.0>}
  2> prx:getuid(Task).
  1000

ioctl(Task, FD, Request, Argp)

-spec ioctl(task(), fd(), constant(), cstruct()) ->
               {ok, #{return_value := integer(), arg := iodata()}} | {error, posix()}.

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>>}.

jail(Task, Jail)

-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(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">>}

kill(Task, OSPid, Signal)

-spec kill(task(), pid_t(), constant()) -> ok | {error, posix()}.

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

lseek(Task, FD, Offset, Whence)

-spec lseek(task(), fd(), off_t(), int32_t()) -> ok | {error, posix()}.

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

mkdir(Task, Path, Mode)

-spec mkdir(task(), iodata(), mode_t()) -> ok | {error, posix()}.

mkdir(2) : create a directory

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.252.0>}
  2> prx:mkdir(Task, "/tmp/prx-mkdir-test", 8#700).
  ok

mkfifo(Task, Path, Mode)

-spec mkfifo(task(), iodata(), mode_t()) -> ok | {error, posix()}.

mkfifo(3) : create a named pipe

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.252.0>}
  2> prx:mkfifo(Task, "/tmp/prx-fifo-test", 8#700).
  ok

mount(Task, Source, Target, FSType, Flags, Data)

-spec mount(task(), iodata(), iodata(), iodata(), uint64_t() | [constant()], iodata()) ->
               ok | {error, posix()}.

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

mount(Task, Source, Target, FSType, Flags, Data, Options)

-spec mount(task(), iodata(), iodata(), iodata(), uint64_t() | [constant()], iodata(), iodata()) ->
               ok | {error, posix()}.

(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 also: mount/6.

open(Task, Path, Flags)

-spec open(task(), iodata(), int32_t() | [constant()]) -> {ok, fd()} | {error, posix()}.

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}

open(Task, Path, Flags, Mode)

-spec open(task(), iodata(), int32_t() | [constant()], mode_t()) -> {ok, fd()} | {error, posix()}.

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}

parent(Task)

-spec parent(task()) -> task() | noproc.

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>

pidof(Task)

-spec pidof(task()) -> pid_t() | noproc.

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

pipeline(Task)

-spec pipeline(task()) -> [pid_t()].

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]

pivot_root(Task, NewRoot, PutOld)

-spec pivot_root(task(), iodata(), iodata()) -> ok | {error, posix()}.

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

pledge(Task, Promises, ExecPromises)

-spec pledge(task(), iodata(), iodata()) -> ok | {error, posix()}.

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

prctl(Task, Arg1, Arg2, Arg3, Arg4, Arg5)

-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(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 also: seccomp/4.

procctl(Task, IDType, ID, Cmd, Data)

-spec procctl(task(), constant(), pid_t(), constant(), [] | cstruct()) ->
                 {ok, binary(), cstruct()} | {error, posix()}.

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
  ]).

ptrace(Task, Request, OSPid, Addr, Data)

-spec ptrace(task(), constant(), pid_t(), ptr_arg(), ptr_arg()) ->
                {ok, integer(), ptr_val(), ptr_val()} | {error, posix()}.

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, []).

read(Task, FD, Count)

-spec read(task(), fd(), size_t()) -> {ok, binary()} | {error, posix()}.

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">>}

readdir(Task, Path)

-spec readdir(task(), iodata()) -> {ok, [binary()]} | {error, posix()}.

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">>,<<...>>|...]}

reexec(Task)

-spec reexec(task()) -> ok | {error, posix()}.

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

reexec(Task, Argv, Env)

-spec reexec(task(), {fd, int32_t(), [string() | [string()]]} | [string() | [string()]], iodata()) ->
                ok | {error, posix()}.

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 also: reexec/1.

replace_process_image(Task)

-spec replace_process_image(task()) -> ok | {error, posix()}.

Equivalent to reexec / 1.

replace_process_image(Task, Argv, Env)

-spec replace_process_image(task(),
                            {fd, int32_t(), [string() | [string()]]} | [string() | [string()]],
                            iodata()) ->
                               ok | {error, posix()}.

Equivalent to reexec / 3.

rmdir(Task, Path)

-spec rmdir(task(), iodata()) -> ok | {error, posix()}.

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

seccomp(Task, Operation, Flags, Prog)

-spec seccomp(task(), constant(), constant(), cstruct()) -> ok | {error, posix()}.

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 also: prctl/6.

select(Task, Readfds, Writefds, Exceptfds, Timeval)

-spec select(task(),
             Readfds :: [fd()],
             Writefds :: [fd()],
             Exceptfds :: [fd()],
             Timeval :: [] | #{sec => int64_t(), usec => int64_t()}) ->
                {ok, [fd()], [fd()], [fd()]} | {error, posix()}.

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],[]}

setcpid(Task, Opt, Val)

-spec setcpid(task(), atom(), int32_t()) -> boolean().

setcpid() : Set options for child process of prx control process

Control behaviour of an exec()'ed process.

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.271.0>}
  2> {ok, Child} = prx:fork(Task).
  {ok,<0.276.0>}
  3> prx:getcpid(Child, flowcontrol).
  -1
  4> prx:setcpid(Child, flowcontrol, 1).
  true
  5> prx:getcpid(Child, flowcontrol).
  1

See also: setcpid/4.

setcpid(Task, Pid, Opt, Val)

-spec setcpid(task(), task() | cpid() | pid_t(), atom(), int32_t()) -> boolean().

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

setenv(Task, Name, Value, Overwrite)

-spec setenv(task(), iodata(), iodata(), int32_t()) -> ok | {error, posix()}.

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">>

setgid(Task, Gid)

-spec setgid(task(), gid_t()) -> ok | {error, posix()}.

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

setgroups(Task, Groups)

-spec setgroups(task(), [gid_t()]) -> ok | {error, posix()}.

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,[]}

sethostname(Task, Hostname)

-spec sethostname(task(), iodata()) -> ok | {error, posix()}.

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.

setns(Task, FD)

-spec setns(task(), fd()) -> ok | {error, posix()}.

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).

setns(Task, FD, NSType)

-spec setns(task(), fd(), constant()) -> ok | {error, posix()}.

(Linux) setns(2) : attach to a namespace, specifying namespace type

Examples

  ok = prx:setns(Task, FD, clone_newnet)

See also: setns/2.

setopt(Task, Opt, Val)

-spec setopt(task(), prx_opt(), int32_t()) -> boolean().

Set port options

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.160.0>}
  2> prx:setopt(Task, maxforkdepth, 128).
  true

See also: getopt/2.

setpgid(Task, OSPid, Pgid)

-spec setpgid(task(), pid_t(), pid_t()) -> ok | {error, posix()}.

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

setpriority(Task, Which, Who, Prio)

-spec setpriority(task(), constant(), int32_t(), int32_t()) -> ok | {error, posix()}.

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}

setproctitle(Task, Name)

-spec setproctitle(task(), iodata()) -> ok.

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 also: prctl/6.

setresgid(Task, Real, Effective, Saved)

-spec setresgid(task(), gid_t(), gid_t(), gid_t()) -> ok | {error, posix()}.

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

setresuid(Task, Real, Effective, Saved)

-spec setresuid(task(), uid_t(), uid_t(), uid_t()) -> ok | {error, posix()}.

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

setrlimit(Task, Resource, Limit)

-spec setrlimit(task(), constant(), #{cur => uint64_t(), max => uint64_t()}) -> ok | {error, posix()}.

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}}

setsid(Task)

-spec setsid(task()) -> {ok, pid_t()} | {error, posix()}.

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}

setuid(Task, User)

-spec setuid(task(), uid_t()) -> ok | {error, posix()}.

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 also: setresuid/4.

sh(Task, Cmd)

-spec sh(task(), iodata()) -> binary() | {error, posix()}.

Run a command using the shell

Run a command synchronously using /bin/sh. The shell output is accumulated and returned to the caller when the shell process exits.

Examples

  1> {ok, Task} = prx:fork().
  {ok,<0.271.0>}
  2> {ok, Child} = prx:fork(Task).
  {ok,<0.276.0>}
  3> prx:sh(Child, "echo test").
  <<"test\n">>
  4> prx:sh(Child, "echo another test run").
  <<"another test run\n">>

sigaction(Task, Signal, Handler)

-spec sigaction(task(),
                constant(),
                atom() | {sig_info, fun((pid(), [pid_t()], atom(), binary()) -> any())}) ->
                   {ok, atom()} | {error, posix()}.

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

socket(Task, Domain, Type, Protocol)

-spec socket(task(), constant(), constant(), int32_t()) -> {ok, fd()} | {error, posix()}.

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}

stdin(Task, Buf)

-spec stdin(task(), iodata()) -> ok.

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

stdio(Task, Pid)

-spec stdio(task(), pid()) -> ok | {error, badarg}.

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.

Examples

  ok = prx:stdio(Owner, NewOwner),
  ok = prx:execvp(Owner, Argv),
  ok = prx:controlling_process(Owner, NewOwner).

stop(Task)

-spec stop(task()) -> ok.

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).
  []

sudo()

-spec sudo() -> ok.

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

sudo(Exec)

-spec sudo(iodata()) -> ok.

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

task(Task, Ops, State)

-spec task(task(), Ops :: [prx_task:op() | [prx_task:op()]], State :: any()) ->
              {ok, task()} | {error, posix()}.

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 also: task/4.

task(Task, Ops, State, Config)

-spec task(task(),
           Ops :: [prx_task:op() | [prx_task:op()]],
           State :: any(),
           Config :: [prx_task:config()]) ->
              {ok, task()} | {error, posix()}.

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 also: prx_task.

umount2(Task, Path, Flags)

-spec umount2(task(), iodata(), [constant()]) -> ok | {error, posix()}.

umount2(2) : unmount a filesystem

On BSD systems, calls unmount(2).

Examples

See also: pivot_root/3.

umount(Task, Path)

-spec umount(task(), iodata()) -> ok | {error, posix()}.

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

unlink(Task, Path)

-spec unlink(task(), iodata()) -> ok | {error, posix()}.

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

unsetenv(Task, Name)

-spec unsetenv(task(), iodata()) -> ok | {error, posix()}.

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

unshare(Task, Flags)

-spec unshare(task(), int32_t() | [constant()]) -> ok | {error, posix()}.

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">>}

unveil(Task, Path, Permissions)

-spec unveil(task(), iodata(), iodata()) -> ok | {error, posix()}.

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}

waitpid(Task, OSPid, Options)

-spec waitpid(task(), pid_t(), int32_t() | [constant()]) ->
                 {ok, pid_t(), int32_t(), [waitstatus()]} | {error, posix()}.

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}]}

write(Task, FD, Buf)

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()}.