532 Commits

Author SHA1 Message Date
Michael Santos
c8a4aabf90 alcove_drv: add start_link/0,1
Export start_link/0,1. start/0,1 is no longer an alias for start_link
and is mainly useful for testing.
2014-12-20 16:49:13 -05:00
Michael Santos
70bd0fa5d9 stream test: make the sleep timeout configurable
Use the environment ALCOVE_TEST_STREAM_MAGIC_SLEEP to control the length
of time the shell process will wait before exiting. The default is 0
(no wait).

The underlying issue is that a parent process is exiting before relaying
all the stdout from the child shell process. While this problem needs
to be fixed, it is interrupting the stress tests which are turning up
some new, interesting failures.
2014-12-19 16:11:01 -05:00
Michael Santos
e0d56586f6 alcove_codec: add type specs 2014-12-17 15:53:22 -05:00
Michael Santos
a9214ac8b6 Avoid compile failures on Smartos with R16B02 2014-12-16 18:26:13 -05:00
Michael Santos
3299d33d73 call: catch improperly typed arguments
Force arguments of the wrong type to crash the process rather then
crashing the gen_server.
2014-12-15 17:01:10 -05:00
Michael Santos
be275290ed tests: increase the magic sleep in stream test
Further obscure infrequent test failures on FreeBSD.
2014-12-14 15:02:24 -05:00
Michael Santos
4efa979037 alcove_proto:returns/1 -> will_return/1 2014-12-13 16:28:34 -05:00
Michael Santos
6e068c971d setpriority test: Linux fix when RLIMIT_NICE set
Linux has a resource limit for scheduling priority. If the value is 0,
processes that have increased their nice value (i.e., set it to a
positive integer between 0 and 20) may not decrease it. If the value is
20, the user may decrease the process priority back to 0.

Uniformly test for this resource limit across all OS'es (even though
only linux seems to support it) and set it down to 0 for the test.
2014-12-12 16:54:25 -05:00
Michael Santos
34b1760e87 Rename signal actions to reflect the POSIX naming
Use the same names for signal actions as the C headers. Since the
actions are atoms, the names are lower cased.

Invent the name 'sig_catch' to denote signals that are caught and passed
to the erlang side as messages.

Add the 'sig_' prefix to all the actions to avoid quoting 'catch', since it
is a reserved keyword.
2014-12-11 18:11:14 -05:00
Michael Santos
27c59e5374 tests: ignore SIGPIPE
Forked/exec'ed executables will sometimes get a sigpipe causing the
tests to fail. On OpenBSD, the "yes" command uses SIGPIPE to cause yes
to exit, so re-enable for that test.
2014-12-10 17:53:34 -05:00
Michael Santos
38c61062f6 test: prevent the stream test from exiting early
There is a race condition in the stream test. Occasionally, when the
shell process exits, a process higher up in the chain is killed before
it has completely relayed the data.

Work around the test failure for now by adding a delay.
2014-12-09 17:21:17 -05:00
Michael Santos
71117bcefd Multiplex erlang process access to the port
Allow any erlang processes to send messages into the port, similar
to the way port drivers can respond directly to a process. The Unix PID
fork path is used as the key to map the port response to the Erlang PID
by the gen_server.

The current implementation is an experiment only and has a race
condition. Process A calls into the gen_server and blocks. Process B calls
into the port for the same Unix fork path and blocks. The response for
both requests will be sent to process B and process A will block forever.

In other words, the last erlang process to make a request to the fork
path becomes the controlling process. If the erlang process dies, any
data generated by the Unix process is sent to the process that started
the gen_server.

What the behaviour should be needs to be defined. For example, using the
gen_tcp behaviour of controlling_process would be problematic: since the
response always goes to the controlling process, a call from another
erlang process would hang. After the Unix process has called exec(),
allowing multiple processes to send in data may make sense.

Making the controlling process the only process privileged to talk to
the fork path would have some weird side effects:

* data for the fork path would have to be serialized through the
  controlling process

* if a non-owning process sends a message to the fork path, either we
  have to extend the type spec for each call to include
  {error,not_owner} or we send a badsig exception and the client is
  forced to wrap each call in a try/catch

There should also be a concept of linking between unix and erlang
processes:

* processes are unlinked

    * erlang process dies: stdout/stderr from the unix process should
      be dropped

    * unix process dies: controlling process gets the normal exit
      messages (exit_status, termsig)

* processes are linked

    * erlang process dies: unix process gets a SIGKILL

    * unix process dies: erlang process gets an exit(kill)

* erlang process monitors unix process (?)

    * unix process dies: erlang process gets a 'DOWN' message

To test the concept works, modify the tcplxc example to talk directly to
the port from each erlang container process (the example needs much more
cleanup and should be converted to an OTP process).
2014-12-08 11:56:45 -05:00
Michael Santos
afea3b9d69 Bump version 2014-12-07 11:09:49 -05:00
Michael Santos
888343e9dd Stream data from the alcove port
Handle framing in erlang code rather than relying on the {packet,2}
option to open_port/2. This change simplifies the port protocol codec
and should eventually allow easily calling exec() in the port.
2014-12-07 08:27:54 -05:00
Michael Santos
ab0526b902 Add tests for setpriority/getpriority 2014-12-06 11:09:05 -05:00
Michael Santos
eed7976d90 Type specs for setpriority/getpriority 2014-12-05 17:17:20 -05:00
Michael Santos
a2f812a9b6 openbsd: fix poll(2) with setrlmit(RLIMIT_NOFILE)
Like Linux and Solaris, poll(2) on OpenBSD will return EINVAL if the
number of file descriptors argument passed to poll is above the maximum
number of fd's allowed by setrlimit(2).

By inspection, NetBSD behaves in a similar way to FreeBSD (nfd is
allowed to exceed maximum fd).
2014-12-04 10:37:42 -05:00
Michael Santos
95eb13e210 Catch exit status event in call
An alcove system process may exit unexpectedly during a call, for
example, if poll(2) exits due to an error. In this situation, the caller
will hang in receive.
2014-12-03 10:46:09 -05:00
Michael Santos
bab762ef2e Move protocol encoding into a separate modules 2014-12-02 07:40:46 -05:00
Michael Santos
af4a17e5f1 Fix compilation on non-Linux systems 2014-12-01 11:54:21 -05:00
Michael Santos
b5f8fd36ff Add setpriority(2)/getpriority(2)
setpriority(2) is useful for limiting the impact of processes running in
a sandbox. It is also possible to exec the sandbox process using nice:

    alcove:execvp(Drv, Path, "/usr/bin/nice", ["/usr/bin/nice", "-n",
            "19", "sandbox"]).

The advantage of supporting set/getpriority natively in alcove are:

* nice doesn't need to exist in the sandbox chroot
* better error values (nice will return errors as a binary string)
* priorities can apply to fork trees
2014-12-01 11:20:48 -05:00
Michael Santos
a6e0c5de49 unsupported calls: use undef function exception
Returning a "plain" error tuple for unsupported calls breaks the type
spec for functions:

    On BSD:
        -spec setproctitle(alcove_drv:ref(),iodata()) -> 'ok'.

    On Linux and Solaris:
        -spec setproctitle(alcove_drv:ref(),iodata()) -> {'error','unsupported'}.

So the type spec would need to be amended to be the union of both return
values and portable code would have to test for both cases, with the
effect that any call in the future might return 'unsupported' if alcove
were ported to a new OS.

Attempting to make an supported call will now result in an "undefined
function" exception:

    1> catch alcove:setproctitle(P, "foo").
    {'EXIT',{undef,[{alcove,call,
                            [<0.46.0>,setproctitle,["foo"]],
                            [{file,"src/alcove.erl"},{line,426}]},
                    {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,661}]},
                    {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,434}]},
                    {shell,exprs,7,[{file,"shell.erl"},{line,684}]},
                    {shell,eval_exprs,7,[{file,"shell.erl"},{line,639}]},
                    {shell,eval_loop,3,[{file,"shell.erl"},{line,624}]}]}}
    2> os:type().
    {unix,linux}

This still places a burden on the caller. Portable code either needs to
hardcode supported functions by OS:

    % And imagine all the error checking this code is not doing ...
    proctitle(Name) ->
        case os:type() of
            {unix,linux} -> alcove:prctl(...);
            {unix,BSD} where BSD = ... -> alcove:proctitle(...)

Or run through sequences of awkward try/catch statements:

    proctitle(Name) ->
        try alcove:proctitle(...) of
            ok -> ok
        catch
            error:undef ->
                try alcove:prctl(...) of
                    {ok,_,_,_,_,_} -> ok
                catch
                    error:undef ->
                        % Does Solaris even?
                        ...
                end,
        end

Dealing with portability should be the job of a higher level library
built on top of alcove.
2014-11-30 09:35:06 -05:00
Michael Santos
8e1074fbd6 linux/solaris: fail if RLIMIT_NOFILE is less than openfd
The interaction between poll(2) and setrlimit(RLIMIT_NOFILE) differs
between BSD and Linux/Solaris.

On BSD systems, if RLIMIT_NOFILE is set to 0, poll(2) will continue to
work with open file descriptors.

On Linux/Solaris, poll(2) requires the minimum value of RLIMIT_NOFILE
to be equal to the max open file descriptor+1. If the nfd parameter to poll(2)
exceeds the current soft limit, poll(2) returns EINVAL, causing the
alcove process to exit.

Setting RLIMIT_NOFILE to 0 is useful for creating rlimit sandboxes. The
caller allocates whatever resources are required, drops privileges, then
sets limits on resources. For example, to prevent file creation, forking
of new processes and opening file descriptors, the following works on
BSD:

    ok = alcove:setrlimit(Drv, [Child], rlimit_fsize,
            #alcove_rlimit{cur = 0, max = 0}),
    ok = alcove:setrlimit(Drv, [Child], rlimit_nproc,
            #alcove_rlimit{cur = 0, max = 0}),
    ok = alcove:setrlimit(Drv, [Child], rlimit_nofile,
            #alcove_rlimit{cur = 0, max = 0}).

If an open file descriptor is closed, it cannot be re-opened:

    1> alcove:open(P, [F], "/etc/passwd", [o_rdonly], 0).
    {ok,6}
    2> alcove:setrlimit(P, [F], rlimit_nofile, #alcove_rlimit{cur = 0, max = 0}).
    ok
    3> alcove:read(P, [F], 6, 10).
    {ok,<<"# $FreeBSD">>}
    4> alcove:close(P, [F], 6).
    ok
    5> alcove:open(P, [F], "/etc/passwd", [o_rdonly], 0).
    {error,emfile}

On a linux or solaris system, RLIMIT_NOFILE would have to be set to 7.

The array holding the file descriptors will grow if RLIMIT_NOFILE is
increased but does not shrink (processes/file descriptors may exist and
would be leaked). Since BSD systems can poll any open file descriptor,
pass the whole array into poll(2). For linux/solaris, check the highest
currently opened fd is below RLIMIT_NOFILE and use the value of
RLIMIT_NOFILE as the number of file descriptors argument.
2014-11-29 15:06:40 -05:00
Michael Santos
cf134922b4 Allow passing options to dialzyer
On ARM, dialyzer crashes when compiling native code. Add support for
dialyzer flags via an environment variable to work around this:

    DIALYZER_FLAGS=--no_native make dialyzer
2014-11-28 16:44:41 -05:00
Michael Santos
97f6e6187d test: fix race condition on exit
Since exit_status is now enabled by default, a test would occasionally
fail depending on how quickly the message was received from the port.
Fix by waiting for the exit_status event to be received.
2014-11-27 15:50:30 -05:00
Michael Santos
965df0c21f tests: clean up integer conversions 2014-11-26 19:28:16 -05:00
Michael Santos
9a0b6821c8 Notify of exit status by default
Processes have 2 ways of notifying the parent of exit: termination
signal and exit status. Since termination signals are enabled by
default, enable exit status for consistency. Exit status can be disabled
by using:

    alcove:setopt(Drv, ForkPath, exit_status, 0)
2014-11-22 15:56:58 -05:00
Michael Santos
1378d0f036 Use the default system compiler
Do not hardcode the compiler in the tests to gcc. FreeBSD uses clang
(although FreeBSD does not support Linux style namespaces anyway).
2014-11-21 11:31:31 -05:00
Michael Santos
667b3bc02c Support cross-compiling the port 2014-11-20 17:51:37 -05:00
Michael Santos
73f1fc4ec5 Type specs for pivot_root 2014-11-19 16:56:23 -05:00
Michael Santos
7834bfcc7f Fix typespec for setproctitle 2014-11-18 18:02:07 -05:00
Michael Santos
8ab1b8908b Add pivot_root(2) 2014-11-15 10:17:46 -05:00
Michael Santos
f55507d906 Add types for setpgid/3,4 2014-11-13 15:43:22 -05:00
Michael Santos
5f1aac3eff Add MIN() macro 2014-11-11 13:17:26 -05:00
Michael Santos
1af5057213 Crash on poll(2) failures 2014-11-10 10:28:07 -05:00
Michael Santos
edcdea530c Namespace functions callable from erlang 2014-11-09 08:33:45 -05:00
Michael Santos
8dd0c78869 Add doc for setres([ug])id/getres([ug])id 2014-11-09 08:33:45 -05:00
Michael Santos
0da2981e48 Add types for setresgid/getresgid 2014-11-08 10:47:03 -05:00
Michael Santos
9500eb0169 Add getresgid(2) 2014-11-07 16:31:41 -05:00
Michael Santos
9cc0bf74f4 Add setresgid(2) 2014-11-06 15:47:26 -05:00
Michael Santos
6c218b8bb4 Add specs for setresuid/getresuid 2014-11-05 11:38:39 -05:00
Michael Santos
2426063ac7 Add getresuid(2) 2014-11-04 13:47:40 -05:00
Michael Santos
33285e1dd8 Fix initialization warnings on OpenBSD 2014-11-03 11:35:43 -05:00
Michael Santos
d608a18a4b Add setresuid/4,5
alcove is usually called from sudo which sets uid, euid and suid
appropriately. However, if run with the setuid bit, the behaviour might
be more mysterious as shown in "Setuid Demystified":

http://www.usenix.org/events/sec02/full_papers/chen/chen.pdf

setresuid(2) is a more predictable interface. But of course it is not
supported by Solaris.
2014-11-02 09:27:08 -05:00
Michael Santos
b50a592858 Add support for notifications when stdin is closed 2014-11-01 16:29:08 -04:00
Michael Santos
c010cf65d4 Term must begin at start of buffer
Initialize the index passed into the macro to 0. These macros should be
removed in the future.
2014-11-01 16:29:08 -04:00
Michael Santos
79ad18051c Rename fd constants to match stdio naming 2014-10-31 15:12:27 -04:00
Michael Santos
96aae9e59c Use the errno for the exit value
Alcove will exit on fatal errors like resource allocation failures. Use
the value of errno as the exit value (which will be sent as a message if
the exit_status option is used).

The reason for the failure is sent to stderr using the err macros and so
will be in format:

    <<"alcove: ", Reason/binary>>
2014-10-30 15:18:10 -04:00
Michael Santos
594f671803 tests: use last PID for execvp chain test
On a single CPU linux test system, the last process in the fork chain may
not have exited when an process earlier in the chain has returned
{error,esrch} from a kill signal. Test the entire fork chain has exited
by waiting for the last element in the chain.
2014-10-29 12:06:03 -04:00
Michael Santos
8c65b19f17 tests: improve the exec in chain test
Instead of sleeping with a magic constant, poll the child of the child
of the process that called exec. Since the process midway in the chain
called exec, we won't be sent termination messages from the child procs.

Interestingly, on OpenBSD, the child of the process calling exec is
reliably terminated. On Linux, FreeBSD, NetBSD and Solaris, the child
process becomes a zombie and consequently, sending a signal of 0 returns
ok. Presumably this occurs because the parent did not call waitpid, but
it could point to another bug somewhere.
2014-10-29 11:39:02 -04:00