Commit Graph

500 Commits

Author SHA1 Message Date
Michael Santos
febe77b585 cred: split out calls into separate files
Begin code re-organization by moving functions to one call per file.
2015-07-03 10:40:41 -04:00
Michael Santos
672af8a39f Port to rebar3
Support building with rebar3 and rebar2 using the makefile generated
from "rebar3 new cmake".

Do a straightforward port of the compiler options from the rebar port
compiler. The optimizations/warnings can be re-enabled later.

Use the fancy pants incremental building of each compilation unit from
the template. It's a lot slower than compiling in one go but is slightly
faster when running any rebar command (rebar runs make on each command
which recreates the target. The Makefile needs to be fixed).
2015-07-02 10:23:08 -04:00
Michael Santos
3127db7223 alcove_drv:start/1: remove maxforkdepth
maxforkdepth can be set at runtime using alcove:setopt/3,4.
2015-06-27 13:17:18 -04:00
Michael Santos
f060d44937 errno_id/2,3: convert errno integer to atom 2015-06-16 16:54:13 -04:00
Michael Santos
c6d1b9c9b5 alcove_drv: use exported fork_path() type 2015-05-28 11:03:05 -04:00
Michael Santos
c2a28e6c3c Update type specs
Improving the readability of the types by making them more specific.
2015-04-25 10:05:01 -04:00
Michael Santos
d81dcb688a Fix compilation errors on Solaris
Use POSIX 200112L/C99 mode on Solaris to suppress compilation warnings.

"-D__EXTENSIONS__=1" is required for NSIG (max number of signals). This
also pulls in an ERR macro which necessitates renaming the goto label
from ERR -> ERROR.
2015-02-21 09:21:48 -05:00
Michael Santos
3b15781433 Use a new message type for stdio events
Make a new message type (alcove_ctl) for informing the erlang process
when a stdio descriptor has been closed.
2015-01-01 17:09:48 -05:00
Michael Santos
4ec936d5d7 Add synch/async sends into port
Messages sent to stdin are asynchronous since a response can be sent
back at any time.
2014-12-29 10:26:38 -05:00
Michael Santos
804316629d Crash if the port exits
If the alcove port exits, cause the gen_server to crash.

The reason for crashing:

1. The type specs for all calls was implictly extended to include
   {error,closed} on port failure.

2. There is no error recovery the calling process could do (except
   cleaning up state). The caller can still catch errors, just like when
   dealing with a normal port.

3. Asynchronous messages into the port (stdin) had to be handled as calls,
   rather than casts.
2014-12-29 10:26:38 -05:00
Michael Santos
a664397868 Track ownership of the port in the dict 2014-12-29 10:26:38 -05:00
Michael Santos
5ea775fced badpid: event -> call
Although badpid may be returned by any process in the the fork chain,
it is a synchronous response to a call like badarg and undef.
2014-12-28 10:38:38 -05:00
Michael Santos
63ae5b1b37 Remove test to make dialyzer happy 2014-12-28 10:23:47 -05:00
Michael Santos
2ba7caaf76 Exit with badpid for invalid OS PID in fork path
A message sent to a non-existent Unix PID would be discarded, causing
the calling erlang process to block forever in receive.

This change modifies the behaviour of the port to return "badpid" if an
element in the fork path is not found. Since any element of the path may
be invalid, an event (as opposed to a call reply) is generated.
Currently any "badpid" event will cause the erlang process to exit.
Since only one call can be active at a time, it shouldn't interfere with
other operations but the mailbox should pattern match on the list
prefix (i.e., a call to [1,2,3,-1,4] would generate badpid for
[1,2,3,-1]).

Also define the behaviour when writing to invalid PIDs (<= 0). Since 0
is used to initialize the list of child PIDs in the port, passing in a
PID of 0 would match the first available slot in the PID table, causing
the port to write to fd 0.
2014-12-27 15:42:10 -05:00
Michael Santos
0f9c27d5d4 prctl: update types and docs 2014-12-24 11:25:11 -05:00
Michael Santos
97df96a7ba alcove_drv: consistently enforce positive PIDs
The port will accept negative PIDs. A PID is usually defined as a signed
32-bit integer.

For fork paths, values will always be positive (as returned by fork;
fork errors will return a tuple rather than -1). If the user passes a
negative number as a component of the fork path, the message will be
discared.
2014-12-23 09:50:34 -05:00
Michael Santos
ce8d642791 Enforce precedence in macros 2014-12-22 07:31:17 -05:00
Michael Santos
17c7277a1a Include all args to call in the stack trace 2014-12-21 15:16:53 -05:00
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