/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

    Fork.c

Abstract:

    This file is a Fork test.

--*/

#include "lxtcommon.h"
#include "unittests.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <sys/time.h>
#include <fpu_control.h>
#include <alloca.h>
#include <stdlib.h>
#include <sys/prctl.h>

#if defined(__amd64__)

#include <asm/prctl.h>

#endif

#if defined(__i386__)
#define GET_STACK_POINTER asm("esp")
#elif defined(__amd64__)
#define GET_STACK_POINTER asm("rsp")
#elif defined(__arm__) || defined(__aarch64__)
#define GET_STACK_POINTER asm("sp")
#else
#error "Unsupported architecture"
#endif

#include <fcntl.h>
#include <unistd.h>

#define LXT_NAME "Fork"

#define LXT_INVALID_TID_VALUE -1
#define LXT_INVALID_TID_ADDRESS ((void*)LXT_INVALID_TID_VALUE)
#define LXT_THREAD_UMASK 0555
#define LXT_CONTROL_WORD_DEFAULT 0x37f
#define LXT_CONTROL_WORD_NEW 0x40
#define LXT_STACK_SIZE (1 * 1024 * 1024)
#define LXT_TEST_CWD "/"

int SetTidAddress(PLXT_ARGS Args);

int ForkPids(PLXT_ARGS Args);

int ExecvFailure(PLXT_ARGS Args);

int RobustFutex(PLXT_ARGS Args);

int CloneFs(PLXT_ARGS Args);

int CloneInvalidFlags(PLXT_ARGS Args);

int CloneThread(PLXT_ARGS Args);

int VForkTestBasic(PLXT_ARGS Args);

int VForkTest(PLXT_ARGS Args);

int CloneTestFlags(PLXT_ARGS Args);

int CloneTestSignalParent(PLXT_ARGS Args);

//
// Global constants
//
// N.B. Test ordering is important for child process variations.
//
// TODO_LX: Enable fork tests.
//

#define LXT_DEFAULT_VARIATIONS ((unsigned long)(-1))
#define LXT_CHILD_VARIATIONS 0

static const LXT_VARIATION g_LxtVariations[] = {
    {"Fork check pids", ForkPids},
    {"Set tid address", SetTidAddress},
    {"Execv failure", ExecvFailure},
    {"Get / Set Robust Futex List", RobustFutex},
    {"Clone CLONE_FS", CloneFs},
    {"Clone invalid flags", CloneInvalidFlags},
    {"VFork test basic", VForkTestBasic},
    {"Clone thread", CloneThread},
    {"Vfork behavior", VForkTest},
    {"Clone test flags", CloneTestFlags},
    {"Clone signal parent test", CloneTestSignalParent},
};

int ForkTestEntry(int Argc, char* Argv[])

/*++
--*/

{

    LXT_ARGS Args;
    int Result;

    LxtCheckResult(LxtInitialize(Argc, Argv, &Args, LXT_NAME));
    if (Args.VariationMask == LXT_DEFAULT_VARIATIONS)
    {
        Args.VariationMask &= ~LXT_CHILD_VARIATIONS;
    }

    LxtCheckResult(LxtRunVariations(&Args, g_LxtVariations, LXT_COUNT_OF(g_LxtVariations)));

ErrorExit:
    LxtUninitialize();
    return !LXT_SUCCESS(Result);
}

long my_set_tid_address(int* tid)

{
    return syscall(SYS_set_tid_address, tid);
}

int my_futex(int* uaddr, int op, int val, const struct timespec* timeout, int* uaddr2, int val3)

{
    return syscall(SYS_futex, uaddr, op, val, timeout, uaddr2, val3);
}

int my_set_robust_list(struct robust_list_head* head, size_t len)

{
    return syscall(SYS_set_robust_list, head, len);
}

int my_get_robust_list(int pid, struct robust_list_head** head_ptr, size_t* len_ptr)

{
    return syscall(SYS_get_robust_list, pid, head_ptr, len_ptr);
}

mode_t my_umask(mode_t mask)

{
    return syscall(SYS_umask, mask);
}

void* SetChildTidThread(void* Args)

{

    int* TidPointer = Args;
    int tid;
    mode_t umaskTemp;

    tid = my_set_tid_address(TidPointer);
    LxtLogInfo("In pthread tid = %d", tid);
    if (TidPointer != LXT_INVALID_TID_ADDRESS)
    {
        *TidPointer = tid;
    }

    umaskTemp = my_umask(LXT_THREAD_UMASK);
    LxtLogInfo("In pthread tid = %d, initial umask %u, umask set to %u", tid, umaskTemp, LXT_THREAD_UMASK);

    sleep(2);
    return 0;
}

int SetTidAddress(PLXT_ARGS Args)
{

    pid_t ChildPid;
    mode_t ChildUmask;
    int ForkTid;
    mode_t OriginalUmask;
    int ParentTid;
    int Result = LXT_RESULT_FAILURE;
    int Return;
    int SavedThread1Tid = 0;
    pthread_t Thread1 = {0};
    int Thread1Tid;
    pthread_t Thread2 = {0};
    int Thread2Tid;
    mode_t UmaskTemp;

    ChildUmask = 0770;
    OriginalUmask = 0777;
    ForkTid = -1;
    UmaskTemp = my_umask(OriginalUmask);
    LxtLogInfo("Initial umask %u", UmaskTemp);
    LxtLogInfo("OriginalUmask %u", OriginalUmask);
    ParentTid = -1;
    Thread1Tid = -1;
    Thread2Tid = -1;
    ParentTid = my_set_tid_address(&ParentTid);
    LxtCheckResult(ChildPid = fork());
    if (ChildPid == 0)
    {

        //
        // Set the clear child tid value for the new process.
        //

        ForkTid = my_set_tid_address(&ForkTid);
        UmaskTemp = my_umask(ChildUmask);
        LxtLogInfo("Before Thread1");
        LxtLogInfo("Initial child umask %u", UmaskTemp);
        LxtLogInfo("ChildUmask %u", ChildUmask);
        LxtLogInfo("ParentTid %d", ParentTid);
        LxtLogInfo("ForkTid %d", ForkTid);
        LxtLogInfo("Thread1Tid %d", Thread1Tid);
        LxtLogInfo("Thread2Tid %d", Thread2Tid);

        //
        // Spawn two threads and set a different address for each child tid.
        //

        LxtCheckErrno(pthread_create(&Thread1, NULL, SetChildTidThread, &Thread1Tid));
        LxtCheckErrno(pthread_create(&Thread2, NULL, SetChildTidThread, LXT_INVALID_TID_ADDRESS));
        sleep(1);
        SavedThread1Tid = Thread1Tid;
        UmaskTemp = my_umask(ChildUmask);
        LxtLogInfo("After thread creation");
        LxtLogInfo("Original umask %u", UmaskTemp);
        LxtLogInfo("set back to ChildUmask %u", ChildUmask);
        LxtLogInfo("ParentTid %d", ParentTid);
        LxtLogInfo("ForkTid %d", ForkTid);
        LxtLogInfo("Thread1Tid %d", Thread1Tid);
        LxtLogInfo("Thread2Tid %d", Thread2Tid);
        if (Thread1Tid == 0)
        {
            LxtLogError("Thread1Tid == 0 after calling set_tid_address");
        }

        //
        // Do a futex wait on Thread1Tid and validate that it has been set to 0
        // by the kernel. Futex will fail with EAGAIN if the value has already
        // been set.
        //

        Return = my_futex(&Thread1Tid, FUTEX_WAIT, SavedThread1Tid, NULL, NULL, 0);
        if (Return != 0)
        {
            if (errno != EAGAIN)
            {
                LxtLogError("futex returned unexpected error %d - %s", errno, strerror(errno));
            }
        }

        //
        // Don't join thread 1; pthread_join is implemented using the clear
        // tid address, so since it has been changed it won't work.
        //

        pthread_join(Thread2, NULL);

        LxtLogInfo("After Thread join and futex wait");
        LxtLogInfo("ParentTid %d", ParentTid);
        LxtLogInfo("ForkTid %d", ForkTid);
        LxtLogInfo("Thread1Tid %d", Thread1Tid);
        LxtLogInfo("Thread2Tid %d", Thread2Tid);
        if (Thread1Tid != 0)
        {
            LxtLogError("Thread1Tid != 0, was %d", Thread1Tid);
        }

        if (Thread2Tid != LXT_INVALID_TID_VALUE)
        {
            LxtLogError("Thread2Tid != -1, was %d", Thread2Tid);
        }

        _exit(0);
    }

    LxtWaitPidPollOptions(ChildPid, 0, 0, 5);
    LxtLogInfo("Parent after fork");
    LxtLogInfo("ParentTid %d", ParentTid);
    LxtLogInfo("ForkTid %d", ForkTid);
    LxtLogInfo("Thread1Tid %d", Thread1Tid);
    LxtLogInfo("Thread2Tid %d", Thread2Tid);

    Result = LXT_RESULT_SUCCESS;

ErrorExit:
    return Result;
}

int ForkPids(PLXT_ARGS Args)

/*++
--*/

{

    pid_t ChildPid;
    int Result;
    pid_t ParentPid;
    pid_t Pid;
    pid_t WaitPidResult;
    int WaitPidStatus;

    //
    // Basic test for fork and vfork that confirms pids are incremented by 1 to
    // ensure syscalls were plumbed correctly. Additional tests should be added
    // to check the many other cases for fork.
    //

    Pid = getpid();
    if (Pid == 0)
    {
        Result = LXT_RESULT_FAILURE;
        LxtLogError("getpid returned 0 for parent");
        goto ErrorExit;
    }

    //
    // Fork should return parent + 1 (assumes no other processes are running).
    //

    LxtCheckResult(ChildPid = fork());
    if (ChildPid == 0)
    {
        Result = LXT_RESULT_SUCCESS;
        ChildPid = getpid();
        if (ChildPid == 0)
        {
            Result = LXT_RESULT_FAILURE;
            LxtLogError("getpid returned 0 for child");
        }

        ParentPid = getppid();
        if (ParentPid != Pid)
        {
            Result = LXT_RESULT_FAILURE;
            LxtLogError("fork() Unexpected parent pid %d for child pid %d", ParentPid, ChildPid);
        }

        _exit(Result);
    }

    LxtCheckResult((WaitPidResult = waitpid(ChildPid, &WaitPidStatus, 0)));
    if ((WIFEXITED(WaitPidStatus) == 0) || (WEXITSTATUS(WaitPidStatus) != 0))
    {
        Result = LXT_RESULT_FAILURE;
        LxtLogError("Unexpected child pid exit status %d", WaitPidStatus);
        goto ErrorExit;
    }

    LxtCheckResult(ChildPid = vfork());
    if (ChildPid == 0)
    {
        Result = LXT_RESULT_SUCCESS;
        ChildPid = getpid();
        if (ChildPid == 0)
        {
            Result = LXT_RESULT_FAILURE;
            LxtLogError("getpid returned 0 for child");
        }

        ParentPid = getppid();
        if (ParentPid != Pid)
        {
            Result = LXT_RESULT_FAILURE;
            LxtLogError("vfork() Unexpected parent pid %d for child pid %d", ParentPid, ChildPid);
        }

        _exit(Result);
    }

    LxtCheckResult((WaitPidResult = waitpid(ChildPid, &WaitPidStatus, 0)));
    if ((WIFEXITED(WaitPidStatus) == 0) || (WEXITSTATUS(WaitPidStatus) != 0))
    {
        Result = LXT_RESULT_FAILURE;
        LxtLogError("Unexpected child pid exit status %d", WaitPidStatus);
        goto ErrorExit;
    }

    Result = LXT_RESULT_SUCCESS;

ErrorExit:
    return Result;
}

int ExecvFailure(PLXT_ARGS Args)

/*++
--*/

{

    char* CommandLine1[] = {"/foo/bar/foo/bar", NULL};
    char* CommandLine2[] = {"/data/test/Makefile", NULL};
    int Result;

    //
    // Check that execv fails for an invalid file name and an invalid elf file.
    //

    LxtCheckErrnoFailure(execv(CommandLine1[0], CommandLine1), ENOENT);
    LxtCheckErrnoFailure(execv(CommandLine2[0], CommandLine2), ENOEXEC);

    Result = LXT_RESULT_SUCCESS;

ErrorExit:
    return Result;
}

int RobustFutex(PLXT_ARGS Args)

{

    struct robust_list_head Head;
    struct robust_list_head* HeadReturned;
    int Result = LXT_RESULT_FAILURE;
    size_t SizeReturned;

    //
    // Set and get the robust list.
    //

    LxtCheckResult(my_set_robust_list(&Head, sizeof(struct robust_list_head)));
    LxtCheckResult(my_get_robust_list(0, &HeadReturned, &SizeReturned));

    if (HeadReturned != &Head)
    {
        LxtLogError("HeadReturned %p != &Head %p", HeadReturned, &Head);
        goto ErrorExit;
    }

    if (SizeReturned != sizeof(struct robust_list_head))
    {
        LxtLogError("SizeReturned %u != sizeof(struct robust_list_head) %u", SizeReturned, sizeof(struct robust_list_head));

        goto ErrorExit;
    }

    //
    // set_robust_list validates the size of the buffer.
    //

    LxtCheckErrnoFailure(my_set_robust_list(&Head, 0), EINVAL);
    LxtCheckErrnoFailure(my_set_robust_list(&Head, (sizeof(struct robust_list_head) + 1)), EINVAL);

    //
    // get_robust_list validates the buffers.
    //

    LxtCheckErrnoFailure(my_get_robust_list(0, NULL, &SizeReturned), EFAULT);
    LxtCheckErrnoFailure(my_get_robust_list(0, &HeadReturned, NULL), EFAULT);

    //
    // No validation is done on the buffer for set_robust_list.
    //

    LxtCheckResult(my_set_robust_list(NULL, sizeof(struct robust_list_head)));
    LxtCheckResult(my_set_robust_list((void*)-1, sizeof(struct robust_list_head)));

    Result = LXT_RESULT_SUCCESS;

ErrorExit:
    return Result;
}

int CloneFs(PLXT_ARGS Args)

/*++
--*/

{

    char BackupCwd[256];
    pid_t ChildPid;
    char Path[256];
    bool RestoreCwd;
    int Result;

    ChildPid = -1;
    RestoreCwd = false;
    LxtLogInfo("cwd = %s", getcwd(BackupCwd, sizeof(BackupCwd)));
    LxtCheckResult(ChildPid = LxtCloneSyscall((CLONE_FS | SIGCHLD), NULL, NULL, NULL, NULL));
    if (ChildPid == 0)
    {
        LxtCheckErrno(chdir(LXT_TEST_CWD));
        LxtLogInfo("child cwd = %s", getcwd(Path, sizeof(Path)));
        goto ErrorExit;
    }

    //
    // Wait for the child to exit.
    //

    RestoreCwd = true;
    LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));

    //
    // Ensure the parent's current working directory was changed.
    //

    LxtLogInfo("parent cwd = %s", getcwd(Path, sizeof(Path)));
    LxtCheckStringEqual(Path, LXT_TEST_CWD);

ErrorExit:
    if (ChildPid == 0)
    {
        _exit(Result);
    }

    if (RestoreCwd != false)
    {
        chdir(BackupCwd);
    }

    return Result;
}

int CloneInvalidFlags(PLXT_ARGS Args)

/*++
--*/

{

    pid_t ChildPid;
    int Result;

    ChildPid = -1;

    //
    // Check for failure cases.
    //

    LxtCheckErrnoFailure(ChildPid = LxtCloneSyscall(CLONE_SIGHAND, NULL, NULL, NULL, NULL), EINVAL);
    LxtCheckErrnoFailure(ChildPid = LxtCloneSyscall(CLONE_THREAD, NULL, NULL, NULL, NULL), EINVAL);
    LxtCheckErrnoFailure(ChildPid = LxtCloneSyscall((CLONE_FS | CLONE_NEWNS), NULL, NULL, NULL, NULL), EINVAL);

ErrorExit:
    if (ChildPid == 0)
    {
        _exit(Result);
    }

    return Result;
}

struct CloneThreadArgs
{
    long Fs0;
    unsigned long FsBase;
    unsigned long GsBase;
};

int CloneThreadEntry(void* Argument)

{

    struct CloneThreadArgs* Args;
    unsigned long FsBase;
    int Result;

    Args = Argument;
    Result = 0;

    //
    // Make sure TLS values can be read without SIGSEGV.
    //

#if defined(__amd64__)

    __asm("movq %%fs:0, %0" : "=r"(Args->Fs0));
    syscall(SYS_arch_prctl, ARCH_GET_FS, &FsBase);
    Result = (Args->FsBase != FsBase);
    Args->FsBase = FsBase;
    syscall(SYS_arch_prctl, ARCH_GET_GS, &Args->GsBase);

#endif

    syscall(SYS_exit, Result);
    return Result;
}

int CloneThread(PLXT_ARGS Args)

{

    struct CloneThreadArgs ThreadArgs;
    int Flags;
    unsigned long FsBase;
    pid_t Pid;
    int Result;
    char* Stack;
    size_t StackSize;
    int Status;
    pid_t Tid;
    long Tls[1];

    StackSize = 1024 * 1024;
    Stack = malloc(StackSize);
    Tls[0] = (long)&Tls[0];
    Flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;

    //
    // Clone without setting TLS.
    //

    LxtCheckErrno(clone(CloneThreadEntry, Stack + StackSize, Flags, &ThreadArgs, &Tid, NULL, &Tid));
    LxtCheckErrno(LxtJoinThread(&Tid));

#if defined(__amd64__)

    LxtCheckErrno(syscall(SYS_arch_prctl, ARCH_GET_FS, &FsBase));
    LxtCheckEqual(ThreadArgs.FsBase, FsBase, "%lx");

#endif

    //
    // Clone and set TLS.
    //

    ThreadArgs.Fs0 = 0;
    ThreadArgs.FsBase = 0;
    ThreadArgs.GsBase = 0;

#if defined(__amd64__)

    LxtCheckErrno(syscall(SYS_arch_prctl, ARCH_SET_GS, &ThreadArgs));

#endif

    LxtCheckErrno(clone(CloneThreadEntry, Stack + StackSize, Flags | CLONE_SETTLS, &ThreadArgs, &Tid, Tls, &Tid));
    LxtCheckErrno(LxtJoinThread(&Tid));

#if defined(__amd64__)

    LxtCheckEqual(ThreadArgs.Fs0, Tls[0], "%ld");
    LxtCheckEqual(ThreadArgs.FsBase, (unsigned long)&Tls[0], "%lx");

    //
    // Ensure GS base is inherited.
    //

    LxtCheckEqual(ThreadArgs.GsBase, (unsigned long)&ThreadArgs, "%lx");

#endif

    //
    // Disallow invalid TLS values.
    //

    LxtCheckErrnoFailure(clone(CloneThreadEntry, Stack + StackSize, Flags | CLONE_SETTLS, NULL, &Tid, (void*)-1, &Tid), EPERM);
    LxtCheckErrno(LxtJoinThread(&Tid));

    //
    // Set TLS on thread group clone too.
    //

    ThreadArgs.FsBase = (unsigned long)&Tls[0];
    LxtCheckErrno(Pid = clone(CloneThreadEntry, Stack + StackSize, CLONE_SETTLS | SIGCHLD, &ThreadArgs, NULL, Tls, NULL));
    LxtCheckErrno(waitpid(Pid, &Status, 0));
    if (!WIFEXITED(Status) || WEXITSTATUS(Status) != 0)
    {
        LxtLogError("FS check failed: %x", Status);
    }

ErrorExit:
    free(Stack);
    return Result;
}

int VForkTestBasicChild(void* Param)

{

    return 0;
}

int VForkTestBasic(PLXT_ARGS Args)

/*++
--*/

{

    LXT_CLONE_ARGS CloneArgs;
    pid_t ChildPid;
    pid_t Pid;
    int Result;

    ChildPid = -1;

    //
    // Check that vfork runs in a new threadgroup.
    //

    Pid = LxtGetTid();
    LxtCheckErrno(ChildPid = vfork());
    if (ChildPid == 0)
    {
        _exit(LXT_RESULT_SUCCESS);
    }

    LxtCheckNotEqual(Pid, ChildPid, "%d");
    LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));

    //
    // Repeat the above with the clone variant of vfork.
    //

    LxtCheckErrno(ChildPid = LxtCloneSyscall(CLONE_VM | CLONE_VFORK | SIGCHLD, NULL, NULL, NULL, NULL));
    if (ChildPid == 0)
    {
        _exit(LXT_RESULT_SUCCESS);
    }

    LxtCheckNotEqual(Pid, ChildPid, "%d");
    LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
    Result = LXT_RESULT_SUCCESS;

ErrorExit:
    if ((Result < 0) && (ChildPid != 0))
    {
        _exit(Result);
    }

    return Result;
}

int VForkTest(PLXT_ARGS Args)

/*++
--*/

{

    char* ChildCmdLine[] = {WSL_UNIT_TEST_BINARY, Args->Argv[0], "-l", "2", "-v", "32", NULL};

    LxtLogInfo("VForkTest ChildCmdLine: %s %s", ChildCmdLine[0], ChildCmdLine[1]);

    pid_t ChildPid;
    pid_t ChildPidFromChild;
    pid_t ChildPidFromChildNested;
    pid_t ChildPidNested;
    unsigned short ControlWord;
    unsigned short OriginalControlWord;
    char* InvalidCmdLine[] = {"/data/test/Makefile", NULL};
    pid_t Pid;
    int Result;

    ChildPid = -1;
    ChildPidFromChild = -1;
    ChildPidFromChild = -1;
    ChildPidFromChildNested = -1;
    ChildPidNested = -1;
    LxtCheckResult(LxtSignalInitialize());

    //
    // Check that vfork runs in a new threadgroup but in the same address space.
    //

    Pid = LxtGetTid();
    LxtCheckErrno(ChildPid = vfork());
    if (ChildPid == 0)
    {
        ChildPidFromChild = LxtGetTid();
        _exit(LXT_RESULT_SUCCESS);
    }

    LxtCheckNotEqual(Pid, ChildPidFromChild, "%d");
    LxtCheckEqual(ChildPid, ChildPidFromChild, "%d");
    LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));

    //
    // Release the above with execv.
    //

    ChildPidFromChild = -1;
    Pid = LxtGetTid();
    LxtCheckErrno(ChildPid = vfork());
    if (ChildPid == 0)
    {
        ChildPidFromChild = LxtGetTid();
        LxtCheckErrno(execv(ChildCmdLine[0], ChildCmdLine));
        _exit(LXT_RESULT_FAILURE);
    }

    LxtCheckNotEqual(Pid, ChildPidFromChild, "%d");
    LxtCheckEqual(ChildPid, ChildPidFromChild, "%d");
    LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));

    //
    // Repeat the above with execv failure.
    //

    ChildPidFromChild = -1;
    Pid = LxtGetTid();
    LxtCheckErrno(ChildPid = vfork());
    if (ChildPid == 0)
    {
        ChildPidFromChild = LxtGetTid();
        LxtCheckErrnoFailure(execv(InvalidCmdLine[0], InvalidCmdLine), ENOEXEC);
        _exit(LXT_RESULT_SUCCESS);
    }

    LxtCheckNotEqual(Pid, ChildPidFromChild, "%d");
    LxtCheckEqual(ChildPid, ChildPidFromChild, "%d");
    LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));

    //
    // Check that signals sent to the parent after the
    // parent releases the address space.
    //

    LxtCheckResult(LxtSignalSetupHandler(SIGUSR1, 0));
    ChildPidFromChild = -1;
    Pid = LxtGetTid();
    LxtCheckErrno(ChildPid = vfork());
    if (ChildPid == 0)
    {
        ChildPidFromChild = LxtGetTid();
        LxtSignalInitializeThread();
        LxtCheckErrno(LxtTKill(Pid, SIGUSR1));
        LxtCheckResult(LxtSignalCheckNoSignal());
        _exit(LXT_RESULT_SUCCESS);
    }

    LxtCheckNotEqual(Pid, ChildPidFromChild, "%d");
    LxtCheckEqual(ChildPid, ChildPidFromChild, "%d");
    LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
    LxtCheckResult(LxtSignalCheckReceived(SIGUSR1));
    LxtSignalResetReceived();

    //
    // Check the behavior for nested vfork.
    //

    Pid = LxtGetTid();
    LxtCheckErrno(ChildPid = vfork());
    if (ChildPid == 0)
    {
        ChildPidFromChild = LxtGetTid();
        LxtCheckErrno(ChildPidNested = vfork());
        if (ChildPidNested == 0)
        {
            ChildPidFromChildNested = LxtGetTid();
            _exit(LXT_RESULT_SUCCESS);
        }

        LxtCheckNotEqual(ChildPid, ChildPidFromChildNested, "%d");
        LxtCheckEqual(ChildPidNested, ChildPidFromChildNested, "%d");
        LxtCheckResult(LxtWaitPidPoll(ChildPidNested, LXT_RESULT_SUCCESS));
        _exit(LXT_RESULT_SUCCESS);
    }

    LxtCheckNotEqual(Pid, ChildPidFromChild, "%d");
    LxtCheckEqual(ChildPid, ChildPidFromChild, "%d");
    LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));

#if defined(__i386__) || defined(__amd64__)

    //
    // Check that floating point context is preserved across vfork.
    //

    _FPU_GETCW(OriginalControlWord);
    // LX_TODO: Initial control word is not being set correctly
    // LxtCheckEqual(LXT_CONTROL_WORD_DEFAULT, OriginalControlWord, "%hu");
    Pid = LxtGetTid();
    LxtCheckErrno(ChildPid = vfork());
    if (ChildPid == 0)
    {
        ChildPidFromChild = LxtGetTid();
        ControlWord = LXT_CONTROL_WORD_NEW;
        _FPU_SETCW(ControlWord);
        _FPU_GETCW(ControlWord);
        LxtCheckEqual(LXT_CONTROL_WORD_NEW, ControlWord, "%hu");
        _exit(LXT_RESULT_SUCCESS);
    }

    _FPU_GETCW(ControlWord);
    LxtCheckEqual(OriginalControlWord, ControlWord, "%hu");
    LxtCheckNotEqual(Pid, ChildPidFromChild, "%d");
    LxtCheckEqual(ChildPid, ChildPidFromChild, "%d");
    LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));

#endif

    //
    // Check the stack pointer isn't modified across vfork.
    //

    {
        register unsigned long Rsp GET_STACK_POINTER;
        LxtCheckNotEqual(Rsp, 0, "%lu");
        Pid = LxtGetTid();
        alloca(1024);
        LxtCheckErrno(ChildPid = vfork());
        if (ChildPid == 0)
        {
            register unsigned long RspChild GET_STACK_POINTER;
            LxtCheckEqual(Rsp, RspChild, "%lu");
            ChildPidFromChild = LxtGetTid();
            _exit(LXT_RESULT_SUCCESS);
        }

        LxtCheckNotEqual(Pid, ChildPidFromChild, "%d");
        LxtCheckEqual(ChildPid, ChildPidFromChild, "%d");
        LxtCheckResult(LxtWaitPidPoll(ChildPid, LXT_RESULT_SUCCESS));
    }

    Result = LXT_RESULT_SUCCESS;

ErrorExit:
    if (ChildPid == 0)
    {
        _exit(Result);
    }

    return Result;
}

struct CloneFlagArgs
{
    int Test;
    int Flags;
    int Fd;
};

int CloneFlags[] = {
    SIGCHLD,
    SIGCHLD | CLONE_FS,
    SIGCHLD | CLONE_FILES,
    SIGCHLD | CLONE_FS | CLONE_FILES,
    SIGCHLD | CLONE_VFORK,
    SIGUSR1,
    0,
    125, // invalid signal
    CLONE_THREAD | CLONE_VM | CLONE_SIGHAND,
    CLONE_THREAD | CLONE_VM | CLONE_SIGHAND | CLONE_FS,
    CLONE_THREAD | CLONE_VM | CLONE_SIGHAND | CLONE_FILES,
    CLONE_THREAD | CLONE_VM | CLONE_SIGHAND | CLONE_FS | CLONE_FILES,
    CLONE_THREAD | CLONE_VM | CLONE_SIGHAND | CLONE_VFORK,
    SIGCHLD | CLONE_THREAD | CLONE_VM | CLONE_SIGHAND,
    125 | CLONE_THREAD | CLONE_VM | CLONE_SIGHAND,
};

int CloneFlagEntry(void* Arg)

{

    struct CloneFlagArgs* FlagArgs = Arg;
    close(FlagArgs->Fd);
    chdir("/");
    syscall(SYS_exit, 0);
    return 0;
}

int CloneTestFlags(PLXT_ARGS Args)

{

    int Result;
    char __attribute__((aligned(16))) Stack[65536];

    int Root;
    LxtCheckErrno(Root = open(".", O_RDONLY));

    for (int Test = 0; Test < sizeof(CloneFlags) / sizeof(CloneFlags[0]); Test++)
    {
        int Flags = CloneFlags[Test];

        int Fd;
        LxtCheckErrno(Fd = dup(Root));

        int TermSignal = Flags & 0xff;
        if ((Flags & CLONE_THREAD) != 0 || TermSignal < 1 || TermSignal > 64)
        {
            TermSignal = 0;
        }

        if (TermSignal != 0 && TermSignal != SIGCHLD)
        {
            signal(TermSignal, SIG_IGN);
        }

        LxtLogInfo("Test %d: Flags 0x%x", Test, Flags);

        struct CloneFlagArgs FlagArgs = {0};
        FlagArgs.Test = Test;
        FlagArgs.Flags = Flags;
        FlagArgs.Fd = Fd;

        if (Flags & CLONE_THREAD)
        {
            Flags |= CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
        }

        int Pid;
        int Tid;
        LxtCheckErrno(Pid = clone(CloneFlagEntry, Stack + sizeof(Stack), Flags, &FlagArgs, &Tid, NULL, &Tid));

        if (Flags & CLONE_THREAD)
        {
            LxtCheckErrnoFailure(waitpid(Pid, NULL, __WALL), ECHILD);
            LxtCheckErrno(LxtJoinThread(&Tid));
        }
        else if (TermSignal == SIGCHLD)
        {
            LxtCheckErrnoFailure(waitpid(Pid, NULL, __WCLONE), ECHILD);
            LxtCheckErrno(waitpid(Pid, NULL, 0));
        }
        else
        {
            LxtCheckErrnoFailure(waitpid(Pid, NULL, 0), ECHILD);
            LxtCheckErrno(waitpid(Pid, NULL, __WCLONE));
        }

        if (TermSignal != 0)
        {
            signal(TermSignal, SIG_DFL);
        }

        if (Flags & CLONE_FILES)
        {
            // Make sure Fd is not open.
            LxtCheckErrnoFailure(fcntl(Fd, F_GETFL), EBADF);
        }
        else
        {
            // Make sure Fd is still open.
            LxtCheckErrno(fcntl(Fd, F_GETFL));
            close(Fd);
        }

        char Path[1024] = {0};
        LxtCheckErrno(LxtGetcwd(Path, sizeof(Path)));
        if (Flags & CLONE_FS)
        {
            if (strcmp(Path, "/") != 0)
            {
                LxtLogError("Root directory did not change from %s.", Path);
            }
            fchdir(Root);
        }
        else
        {
            if (strcmp(Path, "/") == 0)
            {
                LxtLogError("Root directory changed.");
            }
        }
    }

    Result = 0;

ErrorExit:
    return Result;
}

int CloneTestSignalEntry(void* Arg)
{
    //
    // Make sure the two SIGCHLD signals don't coalesce.
    //

    usleep(500000);
    syscall(SYS_exit, 0);
    return 1;
}

static int CloneTestSignalArrived;

void CloneTestSignalHandler(int Signal, siginfo_t* Info, void* Extra)
{

    CloneTestSignalArrived++;
}

int CloneTestSignalParent(PLXT_ARGS Args)
{

    int Result;

    LxtCheckErrno(LxtSignalInitialize());
    LxtCheckErrno(LxtSignalBlock(SIGCHLD));

    int Pid = 0;
    volatile int ChildPid = 0;
    LxtCheckErrno(Pid = vfork());
    if (Pid == 0)
    {
        char Stack[65536];

        //
        // Even though SIGUSR1 is passed here, SIGCHLD should be the
        // received signal since CLONE_PARENT is also passed
        // (SIGCHLD comes from the vfork call above).
        //

        ChildPid = clone(CloneTestSignalEntry, Stack + sizeof(Stack), SIGUSR1 | CLONE_PARENT, NULL);

        _exit(0);
    }

    LxtCheckErrno(LxtSignalWaitBlocked(SIGCHLD, Pid, 1));
    int Status;
    LxtCheckErrno(waitpid(Pid, &Status, 0));

    LxtCheckErrno(ChildPid);
    LxtCheckErrno(LxtSignalWaitBlocked(SIGCHLD, ChildPid, 1));
    LxtCheckErrno(waitpid(ChildPid, NULL, 0));
    Result = 0;

ErrorExit:
    return Result;
}