mirror of
https://github.com/uselessd/alcove.git
synced 2026-04-15 01:04:41 +00:00
759 lines
23 KiB
Erlang
759 lines
23 KiB
Erlang
%%% Copyright (c) 2014, Michael Santos <michael.santos@gmail.com>
|
|
%%%
|
|
%%% Permission to use, copy, modify, and/or distribute this software for any
|
|
%%% purpose with or without fee is hereby granted, provided that the above
|
|
%%% copyright notice and this permission notice appear in all copies.
|
|
%%%
|
|
%%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
%%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
%%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
%%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
%%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
%%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
%%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
-module(alcove_tests).
|
|
|
|
-compile(export_all).
|
|
|
|
-record(state, {
|
|
pid,
|
|
child,
|
|
os,
|
|
clone = false
|
|
}).
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
-include_lib("kernel/include/file.hrl").
|
|
-include_lib("alcove/include/alcove.hrl").
|
|
|
|
alcove_test_() ->
|
|
{setup,
|
|
fun start/0,
|
|
fun stop/1,
|
|
fun run/1
|
|
}.
|
|
|
|
run(State) ->
|
|
% Test order must be maintained
|
|
[
|
|
msg(State),
|
|
version(State),
|
|
iodata(State),
|
|
pid(State),
|
|
getpid(State),
|
|
setopt(State),
|
|
event(State),
|
|
sethostname(State),
|
|
env(State),
|
|
setns(State),
|
|
unshare(State),
|
|
mount_define(State),
|
|
mount(State),
|
|
tmpfs(State),
|
|
chroot(State),
|
|
chdir(State),
|
|
setrlimit(State),
|
|
setgid(State),
|
|
setuid(State),
|
|
fork(State),
|
|
badpid(State),
|
|
signal(State),
|
|
portstress(State),
|
|
forkstress(State),
|
|
forkchain(State),
|
|
eof(State),
|
|
alloc(State),
|
|
prctl(State),
|
|
execvp(State),
|
|
execvp_with_signal(State),
|
|
stdout(State),
|
|
stderr(State),
|
|
execve(State),
|
|
stream(State),
|
|
open(State),
|
|
execvp_mid_chain(State)
|
|
].
|
|
|
|
start() ->
|
|
% export ALCOVE_TEST_EXEC="sudo valgrind --leak-check=yes --log-file=/tmp/alcove.log"
|
|
Exec = getenv("ALCOVE_TEST_EXEC", "sudo"),
|
|
Use_fork = false =/= getenv("ALCOVE_TEST_USE_FORK", false),
|
|
|
|
{ok, Drv} = alcove_drv:start([{exec, Exec}, {maxchild, 8}]),
|
|
|
|
ok = alcove:sigaction(Drv, sigchld, trap),
|
|
|
|
case {Use_fork, os:type()} of
|
|
{false, {unix,linux} = OS} ->
|
|
{ok, Child} = alcove:clone(Drv, [
|
|
clone_newipc,
|
|
clone_newnet,
|
|
clone_newns,
|
|
clone_newpid,
|
|
clone_newuts
|
|
]),
|
|
#state{
|
|
pid = Drv,
|
|
child = Child,
|
|
clone = true,
|
|
os = OS
|
|
};
|
|
{_, {unix,_} = OS} ->
|
|
{ok, Child} = alcove:fork(Drv),
|
|
#state{
|
|
pid = Drv,
|
|
child = Child,
|
|
os = OS
|
|
}
|
|
end.
|
|
|
|
stop(#state{pid = Drv}) ->
|
|
alcove_drv:stop(Drv).
|
|
|
|
msg(_) ->
|
|
% Length, Message type, Term
|
|
% The length of the first message is stripped off by erlang
|
|
Msg = <<
|
|
0,3, 0,0,1,39,
|
|
0,47, 0,3, 0,0,2,39,
|
|
0,39, 0,3, 0,0,3,39,
|
|
0,13, 0,4, 131,109,0,0,0,5,48,46,50,46,48,
|
|
0,16, 0,3, 0,0,3,46,
|
|
0,8, 0,4, 131,100,0,2,111,107
|
|
>>,
|
|
|
|
Reply = alcove_codec:decode(Msg),
|
|
|
|
?_assertEqual(
|
|
[{alcove_call,[295,551,807],<<"0.2.0">>},
|
|
{alcove_call,[814],ok}],
|
|
Reply
|
|
).
|
|
|
|
version(#state{pid = Drv}) ->
|
|
Version = alcove:version(Drv),
|
|
?_assertEqual(true, is_binary(Version)).
|
|
|
|
iodata(#state{pid = Drv}) ->
|
|
Iolist = ["0",1,"2",3,
|
|
[4,[5,6,<<7,8,9>>,["10"]]],
|
|
<<"1112, 13, 14, 15">>,
|
|
[16,17,"18,19,20"],
|
|
21,
|
|
<<22>>],
|
|
|
|
Reply0 = alcove:iolist_to_bin(Drv, Iolist),
|
|
|
|
% Valid iolists: binary, string, lists, bytes must be within a list
|
|
Reply1 = (catch alcove:iolist_to_bin(Drv, 10)),
|
|
Reply2 = (catch alcove:iolist_to_bin(Drv, [123456])),
|
|
Reply3 = alcove:iolist_to_bin(Drv, <<1,2,3,4,5,6>>),
|
|
Reply4 = alcove:iolist_to_bin(Drv, "ok"),
|
|
|
|
% Arbitrary implementation limit of 16 nested
|
|
% lists. iolist_to_binary/1 does not have this limitation.
|
|
Reply5 = alcove:iolist_to_bin(Drv, [[[[[[[[[[[[[[[["ok"]]]]]]]]]]]]]]]]),
|
|
Reply6 = (catch alcove:iolist_to_bin(Drv, [[[[[[[[[[[[[[[[["fail"]]]]]]]]]]]]]]]]])),
|
|
|
|
[
|
|
?_assertEqual(Reply0, iolist_to_binary(Iolist)),
|
|
?_assertMatch({'EXIT',{badarg,_}}, Reply1),
|
|
?_assertMatch({'EXIT',{badarg,_}}, Reply2),
|
|
?_assertEqual(<<1,2,3,4,5,6>>, Reply3),
|
|
?_assertEqual(<<"ok">>, Reply4),
|
|
?_assertEqual(<<"ok">>, Reply5),
|
|
?_assertMatch({'EXIT',{badarg,_}}, Reply6)
|
|
].
|
|
|
|
pid(#state{pid = Drv}) ->
|
|
Pids = alcove:pid(Drv),
|
|
?_assertEqual(1, length(Pids)).
|
|
|
|
getpid(#state{clone = true, pid = Drv, child = Child}) ->
|
|
% Running in a PID namespace
|
|
PID = alcove:getpid(Drv, [Child]),
|
|
?_assertEqual(1, PID);
|
|
getpid(#state{pid = Drv, child = Child}) ->
|
|
PID = alcove:getpid(Drv, [Child]),
|
|
?_assertEqual(true, PID > 0).
|
|
|
|
setopt(#state{pid = Drv}) ->
|
|
{ok, Fork} = alcove:fork(Drv),
|
|
|
|
true = alcove:setopt(Drv, [Fork], maxchild, 128),
|
|
|
|
{ok, Fork1} = alcove:fork(Drv, [Fork]),
|
|
|
|
Opt1 = alcove:getopt(Drv, [Fork], maxchild),
|
|
Opt2 = alcove:getopt(Drv, [Fork, Fork1], maxchild),
|
|
|
|
alcove:exit(Drv, [Fork, Fork1], 0),
|
|
|
|
true = alcove:setopt(Drv, [Fork], exit_status, 0),
|
|
true = alcove:setopt(Drv, [Fork], maxforkdepth, 0),
|
|
|
|
Opt3 = alcove:getopt(Drv, [Fork], exit_status),
|
|
Opt4 = alcove:getopt(Drv, [], maxforkdepth),
|
|
Opt5 = alcove:getopt(Drv, [Fork], maxforkdepth),
|
|
Reply = alcove:fork(Drv, [Fork]),
|
|
|
|
alcove:exit(Drv, [Fork], 0),
|
|
|
|
[
|
|
?_assertEqual(128, Opt1),
|
|
?_assertEqual(128, Opt2),
|
|
?_assertEqual(0, Opt3),
|
|
?_assertNotEqual(0, Opt4),
|
|
?_assertEqual(0, Opt5),
|
|
?_assertEqual({error,eagain}, Reply)
|
|
].
|
|
|
|
event(#state{pid = Drv}) ->
|
|
{ok, Fork} = alcove:fork(Drv),
|
|
Reply0 = alcove:exit(Drv, [Fork], 0),
|
|
Reply1 = alcove:event(Drv, [Fork], 5000),
|
|
Reply2 = alcove:event(Drv, [], 5000),
|
|
|
|
[
|
|
?_assertEqual(ok, Reply0),
|
|
?_assertEqual({exit_status,0}, Reply1),
|
|
?_assertMatch({signal,_}, Reply2)
|
|
].
|
|
|
|
sethostname(#state{clone = true, pid = Drv, child = Child}) ->
|
|
Reply = alcove:sethostname(Drv, [Child], "alcove"),
|
|
Hostname = alcove:gethostname(Drv, [Child]),
|
|
[?_assertEqual(ok, Reply),
|
|
?_assertEqual({ok, <<"alcove">>}, Hostname)];
|
|
sethostname(#state{pid = Drv, child = Child}) ->
|
|
Hostname = alcove:gethostname(Drv, [Child]),
|
|
?_assertMatch({ok, <<_/binary>>}, Hostname).
|
|
|
|
env(#state{pid = Drv, child = Child}) ->
|
|
Reply0 = alcove:getenv(Drv, [Child], "ALCOVE"),
|
|
Reply1 = alcove:setenv(Drv, [Child], "ALCOVE", "12345", 0),
|
|
Reply2 = alcove:getenv(Drv, [Child], "ALCOVE"),
|
|
Reply3 = alcove:setenv(Drv, [Child], "ALCOVE", "abcd", 1),
|
|
Reply4 = alcove:getenv(Drv, [Child], "ALCOVE"),
|
|
Reply5 = alcove:unsetenv(Drv, [Child], "ALCOVE"),
|
|
Reply6 = alcove:getenv(Drv, [Child], "ALCOVE"),
|
|
|
|
Reply7 = alcove:environ(Drv, [Child]),
|
|
Reply8 = alcove:clearenv(Drv, [Child]),
|
|
Reply9 = alcove:environ(Drv, [Child]),
|
|
|
|
[
|
|
?_assertEqual(false, Reply0),
|
|
?_assertEqual(ok, Reply1),
|
|
?_assertEqual(<<"12345">>, Reply2),
|
|
?_assertEqual(ok, Reply3),
|
|
?_assertEqual(<<"abcd">>, Reply4),
|
|
?_assertEqual(ok, Reply5),
|
|
?_assertEqual(false, Reply6),
|
|
|
|
?_assertNotEqual(0, length(Reply7)),
|
|
?_assertEqual(ok, Reply8),
|
|
?_assertEqual(0, length(Reply9))
|
|
].
|
|
|
|
setns(#state{clone = true, pid = Drv, child = Child}) ->
|
|
{ok, Child1} = alcove:fork(Drv),
|
|
ok = alcove:setns(Drv, [Child1], [
|
|
"/proc/",
|
|
integer_to_list(Child),
|
|
"/ns/uts"
|
|
]),
|
|
Hostname0 = alcove:gethostname(Drv, [Child]),
|
|
Hostname1 = alcove:gethostname(Drv, [Child1]),
|
|
?_assertEqual(Hostname0, Hostname1);
|
|
setns(_) ->
|
|
[].
|
|
|
|
unshare(#state{clone = true, pid = Drv}) ->
|
|
{ok, Child1} = alcove:fork(Drv),
|
|
ok = alcove:unshare(Drv, [Child1], [clone_newuts]),
|
|
Reply = alcove:sethostname(Drv, [Child1], "unshare"),
|
|
Hostname = alcove:gethostname(Drv, [Child1]),
|
|
[?_assertEqual(ok, Reply),
|
|
?_assertEqual({ok, <<"unshare">>}, Hostname)];
|
|
unshare(_) ->
|
|
[].
|
|
|
|
mount_define(#state{os = {unix,sunos}, pid = Drv}) ->
|
|
Flags = alcove:define(Drv, [
|
|
rdonly,
|
|
nosuid
|
|
]),
|
|
?_assertEqual(true, is_integer(Flags));
|
|
mount_define(#state{pid = Drv}) ->
|
|
Flags = alcove:define(Drv, [
|
|
rdonly,
|
|
nosuid,
|
|
noexec,
|
|
noatime
|
|
]),
|
|
?_assertEqual(true, is_integer(Flags)).
|
|
|
|
mount(#state{clone = true, pid = Drv, child = Child}) ->
|
|
Mount = alcove:mount(Drv, [Child], "/tmp", "/mnt", "", [
|
|
ms_bind,
|
|
ms_rdonly,
|
|
ms_noexec
|
|
], "", ""),
|
|
Umount = alcove:umount(Drv, [Child], "/mnt"),
|
|
[
|
|
?_assertEqual(ok, Mount),
|
|
?_assertEqual(ok, Umount)
|
|
];
|
|
mount(_) ->
|
|
[].
|
|
|
|
tmpfs(#state{clone = true, pid = Drv, child = Child}) ->
|
|
Mount = alcove:mount(Drv, [Child], "tmpfs", "/mnt", "tmpfs", [ms_noexec], <<"size=16M", 0>>, <<>>),
|
|
Umount = alcove:umount(Drv, [Child], "/mnt"),
|
|
[
|
|
?_assertEqual(ok, Mount),
|
|
?_assertEqual(ok, Umount)
|
|
];
|
|
tmpfs(#state{os = {unix,OS}, pid = Drv, child = Child}) when OS =:= linux; OS =:= openbsd ->
|
|
% Linux: running in a fork in the global namespace
|
|
Dir = "/tmp/alcove." ++ [ crypto:rand_uniform(16#30,16#39) || _ <- lists:seq(1,8) ],
|
|
ok = alcove:mkdir(Drv, [Child], Dir, 8#700),
|
|
Mount = alcove:mount(Drv, [Child], "tmpfs", Dir, "tmpfs", [ms_noexec],
|
|
<<"size=16M", 0>>, <<>>),
|
|
Umount = alcove:umount(Drv, [Child], Dir),
|
|
Rmdir = alcove:rmdir(Drv, [Child], Dir),
|
|
[
|
|
?_assertEqual(ok, Mount),
|
|
?_assertEqual(ok, Umount),
|
|
?_assertEqual(ok, Rmdir)
|
|
];
|
|
tmpfs(#state{os = {unix,sunos}, pid = Drv, child = Child}) ->
|
|
Dir = "/tmp/alcove." ++ [ crypto:rand_uniform(16#30,16#39) || _ <- lists:seq(1,8) ],
|
|
ok = alcove:mkdir(Drv, [Child], Dir, 8#700),
|
|
Mount = alcove:mount(Drv, [Child], "swap", Dir, "tmpfs", [ms_optionstr],
|
|
<<>>, <<"size=16m", 0:4096>>),
|
|
Umount = alcove:umount(Drv, [Child], Dir),
|
|
Rmdir = alcove:rmdir(Drv, [Child], Dir),
|
|
[
|
|
?_assertEqual(ok, Mount),
|
|
?_assertEqual(ok, Umount),
|
|
?_assertEqual(ok, Rmdir)
|
|
];
|
|
tmpfs(_) ->
|
|
[].
|
|
|
|
chroot(#state{os = {unix,OS}, pid = Drv, child = Child}) when OS =:= linux; OS =:= openbsd ->
|
|
Reply = alcove:chroot(Drv, [Child], "/bin"),
|
|
?_assertEqual(ok, Reply);
|
|
chroot(#state{os = {unix,OS}, pid = Drv, child = Child}) when OS =:= freebsd; OS =:= netbsd ->
|
|
Reply = alcove:chroot(Drv, [Child], "/rescue"),
|
|
?_assertEqual(ok, Reply);
|
|
chroot(_) ->
|
|
[].
|
|
|
|
chdir(#state{pid = Drv, child = Child}) ->
|
|
Reply = alcove:chdir(Drv, [Child], "/"),
|
|
CWD = alcove:getcwd(Drv, [Child]),
|
|
[
|
|
?_assertEqual(ok, Reply),
|
|
?_assertEqual({ok, <<"/">>}, CWD)
|
|
].
|
|
|
|
setrlimit(#state{pid = Drv, child = Child}) ->
|
|
Reply = alcove:setrlimit(Drv, [Child], rlimit_nofile, #alcove_rlimit{cur = 64, max = 64}),
|
|
Rlimit = alcove:getrlimit(Drv, [Child], rlimit_nofile),
|
|
[
|
|
?_assertEqual(ok, Reply),
|
|
?_assertEqual({ok, #alcove_rlimit{cur = 64, max = 64}}, Rlimit)
|
|
].
|
|
|
|
setgid(#state{pid = Drv, child = Child}) ->
|
|
Reply = alcove:setgid(Drv, [Child], 65534),
|
|
GID = alcove:getgid(Drv, [Child]),
|
|
[
|
|
?_assertEqual(ok, Reply),
|
|
?_assertEqual(65534, GID)
|
|
].
|
|
|
|
setuid(#state{pid = Drv, child = Child}) ->
|
|
Reply = alcove:setuid(Drv, [Child], 65534),
|
|
UID = alcove:getuid(Drv, [Child]),
|
|
[
|
|
?_assertEqual(ok, Reply),
|
|
?_assertEqual(65534, UID)
|
|
].
|
|
|
|
fork(#state{pid = Drv, child = Child}) ->
|
|
Pids = [ alcove:fork(Drv, [Child]) || _ <- lists:seq(1, 32) ],
|
|
[Last|_Rest] = lists:reverse(Pids),
|
|
Reply0 = alcove:getpid(Drv, [Child]),
|
|
|
|
[{ok, Child0}|_] = [ N || N <- Pids, N =/= {error,eagain} ],
|
|
ok = alcove:exit(Drv, [Child, Child0], 0),
|
|
waitpid(Drv, [Child], Child0),
|
|
Reply1 = alcove:fork(Drv, [Child]),
|
|
Reply2 = alcove:fork(Drv, [Child]),
|
|
|
|
% XXX discard output from killed process
|
|
flush(stdout, Drv, [Child]),
|
|
|
|
[
|
|
?_assertEqual(true, is_integer(Reply0)),
|
|
?_assertEqual({error,eagain}, Last),
|
|
|
|
?_assertMatch({ok, _}, Reply1),
|
|
?_assertEqual({error,eagain}, Reply2)
|
|
].
|
|
|
|
badpid(#state{pid = Drv}) ->
|
|
{ok, Child} = alcove:fork(Drv),
|
|
|
|
% EPIPE or PID not found
|
|
ok = alcove:execvp(Drv, [Child], "/bin/sh",
|
|
["/bin/sh", "-c", "echo > /dev/null"]),
|
|
Reply0 = (catch alcove:call(Drv, [Child], execvp, ["/bin/sh",
|
|
["/bin/sh", "-c", "echo > /dev/null"]], 1000)),
|
|
|
|
PID = get_unused_pid(Drv),
|
|
|
|
% PID not found
|
|
Reply1 = (catch alcove:call(Drv, [PID], execvp, ["/bin/sh",
|
|
["/bin/sh", "-c", "echo > /dev/null"]], 1000)),
|
|
|
|
[
|
|
?_assertEqual({'EXIT',timeout}, Reply0),
|
|
?_assertEqual({'EXIT',timeout}, Reply1)
|
|
].
|
|
|
|
signal(#state{pid = Drv}) ->
|
|
{ok, Child1} = alcove:fork(Drv),
|
|
|
|
SA0 = alcove:sigaction(Drv, [Child1], sigterm, ign),
|
|
Kill0 = alcove:kill(Drv, Child1, sigterm),
|
|
Pid0 = alcove:getpid(Drv, [Child1]),
|
|
|
|
SA1 = alcove:sigaction(Drv, [Child1], sigterm, dfl),
|
|
Kill1 = alcove:kill(Drv, Child1, sigterm),
|
|
waitpid(Drv, [], Child1),
|
|
alcove:kill(Drv, Child1, 0),
|
|
Search = alcove:kill(Drv, Child1, 0),
|
|
|
|
[
|
|
?_assertEqual(ok, SA0),
|
|
?_assertEqual(ok, Kill0),
|
|
?_assertEqual(true, is_integer(Pid0)),
|
|
|
|
?_assertEqual(ok, SA1),
|
|
?_assertEqual(ok, Kill1),
|
|
?_assertEqual({error,esrch}, Search)
|
|
].
|
|
|
|
portstress(#state{pid = Drv, child = Child}) ->
|
|
Reply = [ alcove:version(Drv, [Child]) || _ <- lists:seq(1,1000) ],
|
|
Ok = lists:filter(fun
|
|
(false) -> false;
|
|
(_) -> true
|
|
end, Reply),
|
|
?_assertEqual(Ok, Reply).
|
|
|
|
forkstress(#state{pid = Drv}) ->
|
|
{ok, Fork} = alcove:fork(Drv),
|
|
Reply = forkstress_1(Drv, Fork, 100),
|
|
?_assertEqual(ok, Reply).
|
|
|
|
forkchain(#state{pid = Drv}) ->
|
|
{ok, Child0} = alcove:fork(Drv),
|
|
{ok, Child1} = alcove:fork(Drv, [Child0]),
|
|
{ok, Child2} = alcove:fork(Drv, [Child0, Child1]),
|
|
{ok, Child3} = alcove:fork(Drv, [Child0, Child1, Child2]),
|
|
{ok, Child4} = alcove:fork(Drv, [Child0, Child1, Child2, Child3]),
|
|
|
|
Pid = alcove:getpid(Drv, [Child0, Child1, Child2, Child3, Child4]),
|
|
|
|
alcove:exit(Drv, [Child0], 0),
|
|
|
|
?_assertEqual(Pid, Child4).
|
|
|
|
eof(#state{pid = Drv}) ->
|
|
{ok, Child} = alcove:fork(Drv),
|
|
{ok, Child0} = alcove:fork(Drv, [Child]),
|
|
|
|
ok = alcove:eof(Drv, [Child,Child0], stderr),
|
|
Reply0 = alcove:eof(Drv, [Child,Child0], stderr),
|
|
|
|
ok = alcove:eof(Drv, [Child,Child0], stdout),
|
|
Reply1 = alcove:eof(Drv, [Child,Child0], stdout),
|
|
|
|
ok = alcove:eof(Drv, [Child,Child0]),
|
|
Reply2 = case alcove:eof(Drv, [Child,Child0]) of
|
|
{error,esrch} -> ok;
|
|
{error,ebadf} -> ok;
|
|
N -> N
|
|
end,
|
|
|
|
alcove:exit(Drv, [Child], 0),
|
|
|
|
[
|
|
?_assertEqual({error,ebadf}, Reply0),
|
|
?_assertEqual({error,ebadf}, Reply1),
|
|
?_assertEqual(ok, Reply2)
|
|
].
|
|
|
|
alloc(#state{os = {unix,_}, pid = Drv}) ->
|
|
{ok, Buf, Cstruct} = alcove:alloc(Drv,
|
|
[<<1,2,3,4,5,6,7,8,9,10>>,
|
|
{ptr, 11},
|
|
<<11,12,13,14,15>>,
|
|
{ptr, <<16,17,18,19,20,21,22>>},
|
|
<<23,24,25>>]
|
|
),
|
|
Size = erlang:system_info({wordsize,external}),
|
|
Buflen = 10 + Size + 5 + Size + 3,
|
|
[
|
|
?_assertEqual(Buflen, byte_size(Buf)),
|
|
?_assertEqual(lists:nth(1, Cstruct), <<1,2,3,4,5,6,7,8,9,10>>),
|
|
?_assertEqual(lists:nth(2, Cstruct), {ptr, <<0:88>>}),
|
|
?_assertEqual(lists:nth(3, Cstruct), <<11,12,13,14,15>>),
|
|
?_assertEqual(lists:nth(4, Cstruct), {ptr, <<16,17,18,19,20,21,22>>}),
|
|
?_assertEqual(lists:nth(5, Cstruct), <<23,24,25>>)
|
|
].
|
|
|
|
prctl(#state{os = {unix,linux}, pid = Drv}) ->
|
|
{ok, Fork} = alcove:fork(Drv),
|
|
|
|
% capability is set:
|
|
% returns 0 | 1 in function result, arg2 = int
|
|
Reply0 = alcove:prctl(Drv, [Fork], pr_capbset_read, 0, 0,0,0),
|
|
|
|
% set process name:
|
|
% arg2 = char *, up to 16 bytes, NULL terminated
|
|
Reply1 = alcove:prctl(Drv, [Fork], pr_set_name, <<"test",0>>, 0,0,0),
|
|
|
|
% get process name
|
|
% value returned in arg2 = char *, up to 16 bytes
|
|
Reply2 = alcove:prctl(Drv, [Fork], pr_get_name, <<0:(17*8)>>, 0,0,0),
|
|
|
|
% set parent death signal
|
|
% arg2 = signal
|
|
Reply3 = alcove:prctl(Drv, [Fork], pr_set_pdeathsig, 9, 0,0,0),
|
|
|
|
% get parent death signal
|
|
% arg2 = int *
|
|
Reply4 = alcove:prctl(Drv, [Fork], pr_get_pdeathsig, <<0:32>>, 0,0,0),
|
|
|
|
[
|
|
?_assertEqual({ok,1,0,0,0,0}, Reply0),
|
|
?_assertEqual({ok,0,<<116,101,115,116,0>>,0,0,0}, Reply1),
|
|
?_assertMatch({ok,0,<<116,101,115,116,0,0,0,0,0,0,0,0,0,0,0,0,0>>,0,0,0}, Reply2),
|
|
?_assertMatch({ok,0,9,0,0,0}, Reply3),
|
|
?_assertMatch({ok,0,<<9,0,0,0>>,0,0,0}, Reply4)
|
|
];
|
|
prctl(_) ->
|
|
[].
|
|
|
|
|
|
execvp(#state{os = {unix,linux}, pid = Drv, child = Child}) ->
|
|
% cwd = /, chroot'ed in /bin
|
|
Reply = alcove:execvp(Drv, [Child], "/busybox", ["/busybox", "sh"]),
|
|
?_assertEqual(ok, Reply);
|
|
execvp(#state{os = {unix,OS}, pid = Drv, child = Child}) when OS =:= freebsd; OS =:= openbsd; OS =:= netbsd ->
|
|
% cwd = /, chroot'ed in /rescue
|
|
Reply = alcove:execvp(Drv, [Child], "/sh", ["/sh"]),
|
|
?_assertEqual(ok, Reply);
|
|
execvp(#state{os = {unix,OS}, pid = Drv, child = Child}) when OS =:= sunos ->
|
|
% not in a chroot
|
|
Reply = alcove:execvp(Drv, [Child], "/bin/sh", ["/bin/sh"]),
|
|
?_assertEqual(ok, Reply);
|
|
execvp(_) ->
|
|
[].
|
|
|
|
execvp_with_signal(#state{pid = Drv}) ->
|
|
{ok, Fork} = alcove:fork(Drv),
|
|
Reply0 = (catch alcove:execvp(Drv, [Fork], "/bin/sh",
|
|
["/bin/sh", "-c", "kill -9 $$"])),
|
|
Reply1 = alcove:event(Drv, [Fork], 5000),
|
|
|
|
[
|
|
?_assertEqual(ok, Reply0),
|
|
?_assertEqual({termsig,sigkill}, Reply1)
|
|
].
|
|
|
|
stdout(#state{pid = Drv, child = Child}) ->
|
|
Reply = alcove:stdin(Drv, [Child], "echo 0123456789\n"),
|
|
Stdout = alcove:stdout(Drv, [Child], 5000),
|
|
[
|
|
?_assertEqual(true, Reply),
|
|
?_assertEqual(<<"0123456789\n">>, Stdout)
|
|
].
|
|
|
|
stderr(#state{os = {unix,linux}, pid = Drv, child = Child}) ->
|
|
Reply = alcove:stdin(Drv, [Child], "nonexistent 0123456789\n"),
|
|
Stderr = alcove:stderr(Drv, [Child], 5000),
|
|
[
|
|
?_assertEqual(true, Reply),
|
|
?_assertMatch(<<"sh: ", _/binary>>, Stderr)
|
|
];
|
|
stderr(#state{os = {unix,OS}, pid = Drv, child = Child}) when OS =:= freebsd; OS =:= netbsd ->
|
|
Reply = alcove:stdin(Drv, [Child], "nonexistent 0123456789\n"),
|
|
Stderr = alcove:stderr(Drv, [Child], 5000),
|
|
[
|
|
?_assertEqual(true, Reply),
|
|
?_assertEqual(<<"nonexistent: not found\n">>, Stderr)
|
|
];
|
|
stderr(#state{os = {unix,openbsd}, pid = Drv, child = Child}) ->
|
|
Reply = alcove:stdin(Drv, [Child], "nonexistent 0123456789\n"),
|
|
Stderr = alcove:stderr(Drv, [Child], 5000),
|
|
[
|
|
?_assertEqual(true, Reply),
|
|
?_assertMatch(<<"/sh: ", _/binary>>, Stderr)
|
|
];
|
|
stderr(_) ->
|
|
[].
|
|
|
|
execve(#state{pid = Drv}) ->
|
|
{ok, Child0} = alcove:fork(Drv),
|
|
{ok, Child1} = alcove:fork(Drv),
|
|
|
|
Reply0 = (catch alcove:execve(Drv, [Child0], "/usr/bin/env",
|
|
["/usr/bin/env"], ["A=1", "B=2", "C=3", false])),
|
|
Reply1 = alcove:execve(Drv, [Child0], "/usr/bin/env",
|
|
["/usr/bin/env"], ["FOO=bar", "BAR=1234567"]),
|
|
Reply2 = alcove:execve(Drv, [Child1], "/usr/bin/env",
|
|
["/usr/bin/env"], []),
|
|
|
|
Stdout0 = alcove:stdout(Drv, [Child0], 5000),
|
|
Stdout1 = alcove:stdout(Drv, [Child1], 5000),
|
|
|
|
[
|
|
?_assertMatch({'EXIT',{badarg,_}}, Reply0),
|
|
?_assertEqual(ok, Reply1),
|
|
?_assertEqual(ok, Reply2),
|
|
|
|
?_assertEqual(<<"FOO=bar\nBAR=1234567\n">>, Stdout0),
|
|
?_assertEqual(false, Stdout1)
|
|
].
|
|
|
|
stream(#state{pid = Drv}) ->
|
|
Chain = chain(Drv, 16),
|
|
DefaultCount = 1 * 1024 * 1024,
|
|
Count = getenv("ALCOVE_TEST_STREAM_COUNT", integer_to_list(DefaultCount)),
|
|
Cmd = "yes | head -" ++ Count,
|
|
ok = alcove:execvp(Drv, Chain, "/bin/sh", ["/bin/sh", "-c", Cmd]),
|
|
% <<"y\n">>
|
|
Reply = stream_count(Drv, Chain, list_to_integer(Count)*2),
|
|
?_assertEqual(ok, Reply).
|
|
|
|
open(#state{pid = Drv}) ->
|
|
O_RDONLY = alcove:define(Drv, o_rdonly),
|
|
|
|
File = "/nonexistent",
|
|
|
|
Reply0 = alcove:open(Drv, File, O_RDONLY, 0),
|
|
Reply1 = alcove:open(Drv, File, [O_RDONLY,O_RDONLY,O_RDONLY,O_RDONLY], 0),
|
|
Reply2 = alcove:open(Drv, File, [o_rdonly, o_rdonly, o_rdonly], 0),
|
|
|
|
[
|
|
?_assertEqual({error,enoent}, Reply0),
|
|
?_assertEqual({error,enoent}, Reply1),
|
|
?_assertEqual({error,enoent}, Reply2)
|
|
].
|
|
|
|
execvp_mid_chain(#state{os = {unix,OS}, pid = Drv}) ->
|
|
Chain = chain(Drv, 8),
|
|
{Pids, Rest} = lists:split(3, Chain),
|
|
ok = alcove:execvp(Drv, Pids, "/bin/cat", ["/bin/cat"]),
|
|
|
|
alcove:stdin(Drv, Pids, "test\n"),
|
|
Reply0 = alcove:stdout(Drv, Pids, 5000),
|
|
waitpid_exit(Drv, [], lists:last(Rest)),
|
|
Reply1 = [ alcove:kill(Drv, Pid, 0) || Pid <- Rest ],
|
|
|
|
ChildState = case OS of
|
|
openbsd -> {error,esrch};
|
|
_ -> ok
|
|
end,
|
|
|
|
% The child spawned by the exec'ed process becomes a zombie
|
|
% because the PID will not be reaped.
|
|
[
|
|
?_assertEqual(<<"test\n">>, Reply0),
|
|
?_assertEqual([
|
|
ChildState,
|
|
{error,esrch},
|
|
{error,esrch},
|
|
{error,esrch},
|
|
{error,esrch}
|
|
], Reply1)
|
|
].
|
|
|
|
%%
|
|
%% Utility functions
|
|
%%
|
|
getenv(Name, Default) ->
|
|
case os:getenv(Name) of
|
|
false -> Default;
|
|
Env -> Env
|
|
end.
|
|
|
|
waitpid(Drv, Pids, Child) ->
|
|
case alcove:event(Drv, Pids, 5000) of
|
|
{signal, sigchld} ->
|
|
waitpid_exit(Drv, Pids, Child);
|
|
false ->
|
|
false
|
|
end.
|
|
|
|
waitpid_exit(Drv, Pids, Child) ->
|
|
case alcove:kill(Drv, Pids, Child, 0) of
|
|
ok ->
|
|
timer:sleep(10),
|
|
waitpid_exit(Drv, Pids, Child);
|
|
{error,esrch} ->
|
|
ok
|
|
end.
|
|
|
|
flush(stdout, Drv, Pids) ->
|
|
case alcove:stdout(Drv, Pids) of
|
|
false ->
|
|
ok;
|
|
_ ->
|
|
flush(stdout, Drv, Pids)
|
|
end.
|
|
|
|
get_unused_pid(Drv) ->
|
|
PID = crypto:rand_uniform(16#0affffff, 16#0fffffff),
|
|
case alcove:kill(Drv, PID, 0) of
|
|
{error,esrch} -> PID;
|
|
_ -> get_unused_pid(Drv)
|
|
end.
|
|
|
|
forkstress_1(_Drv, _Child, 0) ->
|
|
ok;
|
|
forkstress_1(Drv, Child, N) ->
|
|
{ok, Fork} = alcove:fork(Drv, [Child]),
|
|
ok = alcove:exit(Drv, [Child,Fork], 0),
|
|
Version = alcove:version(Drv, [Child]),
|
|
Version = alcove:version(Drv, [Child]),
|
|
forkstress_1(Drv, Child, N-1).
|
|
|
|
chain(Drv, N) ->
|
|
chain(Drv, [], N).
|
|
|
|
chain(_Drv, Fork, 0) ->
|
|
Fork;
|
|
chain(Drv, Fork, N) ->
|
|
{ok, Child} = alcove:fork(Drv, Fork),
|
|
chain(Drv, Fork ++ [Child], N-1).
|
|
|
|
stream_count(_Drv, _Chain, 0) ->
|
|
ok;
|
|
stream_count(Drv, Chain, N) ->
|
|
receive
|
|
{alcove_stdout, Drv, Chain, Bin} ->
|
|
stream_count(Drv, Chain, N - byte_size(Bin))
|
|
after
|
|
1000 ->
|
|
{error, N}
|
|
end.
|