TMPDIR = os:getenv("TMPDIR", "/tmp"),

Compile = fun(Name0, Prog) ->
    Name = filename:join(TMPDIR, [os:getpid(), "-", Name0]),
    ok = file:write_file(Name, Prog, [write, exclusive]),
    IO = case os:getenv("ALCOVE_CONFIG_VERBOSE", false) of
      false -> " > /dev/null 2>&1";
      _ -> ""
    end,
    Cmd = erlang:open_port(
      {spawn, ["${CC-cc} -Werror ${ALCOVE_LDFLAGS-} -o /dev/null ", Name, IO]},
      [stream, exit_status]
    ),
    Status = receive
        {Cmd, {exit_status, 0}} ->
            true;
        {Cmd, {exit_status, _}} ->
            false
    end,
    ok = file:delete(Name),
    Status
end,

Test = fun(Name, Prog, Supported, Unsupported) ->
    case Compile(Name, Prog) of
        true ->
            Supported;
        false ->
            Unsupported
    end
end,

Only = fun(OS, Name, Prog, Supported, Unsupported) ->
    case os:type() of
        OS ->
            Test(Name, Prog, Supported, Unsupported);
        _ ->
            Unsupported
    end
end,

Linux = fun(Name, Prog, Supported, Unsupported) ->
    Only({unix,linux}, Name, Prog, Supported, Unsupported)
end,

Append = fun(Str, Flag) ->
    string:join(sets:to_list(sets:add_element(Flag,
                    sets:from_list(string:tokens(Str, " ")))), " ")
end,

Setenv = fun(_Key, "") ->
                true;
            (Key, Val) ->
                Cur = os:getenv(Key, ""),
                os:putenv(Key, Append(Cur, Val))
end,

%%
%% Tests
%%

% Support for fexecve(3)
Fexecve = fun(Config) ->
    Prog = "
#include <unistd.h>
int main(int argc, char *argv[], char *envp[]) {
    (void)fexecve(0, argv, envp);
    return 0;
}",
    Flag = Test("test_fexecve.c", Prog, "-DHAVE_FEXECVE", ""),
    true = Setenv("ALCOVE_DEFINE", Flag),
    Config
end,

% Linux: support for setns(2)
Setns = fun(Config) ->
    Prog = "
#define _GNU_SOURCE
#include <sched.h>
int main(int argc, char *argv[]) {
    (void)setns(0,0);
    return 0;
}",
    Flag = Linux("test_setns.c", Prog, "-DHAVE_SETNS", ""),
    true = Setenv("ALCOVE_DEFINE", Flag),
    Config
end,

% Linux: support for seccomp mode using prctl(2)
PrctlSeccomp = fun(Config) ->
    Prog = "
#include <linux/seccomp.h>
int main(int argc, char *argv[]) {
#ifdef SECCOMP_MODE_FILTER
    return 0;
#endif
}",
    Flag = Linux("test_prctl_seccomp.c", Prog, "-DHAVE_PRCTL_SECCOMP", ""),
    true = Setenv("ALCOVE_DEFINE", Flag),
    Config
end,

% Linux: support for seccomp mode using seccomp(2)
Seccomp = fun(Config) ->
    Prog = "
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>

int main(int argc, char *argv[]) {
    return seccomp(SECCOMP_SET_MODE_STRICT, 0, NULL);
}",
    Flag = Linux("test_seccomp.c", Prog, "-DHAVE_SECCOMP", ""),
    true = Setenv("ALCOVE_DEFINE", Flag),
    Config
end,

RunTests = fun() ->
    case code:lib_dir(alcove, ebin) of
      {error, bad_name} ->
        true;
      Lib ->
        Path = code:get_path(),
        _ = code:del_path(Lib),
        try alcove_drv:progname() of
          Progname ->
            case file:read_file_info(Progname) of
              {ok, _} -> false;
              _ -> true
            end
        catch
            _:_ ->
              true
        after
            code:set_path(Path)
        end
    end
end,

case RunTests() of
    true ->
        lists:foldl(fun(Fun, Cfg) ->
                Fun(Cfg)
            end,
            CONFIG,
            [Fexecve, Setns, PrctlSeccomp, Seccomp]
        );
    false ->
        CONFIG
end.
