120 Commits

Author SHA1 Message Date
Michael Santos
d5e80fac33 setns/4: supporting setting namespace 2015-07-24 10:52:06 -04:00
Michael Santos
b85c5d2fdf Add optional timeout
Experiment with adding an optional timeout to each call. The default is
still infinity.
2015-07-23 09:39:57 -04:00
Michael Santos
d755ada152 Remove function versions with optional fork path
Enforce the use of the fork path. Having an optional fork path was nice
when working in the shell:

    {ok, Child} = alcove:fork(Drv).

Instead of:

    {ok, Child} = alcove:fork(Drv, []). % port process forks

However it introduced a few problems:

* made the interface inconsistent and ambiguous

    alcove:kill(Drv, Pid, 9)
    % vs
    alcove:kill(Drv, [], Pid, 9) % the port process is sending the signal

* calls could not have optional arguments

  Whether or not calls should have optional arguments is an open question
  but the optional fork path would have conflicting arities:

  For example, the last argument to mount/8 is used only by Solaris:

  % arity 6
  -spec mount(alcove_drv:ref(),iodata(),iodata(),iodata(),uint64_t() | [constant()],iodata()) -> 'ok' | {'error', file:posix() | 'unsupported'}.
  % arity 7
  -spec mount(alcove_drv:ref(),iodata(),iodata(),iodata(),uint64_t() | [constant()],iodata(),iodata()) -> 'ok' | {'error', file:posix() | 'unsupported'}.
  % arity 7
  -spec mount(alcove_drv:ref(),fork_path(),iodata(),iodata(),iodata(),uint64_t() | [constant()],iodata()) -> 'ok' | {'error', file:posix() | 'unsupported'}.
  % arity 8
  -spec mount(alcove_drv:ref(),fork_path(),iodata(),iodata(),iodata(),uint64_t() | [constant()],iodata(),iodata()) -> 'ok' | {'error', file:posix() | 'unsupported'}.

* because of the ambiguity in arity, each call can't have an optional
  timeout

  call/5 sets a timeout of 'infinity'. The timeout isn't accessible from
  the "named" functions (e.g., fork, chdir, ...), which means that users
  who need the timeout will have to resort to using the call/5
  interface. An unfortunate side effect of using call/5 is that dialzyer
  won't be able to type check the arguments.

The result of removing the functions without the fork path is that the
code ends up being simpler and more consistent.
2015-07-19 09:41:51 -04:00
Michael Santos
ca0d6f12b4 constants: return {error, unuspported}
Return {error, unsupported} if an atom is used as an argument and the
constant the atom represents does not exist on the current platform.

The previous behaviour was inconsistent and non-deterministic. The
constant might:

* return {error,einval}. System return values could not be distinguished
  from alcove return values.

* cause an exception

* be silently ignored
2015-07-18 10:09:41 -04:00
Michael Santos
f060d44937 errno_id/2,3: convert errno integer to atom 2015-06-16 16:54:13 -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
0f9c27d5d4 prctl: update types and docs 2014-12-24 11:25:11 -05:00
Michael Santos
17c7277a1a Include all args to call in the stack trace 2014-12-21 15:16:53 -05:00
Michael Santos
4efa979037 alcove_proto:returns/1 -> will_return/1 2014-12-13 16:28:34 -05:00
Michael Santos
eed7976d90 Type specs for setpriority/getpriority 2014-12-05 17:17:20 -05:00
Michael Santos
bab762ef2e Move protocol encoding into a separate modules 2014-12-02 07:40:46 -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
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
f55507d906 Add types for setpgid/3,4 2014-11-13 15:43:22 -05:00
Michael Santos
edcdea530c Namespace functions callable from erlang 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
6c218b8bb4 Add specs for setresuid/getresuid 2014-11-05 11:38:39 -05:00
Michael Santos
244c4f6285 Support mount(2) on Solaris
Mount filesystems using the Solaris-specific version of the mount
interface. Solaris adds an options and options length parameter to
mount which takes a NULL terminated string of comma separated arguments.

On other Unix'es the options are either included in the mount flags
(MS_NOEXEC, ...) or in the data argument (<<"size=128M">> for tmpfs).

The behaviour of mount(2) on Solaris is bizarre: the options argument is
input/output, with the mount options placed in the buffer on return.

If the MS_OPTIONSTR is present in the mount flags and the options buffer
is too small, the mount call returns -1 and ERRNO is set to EOVERFLOW
but the mount actually succeeds! A more robust interface might truncate
the options to the size of the buffer, possibly seting the options
length to the required length and return 0.

Surprisingly, the options buffer can also be too large. This is so
weird, it must be a bug in alcove. If the buffer exceeds a certain size,
mount returns -1 with ERRNO set to EINVAL. The mount fails in this case:

    {error,einval} = alcove:mount(Drv, [Child], "swap", Dir, "tmpfs",
            [ms_optionstr], <<>>, <<"size=16m", 0:(1024*8)>>).

Since Solaris has this extra argument, mount/6,7 has to be extended to
mount/7,8, forcing all platforms to pass in an options parameter. This
parameter is ignored on all platforms except Solaris. The result is that
the mount interface is not the Linux mount(2) interface, it is some weird
hybrid that is awkward to use on all platforms (the interface does not
map to the mount(2) man page on any platform).

The value of the option parameter is also not returned to the caller on
Solaris. Options to fix this include:

* breaking out Solaris mount(2) to a Solaris specific call (mountext or
  whatever)

* checking if opt is non-NULL on return and opt len > 0. If so, return:

    {ok, binary()}

  This extends the mount/7,8 type to:

    ok | {ok,binary()} | {error,posix()}
2014-10-26 10:42:54 -04:00
Michael Santos
45cbda4393 const'ify all the things 2014-10-17 12:01:09 -04:00
Michael Santos
26f3743e95 Fix signal handling in calls
There is a race condition in signal handling that gives invalid
responses to calls on some systems:

    in function alcove_seccomp_tests:'-kill/1-fun-2-'/2 (test/alcove_seccomp_tests.erl, line 76)
    **error:{assertEqual_failed,[{module,alcove_seccomp_tests},
                         {line,76},
                         {expression,"Reply1"},
                         {expected,{'EXIT',{termsig,sigsys}}},
                         {value,ok}]}

        alcove_seccomp_tests:77: kill...*failed*
    in function alcove_seccomp_tests:'-kill/1-fun-4-'/2 (test/alcove_seccomp_tests.erl, line 77)
    **error:{assertEqual_failed,[{module,alcove_seccomp_tests},
                         {line,77},
                         {expression,"Reply2"},
                         {expected,{error,esrch}},
                         {value,ok}]}

The failed tests indicates the parent process has seen the child
process' control fd has been closed but the child process has either not
yet terminated or the termination status has not been propagated to the
parent.

There are 2 types of calls: calls that return a value and calls that
never return such as execve(2), execvp(2) and exit(2).

For calls that do not return, the closing of the child's control fd
indicates success.

For all other calls, the child should not exit. If the parent generates
a "fdctl_closed" event on behalf of the child, the erlang process blocks
until a termsig event is received.
2014-09-30 14:59:50 -04:00
Michael Santos
f61e96ce65 setproctitle/2,3: add types 2014-09-25 10:53:36 -04:00
Michael Santos
a6cb2d5811 list_to_buf: write results to a static buf 2014-09-21 11:34:18 -04:00
Michael Santos
489069f934 mount: convert mountflags to list
Pass in an integer or a list of integers/atoms for the mount flags
parameter.

The mount flags is an unsigned integer and the decode function returns
an int. While this is probably ok, there are a number of typecasts in
the lookup code that needs to cleaned up.
2014-09-20 15:23:37 -04:00
Michael Santos
d1c78b831a clone/2,3: use a list of atoms/integers for flags
Accept an integer or a list of atoms/integers for the flags argument. If
a list is passed, the value are OR'ed together. The values for atoms are
looked up from the constants defined in the system header files.
2014-09-20 15:13:56 -04:00
Michael Santos
fcfbc57439 open/4,5: allow atoms/lists as flags
Modify the open/4,5 function to take:

    * integers
    * lists of integers or atoms

The atoms are named the same as the O_* macros, except lowercased.
2014-09-20 13:26:38 -04:00
Michael Santos
5672ba1ff8 prctl/6,7: support atoms as options or args 2014-09-20 11:41:38 -04:00
Michael Santos
0e67a572e4 kill/3,4: accept name or integer for signal 2014-09-17 09:55:00 -04:00
Michael Santos
85acc907b2 Lowercase all the atoms
Use lowercase atoms for the constants taken from header files.
2014-09-15 07:31:09 -04:00
Michael Santos
84c7d2aed1 Use atoms to represent signals
Accept and return signals as names. For example, if a signal is trapped,
the process will receive:

    {signal,'SIGCHLD'}

Versus:

    {signal,17} % on linux

Benefits:

* similar to the way errno is returned, e.g., {errno,enametoolong}

* better portability: integer values differ by platform

* library user does not need lookup the signal value before calling
  sigaction/3,4 or pattern matching the process mailbox

Problems:

* sigaction/3,4 will throw badarg if the name is unknown

  Probably it should return an error ({error,einval}).

* not all functions dealing with signals have been changed to use atoms,
  e.g., kill/3,4

* all functions with mapping of constants -> integers should take
  constants:

    clone_define
    mount_define
    file_define
    prctl_define
    rlimit_define
    syscall_define

  If this is done, alcove:define/2,3 and the *_define/*_constant functions
  should be removed.

* should the constants be upper or lower case atoms?

  atoms are typically lowercased, e.g., {error,ebadf} not
  {error,'EBADF'}

  For example:

    PR_SET_PDEATHSIG -> pr_set_pdeathsig

* if the interface accepts constants, should support for integers be
  removed?

  1. No way to pass in a "raw" signum if the signal does not have a name

  2. Signals without a name will be sent to the process mailbox as:

        {signal,unknown}

     If the caller has trapped 58 and 59, there won't be a way to
     distinguish these signals.
2014-09-14 10:46:14 -04:00
Michael Santos
5de77aad67 Change return value for unknown constants
Mirror the behaviour of erl_errno_id() and use the atom 'unknown' for
unknown constants rather than 'false'.
2014-09-13 13:19:30 -04:00
Michael Santos
55c9c3f1bc alcove:define/2,3: simplify matching atoms 2014-09-13 12:41:14 -04:00
Michael Santos
8449228690 any() -> term()
Follow the example of erlang:port_call/3 and use the term() alias for
any().
2014-09-06 11:21:32 -04:00
Michael Santos
357c307920 alcove_list_to_buf: check length of source buf 2014-08-24 10:15:16 -04:00
Michael Santos
c20f60f06f Check length of buf before encoding
Convert the remaining functions to check the length of the destination
buffer when encoding the term format.
2014-08-23 11:22:54 -04:00
Michael Santos
1fef983004 Use a tuple to represent the C function signature
Replace use of a list to pass arguments to a call with a tuple. Parsing
the elements in the external term format is less error prone. A list, in
the term format, can be:

* a string (a char buf)
* a list header followed by terms, terminated with an empty list
* a list header followed by 1 element, followed by a list header ...,
  terminated by an empty list

Since the calls take a fixed number of arguments (none of the vararg
versions are used), a tuple might be a better erlang representation.
It also fits in better with the usage of tuples in other functional
languages, where a tuple can hold any types but a list contains the
same type.

        alcove:call(Port, fork, []) -> fork()

        alcove:call(Port, exit, [1]) -> exit(1)

        alcove:call(Port, execvp, ["/bin/echo", ["/bin/echo",
                "foobar"]]) -> execvp("/bin/echo", argv)

        alcove:call(Drv, open, ["/tmp/foo", 0, 0]) ->
                open("/tmp/foo", 0, 0)

Would become:

        alcove:call(Port, fork, {}) -> fork()

        alcove:call(Port, exit, {1}) -> exit(1)

        alcove:call(Port, execvp, {"/bin/echo", ["/bin/echo",
                "foobar"]}) -> execvp("/bin/echo", argv)

        alcove:call(Drv, open, {"/tmp/foo", 0, 0}) ->
                open("/tmp/foo", 0, 0)

However that exposes the caller to the dreaded single element tuple.

Simplify the C port code by converting the argument list to a tuple.
2014-08-15 09:25:29 -04:00
Michael Santos
df38a30f73 Minor clean up of term handling 2014-08-11 10:48:44 -04:00
Michael Santos
376487da93 Remove dependency on erl_interface/pthreads
Convert to static buffers for encoding/decoding the erlang external term
format using ei.

Use of pthreads when fork/exec is involved is risky. After a process
calls fork(), threads may be holding a lock and cause a deadlock. So the
operations that can be performed after a fork (before exec) are
basically limited to the same operations that can be done in a signal
handler. Operations that may be unsafe include malloc and the f* stream
functions.

alcove relied on erl_interface for encoding/decoding the external term
format. Internally, erl_interface uses pthreads.

Since the interesting part of alcove is the set of operations that can
be done post-fork and pre-exec, remove the dependency on erl_interface. The
change was done manually and was pretty massive for something done
by hand. alcove compiles and all the tests pass but there will be
regressions, especially considering not all calls are tested. For example,
cgroups are likely broken.

These changes have been tested only on Linux/32-bit/ARM.

This change had the benefit of simplifying the code and applying limits
to some of the inputs.

ei was used for convenience. It may be better to move to a custom
protocol or building a custom library for decoding the ETF.

ei/the term format has many problems:

* lists of small integers are encoded as strings

  A list may be magically converted to a string if the list is comprised
  of integers 0-255. Interestingly, 0 is considered to be a string even
  though strings are NULL-terminated. So [0] is encoded as a string.

  C code must catch both cases and possibly normalize the string into a
  list. Even trickier since a list is terminated by an empty list.

* integer overflows

  All ei functions dealing with static buffers take an pointer to an
  offset. This index is a signed integer which can overflow.

  Not a problem for alcove since messages are limited to lengths that can be
  represented in 2 bytes.

* buffer overflows

  The ei functions do not take a length. While the size of certain types
  (strings, binaries, atoms) can be queried using ei_get_type(), other
  types report a size of 0. Since the size of these encodings are not
  known to the caller, it is questionable what is an appropriately sized
  buffer.

The term encoding changes in this commit are sketchy and need to be
cleaned up. Some of the functions encode a complete term, others encode
at an offset and others return an allocated buffer.
2014-08-10 12:20:34 -04:00
Michael Santos
56857281f8 Crash if call/2,3,4,5 reaches the timeout
To ensure consistency, exit if a timeout is set and reached. Otherwise,
late messages may arrive in the queue, messing up subsequent calls:

    1> {ok,P} = alcove_drv:start().
    {ok,<0.45.0>}
    2> catch alcove:call(P, [], getpid, [], 0).
    {'EXIT',timeout}
    3> alcove:version(P).
    2897
    4> flush().
    Shell got {alcove_call,<0.45.0>,[],<<"0.6.1">>}
    ok

Using timeouts might be needed if it is not known whether is still
running in the event loop.

Remove the cast functions, since they are dangerous and can be emulated
by using call/5.
2014-08-04 16:26:18 -04:00
Michael Santos
a338021cce types: convert to using timeout() in type spec 2014-08-04 14:58:56 -04:00
Michael Santos
7a07c8eec1 Add call/5: call with timeout
Fix the code to match the documentation and add a call with timeout.

Simplify the alcove_drv module by removing the versions of call with
defaults. The alcove module does the work of setting default values.
2014-08-03 10:09:58 -04:00
Michael Santos
9216014669 Specs for setsid/1,2 2014-07-16 13:49:45 -04:00
Michael Santos
1deadf3e90 Specs for call/2,3,4 2014-07-15 17:47:41 -04:00
Michael Santos
d38e617e7d Specs for cast/2,3,4 2014-07-14 16:31:24 -04:00
Michael Santos
ee0e213d29 Specs for event/1,2,3 2014-07-13 11:29:55 -04:00
Michael Santos
43c58773e8 Specs for stdin/2 2014-07-12 09:32:01 -04:00
Michael Santos
a20154553c Specs for stderr/1,2 2014-07-11 16:25:05 -04:00
Michael Santos
216d0e388f Correct specs for stdout/1,2 2014-07-10 17:03:54 -04:00
Michael Santos
f25c9377bb Correct type for version/1,2
Calls now have an infinite timeout.
2014-07-09 10:07:36 -04:00
Michael Santos
ec1a87b4f4 specs: add types for file descriptors
Distinguish fd's and sets of fd's from other lists of integers.
2014-07-08 18:00:31 -04:00