/*
 * Unit tests for fiber functions
 *
 * Copyright (c) 2010 André Hentschel
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>

#include <ntstatus.h>
#define WIN32_NO_STATUS
#include <winternl.h>
#include "wine/test.h"
#include <winuser.h>

static LPVOID (WINAPI *pCreateFiber)(SIZE_T,LPFIBER_START_ROUTINE,LPVOID);
static LPVOID (WINAPI *pConvertThreadToFiber)(LPVOID);
static BOOL (WINAPI *pConvertFiberToThread)(void);
static void (WINAPI *pSwitchToFiber)(LPVOID);
static void (WINAPI *pDeleteFiber)(LPVOID);
static LPVOID (WINAPI *pConvertThreadToFiberEx)(LPVOID,DWORD);
static LPVOID (WINAPI *pCreateFiberEx)(SIZE_T,SIZE_T,DWORD,LPFIBER_START_ROUTINE,LPVOID);
static BOOL (WINAPI *pIsThreadAFiber)(void);
static DWORD (WINAPI *pFlsAlloc)(PFLS_CALLBACK_FUNCTION);
static BOOL (WINAPI *pFlsFree)(DWORD);
static PVOID (WINAPI *pFlsGetValue)(DWORD);
static BOOL (WINAPI *pFlsSetValue)(DWORD,PVOID);
static void (WINAPI *pRtlAcquirePebLock)(void);
static void (WINAPI *pRtlReleasePebLock)(void);
static NTSTATUS (WINAPI *pRtlFlsAlloc)(PFLS_CALLBACK_FUNCTION,DWORD*);
static NTSTATUS (WINAPI *pRtlFlsFree)(ULONG);
static NTSTATUS (WINAPI *pRtlFlsSetValue)(ULONG,void *);
static NTSTATUS (WINAPI *pRtlFlsGetValue)(ULONG,void **);
static void (WINAPI *pRtlProcessFlsData)(void *fls_data, ULONG flags);
static void *fibers[3];
static BYTE testparam = 185;
static DWORD fls_index_to_set = FLS_OUT_OF_INDEXES;
static void* fls_value_to_set;

static int fiberCount = 0;
static int cbCount = 0;

static VOID init_funcs(void)
{
    HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
    HMODULE hntdll = GetModuleHandleA("ntdll.dll");

#define X(f) p##f = (void*)GetProcAddress(hKernel32, #f);
    X(CreateFiber);
    X(ConvertThreadToFiber);
    X(ConvertFiberToThread);
    X(SwitchToFiber);
    X(DeleteFiber);
    X(ConvertThreadToFiberEx);
    X(CreateFiberEx);
    X(IsThreadAFiber);
    X(FlsAlloc);
    X(FlsFree);
    X(FlsGetValue);
    X(FlsSetValue);
#undef X

#define X(f) p##f = (void*)GetProcAddress(hntdll, #f);
    X(RtlFlsAlloc);
    X(RtlFlsFree);
    X(RtlFlsSetValue);
    X(RtlFlsGetValue);
    X(RtlProcessFlsData);
    X(RtlAcquirePebLock);
    X(RtlReleasePebLock);
#undef X

}

static VOID WINAPI FiberLocalStorageProc(PVOID lpFlsData)
{
    ok(lpFlsData == fls_value_to_set,
       "FlsData expected not to be changed, value is %p, expected %p\n",
       lpFlsData, fls_value_to_set);
    cbCount++;
}

static VOID WINAPI FiberMainProc(LPVOID lpFiberParameter)
{
    BYTE *tparam = (BYTE *)lpFiberParameter;
    TEB *teb = NtCurrentTeb();

    ok(!teb->FlsSlots, "Got unexpected FlsSlots %p.\n", teb->FlsSlots);

    fiberCount++;
    ok(*tparam == 185, "Parameterdata expected not to be changed\n");
    if (fls_index_to_set != FLS_OUT_OF_INDEXES)
    {
        void* ret;
        BOOL bret;

        SetLastError( 0xdeadbeef );
        ret = pFlsGetValue(fls_index_to_set);
        ok(ret == NULL, "FlsGetValue returned %p, expected NULL\n", ret);
        ok(GetLastError() == ERROR_INVALID_PARAMETER, "Got unexpected error %lu.\n", GetLastError());

        /* Set the FLS value */
        bret = pFlsSetValue(fls_index_to_set, fls_value_to_set);
        ok(bret, "FlsSetValue failed with error %lu\n", GetLastError());

        ok(!!teb->FlsSlots, "Got unexpected FlsSlots %p.\n", teb->FlsSlots);

        /* Verify that FlsGetValue retrieves the value set by FlsSetValue */
        SetLastError( 0xdeadbeef );
        ret = pFlsGetValue(fls_index_to_set);
        ok(ret == fls_value_to_set, "FlsGetValue returned %p, expected %p\n", ret, fls_value_to_set);
        ok(GetLastError() == ERROR_SUCCESS, "FlsGetValue error %lu\n", GetLastError());
    }
    pSwitchToFiber(fibers[0]);
}

static void test_ConvertThreadToFiber(void)
{
    void *ret;

    if (pConvertThreadToFiber)
    {
        ok( !NtCurrentTeb()->HasFiberData, "already a fiber\n" );
        ok( !NtCurrentTeb()->Tib.FiberData || NtCurrentTeb()->Tib.FiberData == (void *)0x1e00,
            "wrong data %p\n", NtCurrentTeb()->Tib.FiberData );
        fibers[0] = pConvertThreadToFiber(&testparam);
        ok(fibers[0] != NULL, "ConvertThreadToFiber failed with error %lu\n", GetLastError());
        ok( NtCurrentTeb()->HasFiberData, "not a fiber\n" );
        ok( NtCurrentTeb()->Tib.FiberData && NtCurrentTeb()->Tib.FiberData != (void *)0x1e00,
            "wrong data %p\n", NtCurrentTeb()->Tib.FiberData );

        SetLastError(0xdeadbeef);
        ret = pConvertThreadToFiber(&testparam);
        ok(!ret, "Got non NULL ret.\n");
        ok(GetLastError() == ERROR_ALREADY_FIBER, "Got unexpected error %lu.\n", GetLastError());
    }
    else
    {
        win_skip( "ConvertThreadToFiber not present\n" );
    }
}

static void test_ConvertThreadToFiberEx(void)
{
    void *ret;

    if (pConvertThreadToFiberEx)
    {
        ok( !NtCurrentTeb()->HasFiberData, "already a fiber\n" );
        fibers[0] = pConvertThreadToFiberEx(&testparam, 0);
        ok(fibers[0] != NULL, "ConvertThreadToFiberEx failed with error %lu\n", GetLastError());
        ok( NtCurrentTeb()->HasFiberData, "not a fiber\n" );
        ok( NtCurrentTeb()->Tib.FiberData && NtCurrentTeb()->Tib.FiberData != (void *)0x1e00,
            "wrong data %p\n", NtCurrentTeb()->Tib.FiberData );

        SetLastError(0xdeadbeef);
        ret = pConvertThreadToFiberEx(&testparam, 0);
        ok(!ret, "Got non NULL ret.\n");
        ok(GetLastError() == ERROR_ALREADY_FIBER, "Got unexpected error %lu.\n", GetLastError());
    }
    else
    {
        win_skip( "ConvertThreadToFiberEx not present\n" );
    }
}

static void test_ConvertFiberToThread(void)
{
    if (pConvertFiberToThread)
    {
        BOOL ret;
        ok( NtCurrentTeb()->HasFiberData, "not a fiber\n" );
        ret = pConvertFiberToThread();
        ok(ret, "ConvertFiberToThread failed with error %lu\n", GetLastError());
        ok( !NtCurrentTeb()->HasFiberData, "still a fiber\n" );
        ok( !NtCurrentTeb()->Tib.FiberData, "wrong data %p\n", NtCurrentTeb()->Tib.FiberData );
        ret = pConvertFiberToThread();
        ok(!ret, "Got non NULL ret.\n");
        ok(GetLastError() == ERROR_ALREADY_THREAD, "Got unexpected error %lu.\n", GetLastError());
    }
    else
    {
        win_skip( "ConvertFiberToThread not present\n" );
    }
}

static void test_FiberHandling(void)
{
    ok( !NtCurrentTeb()->HasFiberData, "already a fiber\n" );
    ok( NtCurrentTeb()->Tib.FiberData == (void *)0x1e00, "wrong data %p\n", NtCurrentTeb()->Tib.FiberData );

    fiberCount = 0;
    fibers[0] = pCreateFiber(0,FiberMainProc,&testparam);
    ok(fibers[0] != NULL, "CreateFiber failed with error %lu\n", GetLastError());
    pDeleteFiber(fibers[0]);

    test_ConvertThreadToFiber();
    test_ConvertFiberToThread();
    if (pConvertThreadToFiberEx)
        test_ConvertThreadToFiberEx();
    else
        test_ConvertThreadToFiber();

    fibers[1] = pCreateFiber(0,FiberMainProc,&testparam);
    ok(fibers[1] != NULL, "CreateFiber failed with error %lu\n", GetLastError());

    pSwitchToFiber(fibers[1]);
    ok(fiberCount == 1, "Wrong fiber count: %d\n", fiberCount);
    pDeleteFiber(fibers[1]);

    if (pCreateFiberEx)
    {
        fibers[1] = pCreateFiberEx(0,0,0,FiberMainProc,&testparam);
        ok(fibers[1] != NULL, "CreateFiberEx failed with error %lu\n", GetLastError());

        pSwitchToFiber(fibers[1]);
        ok(fiberCount == 2, "Wrong fiber count: %d\n", fiberCount);
        pDeleteFiber(fibers[1]);
    }
    else win_skip( "CreateFiberEx not present\n" );

    if (pIsThreadAFiber) ok(pIsThreadAFiber(), "IsThreadAFiber reported FALSE\n");
    test_ConvertFiberToThread();
    if (pIsThreadAFiber) ok(!pIsThreadAFiber(), "IsThreadAFiber reported TRUE\n");
}

#define FLS_TEST_INDEX_COUNT 4096

static unsigned int check_linked_list(const LIST_ENTRY *le, const LIST_ENTRY *search_entry, unsigned int *index_found)
{
    unsigned int count = 0;
    LIST_ENTRY *entry;

    *index_found = ~0;

    for (entry = le->Flink; entry != le; entry = entry->Flink)
    {
        if (entry == search_entry)
        {
            ok(*index_found == ~0, "Duplicate list entry.\n");
            *index_found = count;
        }
        ++count;
    }
    return count;
}

static unsigned int test_fls_callback_call_count;

static void WINAPI test_fls_callback(void *data)
{
    ++test_fls_callback_call_count;
}

static unsigned int test_fls_chunk_size(unsigned int chunk_index)
{
    return 0x10 << chunk_index;
}

static unsigned int test_fls_chunk_index_from_index(unsigned int index, unsigned int *index_in_chunk)
{
    unsigned int chunk_index = 0;

    while (index >= test_fls_chunk_size(chunk_index))
        index -= test_fls_chunk_size(chunk_index++);

    *index_in_chunk = index;
    return chunk_index;
}

static HANDLE test_fiberlocalstorage_peb_locked_event;
static HANDLE test_fiberlocalstorage_done_event;


static DWORD WINAPI test_FiberLocalStorage_thread(void *arg)
{
    pRtlAcquirePebLock();
    SetEvent(test_fiberlocalstorage_peb_locked_event);
    WaitForSingleObject(test_fiberlocalstorage_done_event, INFINITE);
    pRtlReleasePebLock();
    return 0;
}

static void test_FiberLocalStorage(void)
{
    static DWORD fls_indices[FLS_TEST_INDEX_COUNT];
    unsigned int i, j, count, entry_count, index;
    LIST_ENTRY *fls_list_head, saved_entry;
    TEB_FLS_DATA *fls_data, *new_fls_data;
    GLOBAL_FLS_DATA *g_fls_data;
    DWORD fls, fls_2, result;
    TEB *teb = NtCurrentTeb();
    PEB *peb = teb->Peb;
    NTSTATUS status;
    HANDLE hthread;
    ULONG index2;
    SIZE_T size;
    void* val;
    BOOL ret;

    if (!pFlsAlloc || !pFlsSetValue || !pFlsGetValue || !pFlsFree)
    {
        win_skip( "Fiber Local Storage not supported\n" );
        return;
    }

    if (pRtlFlsAlloc)
    {
        if (pRtlFlsGetValue)
        {
            status = pRtlFlsGetValue(0, NULL);
            ok(status == STATUS_INVALID_PARAMETER, "Got unexpected status %#lx.\n", status);
        }
        else
        {
            win_skip("RtlFlsGetValue is not available.\n");
        }

        for (i = 0; i < FLS_TEST_INDEX_COUNT; ++i)
        {
            fls_indices[i] = 0xdeadbeef;
            status = pRtlFlsAlloc(test_fls_callback, &fls_indices[i]);
            ok(!status || status == STATUS_NO_MEMORY, "Got unexpected status %#lx.\n", status);
            if (status)
            {
                ok(fls_indices[i] == 0xdeadbeef, "Got unexpected index %#lx.\n", fls_indices[i]);
                break;
            }
            if (pRtlFlsSetValue)
            {
                status = pRtlFlsSetValue(fls_indices[i], (void *)(ULONG_PTR)(i + 1));
                ok(!status, "Got unexpected status %#lx.\n", status);
            }
        }
        count = i;

        fls_data = teb->FlsSlots;

        /* FLS limits are increased since Win10 18312. */
        ok(count && (count <= 127 || (count > 4000 && count < 4096)), "Got unexpected count %u.\n", count);

        if (!peb->SparePointers[0] /* was FlsCallback */)
        {
            ok(pRtlFlsSetValue && pRtlFlsGetValue, "Missing RtlFlsGetValue / RtlFlsSetValue.\n");

            fls_list_head = fls_data->fls_list_entry.Flink;

            entry_count = check_linked_list(fls_list_head, &fls_data->fls_list_entry, &index);
            ok(entry_count == 1, "Got unexpected count %u.\n", entry_count);
            ok(!index, "Got unexpected index %u.\n", index);

            g_fls_data = CONTAINING_RECORD(fls_list_head, GLOBAL_FLS_DATA, fls_list_head);

            ok(g_fls_data->fls_high_index == 0xfef, "Got unexpected fls_high_index %#lx.\n", g_fls_data->fls_high_index);

            for (i = 0; i < 8; ++i)
            {
                ok(!!g_fls_data->fls_callback_chunks[i], "Got zero fls_callback_chunks[%u].\n", i);
                ok(g_fls_data->fls_callback_chunks[i]->count == test_fls_chunk_size(i),
                        "Got unexpected g_fls_data->fls_callback_chunks[%u]->count %lu.\n",
                        i, g_fls_data->fls_callback_chunks[i]->count);

                size = HeapSize(GetProcessHeap(), 0, g_fls_data->fls_callback_chunks[i]);
                ok(size == sizeof(ULONG_PTR) + sizeof(FLS_CALLBACK) * test_fls_chunk_size(i),
                        "Got unexpected size %p.\n", (void *)size);

                ok(!!fls_data->fls_data_chunks[i], "Got zero fls_data->fls_data_chunks[%u].\n", i);
                ok(!fls_data->fls_data_chunks[i][0], "Got unexpected fls_data->fls_data_chunks[%u][0] %p.\n",
                        i, fls_data->fls_data_chunks[i][0]);
                size = HeapSize(GetProcessHeap(), 0, fls_data->fls_data_chunks[i]);
                ok(size == sizeof(void *) * (test_fls_chunk_size(i) + 1), "Got unexpected size %p.\n", (void *)size);

                if (!i)
                {
                    ok(g_fls_data->fls_callback_chunks[0]->callbacks[0].callback == (void *)~(ULONG_PTR)0,
                            "Got unexpected callback %p.\n",
                            g_fls_data->fls_callback_chunks[0]->callbacks[0].callback);
                }

                for (j = i ? 0 : fls_indices[0]; j < test_fls_chunk_size(i); ++j)
                {
                    ok(!g_fls_data->fls_callback_chunks[i]->callbacks[j].unknown,
                            "Got unexpected unknown %p, i %u, j %u.\n",
                            g_fls_data->fls_callback_chunks[i]->callbacks[j].unknown, i, j);
                    ok(g_fls_data->fls_callback_chunks[i]->callbacks[j].callback == test_fls_callback,
                            "Got unexpected callback %p, i %u, j %u.\n",
                            g_fls_data->fls_callback_chunks[i]->callbacks[j].callback, i, j);
                }
            }
            for (i = 0; i < count; ++i)
            {
                j = test_fls_chunk_index_from_index(fls_indices[i], &index);
                ok(fls_data->fls_data_chunks[j][index + 1] == (void *)(ULONG_PTR)(i + 1),
                        "Got unexpected FLS value %p, i %u, j %u, index %u.\n",
                        fls_data->fls_data_chunks[j][index + 1], i, j, index);
            }
            j = test_fls_chunk_index_from_index(fls_indices[0x10], &index);
            g_fls_data->fls_callback_chunks[j]->callbacks[index].callback = NULL;
            status = pRtlFlsFree(fls_indices[0x10]);
            ok(status == STATUS_INVALID_PARAMETER, "Got unexpected status %#lx.\n", status);

            g_fls_data->fls_callback_chunks[j]->callbacks[index].callback = test_fls_callback;
            test_fls_callback_call_count = 0;
            status = pRtlFlsFree(fls_indices[0x10]);
            ok(!status, "Got unexpected status %#lx.\n", status);
            ok(test_fls_callback_call_count == 1, "Got unexpected callback call count %u.\n",
                    test_fls_callback_call_count);

            ok(!fls_data->fls_data_chunks[j][0], "Got unexpected fls_data->fls_data_chunks[%u][0] %p.\n",
                    j, fls_data->fls_data_chunks[j][0]);
            ok(!g_fls_data->fls_callback_chunks[j]->callbacks[index].callback,
                    "Got unexpected callback %p.\n",
                    g_fls_data->fls_callback_chunks[j]->callbacks[index].callback);

            fls_data->fls_data_chunks[j][index + 1] = (void *)(ULONG_PTR)0x28;
            status = pRtlFlsAlloc(test_fls_callback, &index2);
            ok(!status, "Got unexpected status %#lx.\n", status);
            ok(index2 == fls_indices[0x10], "Got unexpected index %lu.\n", index2);
            ok(fls_data->fls_data_chunks[j][index + 1] == (void *)(ULONG_PTR)0x28, "Got unexpected data %p.\n",
                    fls_data->fls_data_chunks[j][index + 1]);

            status = pRtlFlsSetValue(index2, (void *)(ULONG_PTR)0x11);
            ok(!status, "Got unexpected status %#lx.\n", status);

            teb->FlsSlots = NULL;

            val = (void *)0xdeadbeef;
            status = pRtlFlsGetValue(fls_indices[1], &val);
            new_fls_data = teb->FlsSlots;
            ok(status == STATUS_INVALID_PARAMETER, "Got unexpected status %#lx.\n", status);
            ok(val == (void *)0xdeadbeef, "Got unexpected val %p.\n", val);
            ok(!new_fls_data, "Got unexpected teb->FlsSlots %p.\n", new_fls_data);

            status = pRtlFlsSetValue(fls_indices[1], (void *)(ULONG_PTR)0x28);
            new_fls_data = teb->FlsSlots;
            ok(!status, "Got unexpected status %#lx.\n", status);
            ok(!!new_fls_data, "Got unexpected teb->FlsSlots %p.\n", new_fls_data);

            entry_count = check_linked_list(fls_list_head, &fls_data->fls_list_entry, &index);
            ok(entry_count == 2, "Got unexpected count %u.\n", entry_count);
            ok(!index, "Got unexpected index %u.\n", index);
            check_linked_list(fls_list_head, &new_fls_data->fls_list_entry, &index);
            ok(index == 1, "Got unexpected index %u.\n", index);

            val = (void *)0xdeadbeef;
            status = pRtlFlsGetValue(fls_indices[2], &val);
            ok(!status, "Got unexpected status %#lx.\n", status);
            ok(!val, "Got unexpected val %p.\n", val);


            /* With bit 0 of flags set RtlProcessFlsData is removing FLS data from the linked list
             * and calls FLS callbacks. With bit 1 set the memory is freed. The remaining bits do not seem
             * to have any obvious effect. */
            for (i = 2; i < 32; ++i)
            {
                pRtlProcessFlsData(new_fls_data, 1 << i);
                size = HeapSize(GetProcessHeap(), 0, new_fls_data);
                ok(size == sizeof(*new_fls_data), "Got unexpected size %p.\n", (void *)size);
            }

            if (0)
            {
                pRtlProcessFlsData(new_fls_data, 2);
                entry_count = check_linked_list(fls_list_head, &fls_data->fls_list_entry, &index);
                ok(entry_count == 2, "Got unexpected count %u.\n", entry_count);

                /* Crashes on Windows. */
                HeapSize(GetProcessHeap(), 0, new_fls_data);
            }

            test_fiberlocalstorage_peb_locked_event = CreateEventA(NULL, FALSE, FALSE, NULL);
            test_fiberlocalstorage_done_event = CreateEventA(NULL, FALSE, FALSE, NULL);
            hthread = CreateThread(NULL, 0, test_FiberLocalStorage_thread, NULL, 0, NULL);
            ok(!!hthread, "CreateThread failed.\n");
            result = WaitForSingleObject(test_fiberlocalstorage_peb_locked_event, INFINITE);
            ok(result == WAIT_OBJECT_0, "Got unexpected result %lu.\n", result);
            teb->FlsSlots = NULL;

            test_fls_callback_call_count = 0;
            saved_entry = new_fls_data->fls_list_entry;
            pRtlProcessFlsData(new_fls_data, 1);
            ok(!teb->FlsSlots, "Got unexpected teb->FlsSlots %p.\n", teb->FlsSlots);

            teb->FlsSlots = fls_data;
            ok(test_fls_callback_call_count == 1, "Got unexpected callback call count %u.\n",
                    test_fls_callback_call_count);

            SetEvent(test_fiberlocalstorage_done_event);
            WaitForSingleObject(hthread, INFINITE);
            CloseHandle(hthread);
            CloseHandle(test_fiberlocalstorage_peb_locked_event);
            CloseHandle(test_fiberlocalstorage_done_event);

            ok(new_fls_data->fls_list_entry.Flink == saved_entry.Flink, "Got unexpected Flink %p.\n",
                    saved_entry.Flink);
            ok(new_fls_data->fls_list_entry.Blink == saved_entry.Blink, "Got unexpected Flink %p.\n",
                    saved_entry.Blink);
            size = HeapSize(GetProcessHeap(), 0, new_fls_data);
            ok(size == sizeof(*new_fls_data), "Got unexpected size %p.\n", (void *)size);
            test_fls_callback_call_count = 0;
            i = test_fls_chunk_index_from_index(fls_indices[1], &index);
            new_fls_data->fls_data_chunks[i][index + 1] = (void *)(ULONG_PTR)0x28;
            pRtlProcessFlsData(new_fls_data, 2);
            ok(!test_fls_callback_call_count, "Got unexpected callback call count %u.\n",
                    test_fls_callback_call_count);

            if (0)
            {
                /* crashes on Windows. */
                HeapSize(GetProcessHeap(), 0, new_fls_data);
            }

            entry_count = check_linked_list(fls_list_head, &fls_data->fls_list_entry, &index);
            ok(entry_count == 1, "Got unexpected count %u.\n", entry_count);
            ok(!index, "Got unexpected index %u.\n", index);
        }
        else
        {
            win_skip("Old FLS data storage layout, skipping test.\n");
            g_fls_data = NULL;
        }

        if (0)
        {
            /* crashes on Windows. */
            pRtlFlsGetValue(fls_indices[0], NULL);
        }

        for (i = 0; i < count; ++i)
        {
            if (pRtlFlsGetValue)
            {
                status = pRtlFlsGetValue(fls_indices[i], &val);
                ok(!status, "Got unexpected status %#lx.\n", status);
                ok(val == (void *)(ULONG_PTR)(i + 1), "Got unexpected val %p, i %u.\n", val, i);
            }

            status = pRtlFlsFree(fls_indices[i]);
            ok(!status, "Got unexpected status %#lx, i %u.\n", status, i);
        }

        if (g_fls_data)
        {
            ok(g_fls_data->fls_high_index == 0xfef, "Got unexpected fls_high_index %#lx.\n",
                    g_fls_data->fls_high_index);

            for (i = 0; i < 8; ++i)
            {
                ok(!!g_fls_data->fls_callback_chunks[i], "Got zero fls_callback_chunks[%u].\n", i);
                ok(!!fls_data->fls_data_chunks[i], "Got zero fls_data->fls_data_chunks[%u].\n", i);
            }
        }
    }
    else
    {
        win_skip("RtlFlsAlloc is not available.\n");
    }

    /* Test an unallocated index
     * FlsFree should fail
     * FlsGetValue and FlsSetValue should succeed
     */
    SetLastError( 0xdeadbeef );
    ret = pFlsFree( 127 );
    ok( !ret, "freeing fls index 127 (unallocated) succeeded\n" );
    ok( GetLastError() == ERROR_INVALID_PARAMETER,
        "freeing fls index 127 (unallocated) wrong error %lu\n", GetLastError() );

    val = pFlsGetValue( 127 );
    ok( val == NULL,
        "getting fls index 127 (unallocated) failed with error %lu\n", GetLastError() );

    if (pRtlFlsGetValue)
    {
        val = (void *)0xdeadbeef;
        status = pRtlFlsGetValue(127, &val);
        ok( !status, "Got unexpected status %#lx.\n", status );
        ok( !val, "Got unexpected val %p.\n", val );
    }

    ret = pFlsSetValue( 127, (void*) 0x217 );
    ok( ret, "setting fls index 127 (unallocated) failed with error %lu\n", GetLastError() );

    SetLastError( 0xdeadbeef );
    val = pFlsGetValue( 127 );
    ok( val == (void*) 0x217, "fls index 127 (unallocated) wrong value %p\n", val );
    ok( GetLastError() == ERROR_SUCCESS,
        "getting fls index 127 (unallocated) failed with error %lu\n", GetLastError() );

    if (pRtlFlsGetValue)
    {
        val = (void *)0xdeadbeef;
        status = pRtlFlsGetValue(127, &val);
        ok( !status, "Got unexpected status %#lx.\n", status );
        ok( val == (void*)0x217, "Got unexpected val %p.\n", val );
    }

    /* FlsFree, FlsGetValue, and FlsSetValue out of bounds should return
     * ERROR_INVALID_PARAMETER
     */
    SetLastError( 0xdeadbeef );
    ret = pFlsFree( 128 );
    ok( !ret, "freeing fls index 128 (out of bounds) succeeded\n" );
    ok( GetLastError() == ERROR_INVALID_PARAMETER,
        "freeing fls index 128 (out of bounds) wrong error %lu\n", GetLastError() );

    SetLastError( 0xdeadbeef );
    ret = pFlsSetValue( 128, (void*) 0x217 );
    ok( ret || GetLastError() == ERROR_INVALID_PARAMETER,
        "setting fls index 128 (out of bounds) wrong error %lu\n", GetLastError() );

    SetLastError( 0xdeadbeef );
    val = pFlsGetValue( 128 );
    ok( GetLastError() == ERROR_INVALID_PARAMETER || val == (void *)0x217,
        "getting fls index 128 (out of bounds) wrong error %lu\n", GetLastError() );

    /* Test index 0 */
    SetLastError( 0xdeadbeef );
    val = pFlsGetValue( 0 );
    ok( !val, "fls index 0 set to %p\n", val );
    ok( GetLastError() == ERROR_INVALID_PARAMETER, "setting fls index wrong error %lu\n", GetLastError() );
    if (pRtlFlsGetValue)
    {
        val = (void *)0xdeadbeef;
        status = pRtlFlsGetValue(0, &val);
        ok( status == STATUS_INVALID_PARAMETER, "Got unexpected status %#lx.\n", status );
        ok( val == (void*)0xdeadbeef, "Got unexpected val %p.\n", val );
    }

    SetLastError( 0xdeadbeef );
    ret = pFlsSetValue( 0, (void *)0xdeadbeef );
    ok( !ret, "setting fls index 0 succeeded\n" );
    ok( GetLastError() == ERROR_INVALID_PARAMETER, "setting fls index wrong error %lu\n", GetLastError() );
    if (pRtlFlsSetValue)
    {
        status = pRtlFlsSetValue( 0, (void *)0xdeadbeef );
        ok( status == STATUS_INVALID_PARAMETER, "Got unexpected status %#lx.\n", status );
    }
    SetLastError( 0xdeadbeef );
    val = pFlsGetValue( 0 );
    ok( !val, "fls index 0 wrong value %p\n", val );
    ok( GetLastError() == ERROR_INVALID_PARAMETER, "setting fls index wrong error %lu\n", GetLastError() );

    /* Test creating an FLS index */
    fls = pFlsAlloc( NULL );
    ok( fls != FLS_OUT_OF_INDEXES, "FlsAlloc failed\n" );
    ok( fls != 0, "fls index 0 allocated\n" );
    val = pFlsGetValue( fls );
    ok( !val, "fls index %lu wrong value %p\n", fls, val );
    SetLastError( 0xdeadbeef );
    ret = pFlsSetValue( fls, (void *)0xdeadbeef );
    ok( ret, "setting fls index %lu failed\n", fls );
    ok( GetLastError() == 0xdeadbeef, "setting fls index wrong error %lu\n", GetLastError() );
    SetLastError( 0xdeadbeef );
    val = pFlsGetValue( fls );
    ok( val == (void *)0xdeadbeef, "fls index %lu wrong value %p\n", fls, val );
    ok( GetLastError() == ERROR_SUCCESS,
        "getting fls index %lu failed with error %lu\n", fls, GetLastError() );
    pFlsFree( fls );

    /* Undefined behavior: verify the value is NULL after it the slot is freed */
    SetLastError( 0xdeadbeef );
    val = pFlsGetValue( fls );
    ok( val == NULL, "fls index %lu wrong value %p\n", fls, val );
    ok( GetLastError() == ERROR_SUCCESS,
        "getting fls index %lu failed with error %lu\n", fls, GetLastError() );

    /* Undefined behavior: verify the value is settable after the slot is freed */
    ret = pFlsSetValue( fls, (void *)0xdeadbabe );
    ok( ret, "setting fls index %lu failed\n", fls );
    val = pFlsGetValue( fls );
    ok( val == (void *)0xdeadbabe, "fls index %lu wrong value %p\n", fls, val );

    /* Try to create the same FLS index again, and verify that is initialized to NULL */
    fls_2 = pFlsAlloc( NULL );
    ok( fls != FLS_OUT_OF_INDEXES, "FlsAlloc failed with error %lu\n", GetLastError() );
    /* If this fails it is not an API error, but the test will be inconclusive */
    ok( fls_2 == fls, "different FLS index allocated, was %lu, now %lu\n", fls, fls_2 );

    SetLastError( 0xdeadbeef );
    val = pFlsGetValue( fls_2 );
    ok( val == NULL || val == (void *)0xdeadbabe, "fls index %lu wrong value %p\n", fls, val );
    ok( GetLastError() == ERROR_SUCCESS,
        "getting fls index %lu failed with error %lu\n", fls_2, GetLastError() );
    pFlsFree( fls_2 );
}

static void test_FiberLocalStorageCallback(PFLS_CALLBACK_FUNCTION cbfunc)
{
    DWORD fls;
    BOOL ret;
    void* val, *val2;

    if (!pFlsAlloc || !pFlsSetValue || !pFlsGetValue || !pFlsFree)
    {
        win_skip( "Fiber Local Storage not supported\n" );
        return;
    }

    /* Test that the callback is executed */
    cbCount = 0;
    fls = pFlsAlloc( cbfunc );
    ok( fls != FLS_OUT_OF_INDEXES, "FlsAlloc failed with error %lu\n", GetLastError() );

    val = (void*) 0x1587;
    fls_value_to_set = val;
    ret = pFlsSetValue( fls, val );
    ok(ret, "FlsSetValue failed with error %lu\n", GetLastError() );

    val2 = pFlsGetValue( fls );
    ok(val == val2, "FlsGetValue returned %p, expected %p\n", val2, val);

    ret = pFlsFree( fls );
    ok(ret, "FlsFree failed with error %lu\n", GetLastError() );
    ok( cbCount == 1, "Wrong callback count: %d\n", cbCount );

    /* Test that callback is not executed if value is NULL */
    cbCount = 0;
    fls = pFlsAlloc( cbfunc );
    ok( fls != FLS_OUT_OF_INDEXES, "FlsAlloc failed with error %lu\n", GetLastError() );

    ret = pFlsSetValue( fls, NULL );
    ok( ret, "FlsSetValue failed with error %lu\n", GetLastError() );

    pFlsFree( fls );
    ok( ret, "FlsFree failed with error %lu\n", GetLastError() );
    ok( cbCount == 0, "Wrong callback count: %d\n", cbCount );
}

static void test_FiberLocalStorageWithFibers(PFLS_CALLBACK_FUNCTION cbfunc)
{
    void* val1 = (void*) 0x314;
    void* val2 = (void*) 0x152;
    BOOL ret;

    if (!pFlsAlloc || !pFlsFree || !pFlsSetValue || !pFlsGetValue)
    {
        win_skip( "Fiber Local Storage not supported\n" );
        return;
    }

    fls_index_to_set = pFlsAlloc(cbfunc);
    ok(fls_index_to_set != FLS_OUT_OF_INDEXES, "FlsAlloc failed with error %lu\n", GetLastError());

    test_ConvertThreadToFiber();

    fiberCount = 0;
    cbCount = 0;
    fibers[1] = pCreateFiber(0,FiberMainProc,&testparam);
    fibers[2] = pCreateFiber(0,FiberMainProc,&testparam);
    ok(fibers[1] != NULL, "CreateFiber failed with error %lu\n", GetLastError());
    ok(fibers[2] != NULL, "CreateFiber failed with error %lu\n", GetLastError());
    ok(fiberCount == 0, "Wrong fiber count: %d\n", fiberCount);
    ok(cbCount == 0, "Wrong callback count: %d\n", cbCount);

    fiberCount = 0;
    cbCount = 0;
    fls_value_to_set = val1;
    pSwitchToFiber(fibers[1]);
    ok(fiberCount == 1, "Wrong fiber count: %d\n", fiberCount);
    ok(cbCount == 0, "Wrong callback count: %d\n", cbCount);

    fiberCount = 0;
    cbCount = 0;
    fls_value_to_set = val2;
    pSwitchToFiber(fibers[2]);
    ok(fiberCount == 1, "Wrong fiber count: %d\n", fiberCount);
    ok(cbCount == 0, "Wrong callback count: %d\n", cbCount);

    fls_value_to_set = val2;
    ret = pFlsSetValue(fls_index_to_set, fls_value_to_set);
    ok(ret, "FlsSetValue failed\n");
    ok(val2 == pFlsGetValue(fls_index_to_set), "FlsGetValue failed\n");

    fiberCount = 0;
    cbCount = 0;
    fls_value_to_set = val1;
    pDeleteFiber(fibers[1]);
    ok(fiberCount == 0, "Wrong fiber count: %d\n", fiberCount);
    ok(cbCount == 1, "Wrong callback count: %d\n", cbCount);

    fiberCount = 0;
    cbCount = 0;
    fls_value_to_set = val2;
    pFlsFree(fls_index_to_set);
    ok(fiberCount == 0, "Wrong fiber count: %d\n", fiberCount);
    ok(cbCount == 2, "Wrong callback count: %d\n", cbCount);

    fiberCount = 0;
    cbCount = 0;
    fls_value_to_set = val1;
    pDeleteFiber(fibers[2]);
    ok(fiberCount == 0, "Wrong fiber count: %d\n", fiberCount);
    ok(cbCount == 0, "Wrong callback count: %d\n", cbCount);

    test_ConvertFiberToThread();
}

#define check_current_actctx_is(e,t) check_current_actctx_is_(__LINE__, e, t)
static void check_current_actctx_is_(int line, HANDLE expected_actctx, BOOL todo)
{
    HANDLE cur_actctx;
    BOOL ret;

    cur_actctx = (void*)0xdeadbeef;
    ret = GetCurrentActCtx(&cur_actctx);
    ok_(__FILE__, line)(ret, "thread GetCurrentActCtx failed, %lu\n", GetLastError());

    todo_wine_if(todo)
    ok_(__FILE__, line)(cur_actctx == expected_actctx, "got %p, expected %p\n", cur_actctx, expected_actctx);

    ReleaseActCtx(cur_actctx);
}

static DWORD WINAPI subthread_actctx_func(void *actctx)
{
    HANDLE fiber;
    BOOL ret;

    check_current_actctx_is(actctx, FALSE);

    fiber = pConvertThreadToFiber(NULL);
    ok(fiber != NULL, "ConvertThreadToFiber returned error %lu\n", GetLastError());
    check_current_actctx_is(actctx, FALSE);
    fibers[2] = fiber;

    SwitchToFiber(fibers[0]);
    check_current_actctx_is(actctx, FALSE);

    ok(fibers[2] == fiber, "fibers[2]: expected %p, got %p\n", fiber, fibers[2]);
    fibers[2] = NULL;
    ret = pConvertFiberToThread();
    ok(ret, "ConvertFiberToThread returned error %lu\n", GetLastError());
    check_current_actctx_is(actctx, FALSE);

    return 0;
}

static void WINAPI fiber_actctx_func(void *actctx)
{
    ULONG_PTR cookie;
    DWORD tid, wait;
    HANDLE thread;
    BOOL ret;

    check_current_actctx_is(NULL, FALSE);

    ret = ActivateActCtx(actctx, &cookie);
    ok(ret, "ActivateActCtx returned error %lu\n", GetLastError());
    check_current_actctx_is(actctx, FALSE);

    SwitchToFiber(fibers[0]);
    check_current_actctx_is(actctx, FALSE);

    thread = CreateThread(NULL, 0, subthread_actctx_func, actctx, 0, &tid);
    ok(thread != NULL, "CreateThread returned error %lu\n", GetLastError());

    wait = WaitForSingleObject(thread, INFINITE);
    ok(wait == WAIT_OBJECT_0, "WaitForSingleObject returned %lu (last error: %lu)\n",
       wait, GetLastError());
    CloseHandle(thread);

    ret = DeactivateActCtx(0, cookie);
    ok(ret, "DeactivateActCtx returned error %lu\n", GetLastError());
    check_current_actctx_is(NULL, FALSE);

    SwitchToFiber(fibers[0]);
    ok(0, "unreachable\n");
}

/* Test that activation context is switched on SwitchToFiber() call */
static void subtest_fiber_actctx_switch(HANDLE current_actctx, HANDLE child_actctx)
{
    fibers[1] = pCreateFiber(0, fiber_actctx_func, child_actctx);
    ok(fibers[1] != NULL, "CreateFiber returned error %lu\n", GetLastError());
    check_current_actctx_is(current_actctx, FALSE);

    SwitchToFiber(fibers[1]);
    check_current_actctx_is(current_actctx, FALSE);

    SwitchToFiber(fibers[1]);
    check_current_actctx_is(current_actctx, FALSE);

    SwitchToFiber(fibers[2]);
    check_current_actctx_is(current_actctx, FALSE);
    ok(fibers[2] == NULL, "expected fiber to be deleted (got %p)\n", fibers[2]);

    DeleteFiber(fibers[1]);
    fibers[1] = NULL;
}

static void WINAPI exit_thread_fiber_func(void *unused)
{
    BOOL ret;

    ret = pConvertFiberToThread();
    ok(ret, "ConvertFiberToThread returned error %lu\n", GetLastError());

    ExitThread(16);
}

static DWORD WINAPI thread_actctx_func_early_exit(void *actctx)
{
    HANDLE exit_thread_fiber;

    check_current_actctx_is(actctx, FALSE);

    fibers[1] = pConvertThreadToFiber(NULL);
    ok(fibers[1] != NULL, "ConvertThreadToFiber returned error %lu\n", GetLastError());
    check_current_actctx_is(actctx, FALSE);

    exit_thread_fiber = pCreateFiber(0, exit_thread_fiber_func, NULL);
    ok(exit_thread_fiber != NULL, "CreateFiber returned error %lu\n", GetLastError());

    /* exit thread, but keep current fiber */
    SwitchToFiber(exit_thread_fiber);
    check_current_actctx_is(actctx, FALSE);

    SwitchToFiber(fibers[0]);
    ok(0, "unreachable\n");
    return 17;
}

/* Test that fiber activation context stack is preserved regardless of creator thread's lifetime */
static void subtest_fiber_actctx_preservation(HANDLE current_actctx, HANDLE child_actctx)
{
    ULONG_PTR cookie;
    DWORD tid, wait;
    HANDLE thread;
    BOOL ret;

    ret = ActivateActCtx(child_actctx, &cookie);
    ok(ret, "ActivateActCtx returned error %lu\n", GetLastError());
    check_current_actctx_is(child_actctx, FALSE);

    thread = CreateThread(NULL, 0, thread_actctx_func_early_exit, child_actctx, 0, &tid);
    ok(thread != NULL, "CreateThread returned error %lu\n", GetLastError());

    ret = DeactivateActCtx(0, cookie);
    ok(ret, "DeactivateActCtx returned error %lu\n", GetLastError());
    check_current_actctx_is(current_actctx, FALSE);

    wait = WaitForSingleObject(thread, INFINITE);
    ok(wait == WAIT_OBJECT_0, "WaitForSingleObject returned %lu (last error: %lu)\n",
       wait, GetLastError());
    CloseHandle(thread);

    /* The exited thread has been converted to a fiber */
    SwitchToFiber(fibers[1]);
    check_current_actctx_is(current_actctx, FALSE);

    DeleteFiber(fibers[1]);
    fibers[1] = NULL;
}

static HANDLE create_actctx_from_module_manifest(void)
{
    ACTCTXW actctx;

    memset(&actctx, 0, sizeof(ACTCTXW));
    actctx.cbSize = sizeof(actctx);
    actctx.dwFlags = ACTCTX_FLAG_HMODULE_VALID | ACTCTX_FLAG_RESOURCE_NAME_VALID;
    actctx.lpResourceName = MAKEINTRESOURCEW(124);
    actctx.hModule = GetModuleHandleW(NULL);

    return CreateActCtxW(&actctx);
}

static void test_fiber_actctx(void)
{
    ULONG_PTR cookies[3];
    HANDLE actctxs[3];
    size_t i, j;
    BOOL ret;

    for (i = 0; i < ARRAY_SIZE(actctxs); i++)
    {
        actctxs[i] = create_actctx_from_module_manifest();
        ok(actctxs[i] != INVALID_HANDLE_VALUE, "failed to create context, error %lu\n", GetLastError());
        for (j = 0; j < i; j++)
        {
            ok(actctxs[i] != actctxs[j],
               "actctxs[%Iu] (%p) and actctxs[%Iu] (%p) should not alias\n",
               i, actctxs[i], j, actctxs[j]);
        }
    }

    ret = ActivateActCtx(actctxs[0], &cookies[0]);
    ok(ret, "ActivateActCtx returned error %lu\n", GetLastError());
    check_current_actctx_is(actctxs[0], FALSE);

    test_ConvertThreadToFiber();
    check_current_actctx_is(actctxs[0], FALSE);

    ret = ActivateActCtx(actctxs[1], &cookies[1]);
    ok(ret, "ActivateActCtx returned error %lu\n", GetLastError());
    check_current_actctx_is(actctxs[1], FALSE);

    subtest_fiber_actctx_switch(actctxs[1], actctxs[2]);
    subtest_fiber_actctx_preservation(actctxs[1], actctxs[2]);

    ret = DeactivateActCtx(0, cookies[1]);
    ok(ret, "DeactivateActCtx returned error %lu\n", GetLastError());
    check_current_actctx_is(actctxs[0], FALSE);

    test_ConvertFiberToThread();
    check_current_actctx_is(actctxs[0], FALSE);

    ret = DeactivateActCtx(0, cookies[0]);
    ok(ret, "DeactivateActCtx returned error %lu\n", GetLastError());
    check_current_actctx_is(NULL, FALSE);

    for (i = ARRAY_SIZE(actctxs); i > 0; )
        ReleaseActCtx(actctxs[--i]);
}


static void WINAPI fls_exit_deadlock_callback(void *arg)
{
    if (arg == (void *)1)
        Sleep(INFINITE);
    if (arg == (void *)2)
        /* Unfortunately this test won't affect the exit code if it fails, but
         * at least it will print a failure message. */
        ok(RtlDllShutdownInProgress(), "expected DLL shutdown\n");
}

static DWORD CALLBACK fls_exit_deadlock_thread(void *arg)
{
    FlsSetValue((DWORD_PTR)arg, (void *)1);
    return 0;
}

static void fls_exit_deadlock_child(void)
{
    DWORD index = FlsAlloc(fls_exit_deadlock_callback);
    FlsSetValue(index, (void *)2);
    CreateThread(NULL, 0, fls_exit_deadlock_thread, (void *)(DWORD_PTR)index, 0, NULL);
    Sleep(100);
    ExitProcess(0);
}

static void test_fls_exit_deadlock(void)
{
    char **argv, cmdline[MAX_PATH];
    PROCESS_INFORMATION pi;
    STARTUPINFOA si = {0};
    BOOL ret;

    /* Regression test for the following deadlock:
     *
     * Thread A             Thread B
     * -----------------------------
     * ExitThread
     * acquire FLS lock
     * call FLS callback
     *                      ExitProcess
     *                      terminate thread A
     *                      acquire FLS lock
     *
     * Thread B deadlocks on acquiring the FLS lock (in order to itself call its
     * FLS callbacks.)
     */

    winetest_get_mainargs(&argv);
    sprintf(cmdline, "%s %s fls_exit_deadlock", argv[0], argv[1]);
    ret = CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
    ok(ret, "failed to create child, error %lu\n", GetLastError());
    ret = WaitForSingleObject(pi.hProcess, 1000);
    ok(!ret, "wait failed\n");
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
}

START_TEST(fiber)
{
    char **argv;
    int argc;

    argc = winetest_get_mainargs(&argv);

    if (argc == 3 && !strcmp(argv[2], "fls_exit_deadlock"))
    {
        fls_exit_deadlock_child();
        return;
    }

    init_funcs();

    if (!pCreateFiber)
    {
        win_skip( "Fibers not supported by win95\n" );
        return;
    }

    test_FiberHandling();
    test_FiberLocalStorage();
    test_FiberLocalStorageCallback(FiberLocalStorageProc);
    test_FiberLocalStorageWithFibers(FiberLocalStorageProc);
    test_fiber_actctx();
    test_fls_exit_deadlock();
}
