Reactos
1/*
2 * Unit tests for fiber functions
3 *
4 * Copyright (c) 2010 André Hentschel
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21#include <stdarg.h>
22
23#include <ntstatus.h>
24#define WIN32_NO_STATUS
25#include <winternl.h>
26#include "wine/test.h"
27#include <winuser.h>
28
29static LPVOID (WINAPI *pCreateFiber)(SIZE_T,LPFIBER_START_ROUTINE,LPVOID);
30static LPVOID (WINAPI *pConvertThreadToFiber)(LPVOID);
31static BOOL (WINAPI *pConvertFiberToThread)(void);
32static void (WINAPI *pSwitchToFiber)(LPVOID);
33static void (WINAPI *pDeleteFiber)(LPVOID);
34static LPVOID (WINAPI *pConvertThreadToFiberEx)(LPVOID,DWORD);
35static LPVOID (WINAPI *pCreateFiberEx)(SIZE_T,SIZE_T,DWORD,LPFIBER_START_ROUTINE,LPVOID);
36static BOOL (WINAPI *pIsThreadAFiber)(void);
37static DWORD (WINAPI *pFlsAlloc)(PFLS_CALLBACK_FUNCTION);
38static BOOL (WINAPI *pFlsFree)(DWORD);
39static PVOID (WINAPI *pFlsGetValue)(DWORD);
40static BOOL (WINAPI *pFlsSetValue)(DWORD,PVOID);
41static void (WINAPI *pRtlAcquirePebLock)(void);
42static void (WINAPI *pRtlReleasePebLock)(void);
43static NTSTATUS (WINAPI *pRtlFlsAlloc)(PFLS_CALLBACK_FUNCTION,DWORD*);
44static NTSTATUS (WINAPI *pRtlFlsFree)(ULONG);
45static NTSTATUS (WINAPI *pRtlFlsSetValue)(ULONG,void *);
46static NTSTATUS (WINAPI *pRtlFlsGetValue)(ULONG,void **);
47static void (WINAPI *pRtlProcessFlsData)(void *fls_data, ULONG flags);
48static void *fibers[3];
49static BYTE testparam = 185;
50static DWORD fls_index_to_set = FLS_OUT_OF_INDEXES;
51static void* fls_value_to_set;
52
53static int fiberCount = 0;
54static int cbCount = 0;
55
56static VOID init_funcs(void)
57{
58 HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
59 HMODULE hntdll = GetModuleHandleA("ntdll.dll");
60
61#define X(f) p##f = (void*)GetProcAddress(hKernel32, #f);
62 X(CreateFiber);
63 X(ConvertThreadToFiber);
64 X(ConvertFiberToThread);
65 X(SwitchToFiber);
66 X(DeleteFiber);
67 X(ConvertThreadToFiberEx);
68 X(CreateFiberEx);
69 X(IsThreadAFiber);
70 X(FlsAlloc);
71 X(FlsFree);
72 X(FlsGetValue);
73 X(FlsSetValue);
74#undef X
75
76#define X(f) p##f = (void*)GetProcAddress(hntdll, #f);
77 X(RtlFlsAlloc);
78 X(RtlFlsFree);
79 X(RtlFlsSetValue);
80 X(RtlFlsGetValue);
81 X(RtlProcessFlsData);
82 X(RtlAcquirePebLock);
83 X(RtlReleasePebLock);
84#undef X
85
86}
87
88static VOID WINAPI FiberLocalStorageProc(PVOID lpFlsData)
89{
90 ok(lpFlsData == fls_value_to_set,
91 "FlsData expected not to be changed, value is %p, expected %p\n",
92 lpFlsData, fls_value_to_set);
93 cbCount++;
94}
95
96static VOID WINAPI FiberMainProc(LPVOID lpFiberParameter)
97{
98 BYTE *tparam = (BYTE *)lpFiberParameter;
99 TEB *teb = NtCurrentTeb();
100
101 ok(!teb->FlsSlots, "Got unexpected FlsSlots %p.\n", teb->FlsSlots);
102
103 fiberCount++;
104 ok(*tparam == 185, "Parameterdata expected not to be changed\n");
105 if (fls_index_to_set != FLS_OUT_OF_INDEXES)
106 {
107 void* ret;
108 BOOL bret;
109
110 SetLastError( 0xdeadbeef );
111 ret = pFlsGetValue(fls_index_to_set);
112 ok(ret == NULL, "FlsGetValue returned %p, expected NULL\n", ret);
113 ok(GetLastError() == ERROR_INVALID_PARAMETER, "Got unexpected error %lu.\n", GetLastError());
114
115 /* Set the FLS value */
116 bret = pFlsSetValue(fls_index_to_set, fls_value_to_set);
117 ok(bret, "FlsSetValue failed with error %lu\n", GetLastError());
118
119 ok(!!teb->FlsSlots, "Got unexpected FlsSlots %p.\n", teb->FlsSlots);
120
121 /* Verify that FlsGetValue retrieves the value set by FlsSetValue */
122 SetLastError( 0xdeadbeef );
123 ret = pFlsGetValue(fls_index_to_set);
124 ok(ret == fls_value_to_set, "FlsGetValue returned %p, expected %p\n", ret, fls_value_to_set);
125 ok(GetLastError() == ERROR_SUCCESS, "FlsGetValue error %lu\n", GetLastError());
126 }
127 pSwitchToFiber(fibers[0]);
128}
129
130static void test_ConvertThreadToFiber(void)
131{
132 void *ret;
133
134 if (pConvertThreadToFiber)
135 {
136 fibers[0] = pConvertThreadToFiber(&testparam);
137 ok(fibers[0] != NULL, "ConvertThreadToFiber failed with error %lu\n", GetLastError());
138
139 SetLastError(0xdeadbeef);
140 ret = pConvertThreadToFiber(&testparam);
141 ok(!ret, "Got non NULL ret.\n");
142 ok(GetLastError() == ERROR_ALREADY_FIBER, "Got unexpected error %lu.\n", GetLastError());
143 }
144 else
145 {
146 win_skip( "ConvertThreadToFiber not present\n" );
147 }
148}
149
150static void test_ConvertThreadToFiberEx(void)
151{
152 void *ret;
153
154 if (pConvertThreadToFiberEx)
155 {
156 fibers[0] = pConvertThreadToFiberEx(&testparam, 0);
157 ok(fibers[0] != NULL, "ConvertThreadToFiberEx failed with error %lu\n", GetLastError());
158
159 SetLastError(0xdeadbeef);
160 ret = pConvertThreadToFiberEx(&testparam, 0);
161 ok(!ret, "Got non NULL ret.\n");
162 ok(GetLastError() == ERROR_ALREADY_FIBER, "Got unexpected error %lu.\n", GetLastError());
163 }
164 else
165 {
166 win_skip( "ConvertThreadToFiberEx not present\n" );
167 }
168}
169
170static void test_ConvertFiberToThread(void)
171{
172 if (pConvertFiberToThread)
173 {
174 BOOL ret = pConvertFiberToThread();
175 ok(ret, "ConvertFiberToThread failed with error %lu\n", GetLastError());
176 }
177 else
178 {
179 win_skip( "ConvertFiberToThread not present\n" );
180 }
181}
182
183static void test_FiberHandling(void)
184{
185 fiberCount = 0;
186 fibers[0] = pCreateFiber(0,FiberMainProc,&testparam);
187 ok(fibers[0] != NULL, "CreateFiber failed with error %lu\n", GetLastError());
188 pDeleteFiber(fibers[0]);
189
190 test_ConvertThreadToFiber();
191 test_ConvertFiberToThread();
192 if (pConvertThreadToFiberEx)
193 test_ConvertThreadToFiberEx();
194 else
195 test_ConvertThreadToFiber();
196
197 fibers[1] = pCreateFiber(0,FiberMainProc,&testparam);
198 ok(fibers[1] != NULL, "CreateFiber failed with error %lu\n", GetLastError());
199
200 pSwitchToFiber(fibers[1]);
201 ok(fiberCount == 1, "Wrong fiber count: %d\n", fiberCount);
202 pDeleteFiber(fibers[1]);
203
204 if (pCreateFiberEx)
205 {
206 fibers[1] = pCreateFiberEx(0,0,0,FiberMainProc,&testparam);
207 ok(fibers[1] != NULL, "CreateFiberEx failed with error %lu\n", GetLastError());
208
209 pSwitchToFiber(fibers[1]);
210 ok(fiberCount == 2, "Wrong fiber count: %d\n", fiberCount);
211 pDeleteFiber(fibers[1]);
212 }
213 else win_skip( "CreateFiberEx not present\n" );
214
215 if (pIsThreadAFiber) ok(pIsThreadAFiber(), "IsThreadAFiber reported FALSE\n");
216 test_ConvertFiberToThread();
217 if (pIsThreadAFiber) ok(!pIsThreadAFiber(), "IsThreadAFiber reported TRUE\n");
218}
219
220#define FLS_TEST_INDEX_COUNT 4096
221
222static unsigned int check_linked_list(const LIST_ENTRY *le, const LIST_ENTRY *search_entry, unsigned int *index_found)
223{
224 unsigned int count = 0;
225 LIST_ENTRY *entry;
226
227 *index_found = ~0;
228
229 for (entry = le->Flink; entry != le; entry = entry->Flink)
230 {
231 if (entry == search_entry)
232 {
233 ok(*index_found == ~0, "Duplicate list entry.\n");
234 *index_found = count;
235 }
236 ++count;
237 }
238 return count;
239}
240
241static unsigned int test_fls_callback_call_count;
242
243static void WINAPI test_fls_callback(void *data)
244{
245 ++test_fls_callback_call_count;
246}
247
248static unsigned int test_fls_chunk_size(unsigned int chunk_index)
249{
250 return 0x10 << chunk_index;
251}
252
253static unsigned int test_fls_chunk_index_from_index(unsigned int index, unsigned int *index_in_chunk)
254{
255 unsigned int chunk_index = 0;
256
257 while (index >= test_fls_chunk_size(chunk_index))
258 index -= test_fls_chunk_size(chunk_index++);
259
260 *index_in_chunk = index;
261 return chunk_index;
262}
263
264static HANDLE test_fiberlocalstorage_peb_locked_event;
265static HANDLE test_fiberlocalstorage_done_event;
266
267
268static DWORD WINAPI test_FiberLocalStorage_thread(void *arg)
269{
270 pRtlAcquirePebLock();
271 SetEvent(test_fiberlocalstorage_peb_locked_event);
272 WaitForSingleObject(test_fiberlocalstorage_done_event, INFINITE);
273 pRtlReleasePebLock();
274 return 0;
275}
276
277static void test_FiberLocalStorage(void)
278{
279 static DWORD fls_indices[FLS_TEST_INDEX_COUNT];
280 unsigned int i, j, count, entry_count, index;
281 LIST_ENTRY *fls_list_head, saved_entry;
282 TEB_FLS_DATA *fls_data, *new_fls_data;
283 GLOBAL_FLS_DATA *g_fls_data;
284 DWORD fls, fls_2, result;
285 TEB *teb = NtCurrentTeb();
286 PEB *peb = teb->Peb;
287 NTSTATUS status;
288 HANDLE hthread;
289 ULONG index2;
290 SIZE_T size;
291 void* val;
292 BOOL ret;
293
294 if (!pFlsAlloc || !pFlsSetValue || !pFlsGetValue || !pFlsFree)
295 {
296 win_skip( "Fiber Local Storage not supported\n" );
297 return;
298 }
299
300 if (pRtlFlsAlloc)
301 {
302 if (pRtlFlsGetValue)
303 {
304 status = pRtlFlsGetValue(0, NULL);
305 ok(status == STATUS_INVALID_PARAMETER, "Got unexpected status %#lx.\n", status);
306 }
307 else
308 {
309 win_skip("RtlFlsGetValue is not available.\n");
310 }
311
312 for (i = 0; i < FLS_TEST_INDEX_COUNT; ++i)
313 {
314 fls_indices[i] = 0xdeadbeef;
315 status = pRtlFlsAlloc(test_fls_callback, &fls_indices[i]);
316 ok(!status || status == STATUS_NO_MEMORY, "Got unexpected status %#lx.\n", status);
317 if (status)
318 {
319 ok(fls_indices[i] == 0xdeadbeef, "Got unexpected index %#lx.\n", fls_indices[i]);
320 break;
321 }
322 if (pRtlFlsSetValue)
323 {
324 status = pRtlFlsSetValue(fls_indices[i], (void *)(ULONG_PTR)(i + 1));
325 ok(!status, "Got unexpected status %#lx.\n", status);
326 }
327 }
328 count = i;
329
330 fls_data = teb->FlsSlots;
331
332 /* FLS limits are increased since Win10 18312. */
333 ok(count && (count <= 127 || (count > 4000 && count < 4096)), "Got unexpected count %u.\n", count);
334
335#if defined(__REACTOS__) && defined(_WIN64)
336 /* peb layout is different on ReactOS x64 */
337 if (!is_reactos() && !peb->FlsCallback)
338#else
339 if (!peb->FlsCallback)
340#endif
341 {
342 ok(pRtlFlsSetValue && pRtlFlsGetValue, "Missing RtlFlsGetValue / RtlFlsSetValue.\n");
343 ok(!peb->FlsBitmap, "Got unexpected FlsBitmap %p.\n", peb->FlsBitmap);
344 ok(!peb->FlsListHead.Flink && !peb->FlsListHead.Blink, "Got nonzero FlsListHead.\n");
345 ok(!peb->FlsHighIndex, "Got unexpected FlsHighIndex %lu.\n", peb->FlsHighIndex);
346
347 fls_list_head = fls_data->fls_list_entry.Flink;
348
349 entry_count = check_linked_list(fls_list_head, &fls_data->fls_list_entry, &index);
350 ok(entry_count == 1, "Got unexpected count %u.\n", entry_count);
351 ok(!index, "Got unexpected index %u.\n", index);
352
353 g_fls_data = CONTAINING_RECORD(fls_list_head, GLOBAL_FLS_DATA, fls_list_head);
354
355 ok(g_fls_data->fls_high_index == 0xfef, "Got unexpected fls_high_index %#lx.\n", g_fls_data->fls_high_index);
356
357 for (i = 0; i < 8; ++i)
358 {
359 ok(!!g_fls_data->fls_callback_chunks[i], "Got zero fls_callback_chunks[%u].\n", i);
360 ok(g_fls_data->fls_callback_chunks[i]->count == test_fls_chunk_size(i),
361 "Got unexpected g_fls_data->fls_callback_chunks[%u]->count %lu.\n",
362 i, g_fls_data->fls_callback_chunks[i]->count);
363
364 size = HeapSize(GetProcessHeap(), 0, g_fls_data->fls_callback_chunks[i]);
365 ok(size == sizeof(ULONG_PTR) + sizeof(FLS_CALLBACK) * test_fls_chunk_size(i),
366 "Got unexpected size %p.\n", (void *)size);
367
368 ok(!!fls_data->fls_data_chunks[i], "Got zero fls_data->fls_data_chunks[%u].\n", i);
369 ok(!fls_data->fls_data_chunks[i][0], "Got unexpected fls_data->fls_data_chunks[%u][0] %p.\n",
370 i, fls_data->fls_data_chunks[i][0]);
371 size = HeapSize(GetProcessHeap(), 0, fls_data->fls_data_chunks[i]);
372 ok(size == sizeof(void *) * (test_fls_chunk_size(i) + 1), "Got unexpected size %p.\n", (void *)size);
373
374 if (!i)
375 {
376 ok(g_fls_data->fls_callback_chunks[0]->callbacks[0].callback == (void *)~(ULONG_PTR)0,
377 "Got unexpected callback %p.\n",
378 g_fls_data->fls_callback_chunks[0]->callbacks[0].callback);
379 }
380
381 for (j = i ? 0 : fls_indices[0]; j < test_fls_chunk_size(i); ++j)
382 {
383 ok(!g_fls_data->fls_callback_chunks[i]->callbacks[j].unknown,
384 "Got unexpected unknown %p, i %u, j %u.\n",
385 g_fls_data->fls_callback_chunks[i]->callbacks[j].unknown, i, j);
386 ok(g_fls_data->fls_callback_chunks[i]->callbacks[j].callback == test_fls_callback,
387 "Got unexpected callback %p, i %u, j %u.\n",
388 g_fls_data->fls_callback_chunks[i]->callbacks[j].callback, i, j);
389 }
390 }
391 for (i = 0; i < count; ++i)
392 {
393 j = test_fls_chunk_index_from_index(fls_indices[i], &index);
394 ok(fls_data->fls_data_chunks[j][index + 1] == (void *)(ULONG_PTR)(i + 1),
395 "Got unexpected FLS value %p, i %u, j %u, index %u.\n",
396 fls_data->fls_data_chunks[j][index + 1], i, j, index);
397 }
398 j = test_fls_chunk_index_from_index(fls_indices[0x10], &index);
399 g_fls_data->fls_callback_chunks[j]->callbacks[index].callback = NULL;
400 status = pRtlFlsFree(fls_indices[0x10]);
401 ok(status == STATUS_INVALID_PARAMETER, "Got unexpected status %#lx.\n", status);
402
403 g_fls_data->fls_callback_chunks[j]->callbacks[index].callback = test_fls_callback;
404 test_fls_callback_call_count = 0;
405 status = pRtlFlsFree(fls_indices[0x10]);
406 ok(!status, "Got unexpected status %#lx.\n", status);
407 ok(test_fls_callback_call_count == 1, "Got unexpected callback call count %u.\n",
408 test_fls_callback_call_count);
409
410 ok(!fls_data->fls_data_chunks[j][0], "Got unexpected fls_data->fls_data_chunks[%u][0] %p.\n",
411 j, fls_data->fls_data_chunks[j][0]);
412 ok(!g_fls_data->fls_callback_chunks[j]->callbacks[index].callback,
413 "Got unexpected callback %p.\n",
414 g_fls_data->fls_callback_chunks[j]->callbacks[index].callback);
415
416 fls_data->fls_data_chunks[j][index + 1] = (void *)(ULONG_PTR)0x28;
417 status = pRtlFlsAlloc(test_fls_callback, &index2);
418 ok(!status, "Got unexpected status %#lx.\n", status);
419 ok(index2 == fls_indices[0x10], "Got unexpected index %lu.\n", index2);
420 ok(fls_data->fls_data_chunks[j][index + 1] == (void *)(ULONG_PTR)0x28, "Got unexpected data %p.\n",
421 fls_data->fls_data_chunks[j][index + 1]);
422
423 status = pRtlFlsSetValue(index2, (void *)(ULONG_PTR)0x11);
424 ok(!status, "Got unexpected status %#lx.\n", status);
425
426 teb->FlsSlots = NULL;
427
428 val = (void *)0xdeadbeef;
429 status = pRtlFlsGetValue(fls_indices[1], &val);
430 new_fls_data = teb->FlsSlots;
431 ok(status == STATUS_INVALID_PARAMETER, "Got unexpected status %#lx.\n", status);
432 ok(val == (void *)0xdeadbeef, "Got unexpected val %p.\n", val);
433 ok(!new_fls_data, "Got unexpected teb->FlsSlots %p.\n", new_fls_data);
434
435 status = pRtlFlsSetValue(fls_indices[1], (void *)(ULONG_PTR)0x28);
436 new_fls_data = teb->FlsSlots;
437 ok(!status, "Got unexpected status %#lx.\n", status);
438 ok(!!new_fls_data, "Got unexpected teb->FlsSlots %p.\n", new_fls_data);
439
440 entry_count = check_linked_list(fls_list_head, &fls_data->fls_list_entry, &index);
441 ok(entry_count == 2, "Got unexpected count %u.\n", entry_count);
442 ok(!index, "Got unexpected index %u.\n", index);
443 check_linked_list(fls_list_head, &new_fls_data->fls_list_entry, &index);
444 ok(index == 1, "Got unexpected index %u.\n", index);
445
446 val = (void *)0xdeadbeef;
447 status = pRtlFlsGetValue(fls_indices[2], &val);
448 ok(!status, "Got unexpected status %#lx.\n", status);
449 ok(!val, "Got unexpected val %p.\n", val);
450
451
452 /* With bit 0 of flags set RtlProcessFlsData is removing FLS data from the linked list
453 * and calls FLS callbacks. With bit 1 set the memory is freed. The remaining bits do not seem
454 * to have any obvious effect. */
455 for (i = 2; i < 32; ++i)
456 {
457 pRtlProcessFlsData(new_fls_data, 1 << i);
458 size = HeapSize(GetProcessHeap(), 0, new_fls_data);
459 ok(size == sizeof(*new_fls_data), "Got unexpected size %p.\n", (void *)size);
460 }
461
462 if (0)
463 {
464 pRtlProcessFlsData(new_fls_data, 2);
465 entry_count = check_linked_list(fls_list_head, &fls_data->fls_list_entry, &index);
466 ok(entry_count == 2, "Got unexpected count %u.\n", entry_count);
467
468 /* Crashes on Windows. */
469 HeapSize(GetProcessHeap(), 0, new_fls_data);
470 }
471
472 test_fiberlocalstorage_peb_locked_event = CreateEventA(NULL, FALSE, FALSE, NULL);
473 test_fiberlocalstorage_done_event = CreateEventA(NULL, FALSE, FALSE, NULL);
474 hthread = CreateThread(NULL, 0, test_FiberLocalStorage_thread, NULL, 0, NULL);
475 ok(!!hthread, "CreateThread failed.\n");
476 result = WaitForSingleObject(test_fiberlocalstorage_peb_locked_event, INFINITE);
477 ok(result == WAIT_OBJECT_0, "Got unexpected result %lu.\n", result);
478 teb->FlsSlots = NULL;
479
480 test_fls_callback_call_count = 0;
481 saved_entry = new_fls_data->fls_list_entry;
482 pRtlProcessFlsData(new_fls_data, 1);
483 ok(!teb->FlsSlots, "Got unexpected teb->FlsSlots %p.\n", teb->FlsSlots);
484
485 teb->FlsSlots = fls_data;
486 ok(test_fls_callback_call_count == 1, "Got unexpected callback call count %u.\n",
487 test_fls_callback_call_count);
488
489 SetEvent(test_fiberlocalstorage_done_event);
490 WaitForSingleObject(hthread, INFINITE);
491 CloseHandle(hthread);
492 CloseHandle(test_fiberlocalstorage_peb_locked_event);
493 CloseHandle(test_fiberlocalstorage_done_event);
494
495 ok(new_fls_data->fls_list_entry.Flink == saved_entry.Flink, "Got unexpected Flink %p.\n",
496 saved_entry.Flink);
497 ok(new_fls_data->fls_list_entry.Blink == saved_entry.Blink, "Got unexpected Flink %p.\n",
498 saved_entry.Blink);
499 size = HeapSize(GetProcessHeap(), 0, new_fls_data);
500 ok(size == sizeof(*new_fls_data), "Got unexpected size %p.\n", (void *)size);
501 test_fls_callback_call_count = 0;
502 i = test_fls_chunk_index_from_index(fls_indices[1], &index);
503 new_fls_data->fls_data_chunks[i][index + 1] = (void *)(ULONG_PTR)0x28;
504 pRtlProcessFlsData(new_fls_data, 2);
505 ok(!test_fls_callback_call_count, "Got unexpected callback call count %u.\n",
506 test_fls_callback_call_count);
507
508 if (0)
509 {
510 /* crashes on Windows. */
511 HeapSize(GetProcessHeap(), 0, new_fls_data);
512 }
513
514 entry_count = check_linked_list(fls_list_head, &fls_data->fls_list_entry, &index);
515 ok(entry_count == 1, "Got unexpected count %u.\n", entry_count);
516 ok(!index, "Got unexpected index %u.\n", index);
517 }
518 else
519 {
520 win_skip("Old FLS data storage layout, skipping test.\n");
521 g_fls_data = NULL;
522 }
523
524 if (0)
525 {
526 /* crashes on Windows. */
527 pRtlFlsGetValue(fls_indices[0], NULL);
528 }
529
530 for (i = 0; i < count; ++i)
531 {
532 if (pRtlFlsGetValue)
533 {
534 status = pRtlFlsGetValue(fls_indices[i], &val);
535 ok(!status, "Got unexpected status %#lx.\n", status);
536 ok(val == (void *)(ULONG_PTR)(i + 1), "Got unexpected val %p, i %u.\n", val, i);
537 }
538
539 status = pRtlFlsFree(fls_indices[i]);
540 ok(!status, "Got unexpected status %#lx, i %u.\n", status, i);
541 }
542
543#if defined(__REACTOS__) && defined(_WIN64)
544 /* peb layout is different on ReactOS x64 */
545 if (!is_reactos() && !peb->FlsCallback)
546#else
547 if (!peb->FlsCallback)
548#endif
549 {
550 ok(g_fls_data->fls_high_index == 0xfef, "Got unexpected fls_high_index %#lx.\n",
551 g_fls_data->fls_high_index);
552
553 for (i = 0; i < 8; ++i)
554 {
555 ok(!!g_fls_data->fls_callback_chunks[i], "Got zero fls_callback_chunks[%u].\n", i);
556 ok(!!fls_data->fls_data_chunks[i], "Got zero fls_data->fls_data_chunks[%u].\n", i);
557 }
558 }
559 }
560 else
561 {
562 win_skip("RtlFlsAlloc is not available.\n");
563 }
564
565 /* Test an unallocated index
566 * FlsFree should fail
567 * FlsGetValue and FlsSetValue should succeed
568 */
569 SetLastError( 0xdeadbeef );
570 ret = pFlsFree( 127 );
571 ok( !ret, "freeing fls index 127 (unallocated) succeeded\n" );
572 ok( GetLastError() == ERROR_INVALID_PARAMETER,
573 "freeing fls index 127 (unallocated) wrong error %lu\n", GetLastError() );
574
575 val = pFlsGetValue( 127 );
576 ok( val == NULL,
577 "getting fls index 127 (unallocated) failed with error %lu\n", GetLastError() );
578
579 if (pRtlFlsGetValue)
580 {
581 val = (void *)0xdeadbeef;
582 status = pRtlFlsGetValue(127, &val);
583 ok( !status, "Got unexpected status %#lx.\n", status );
584 ok( !val, "Got unexpected val %p.\n", val );
585 }
586
587 ret = pFlsSetValue( 127, (void*) 0x217 );
588 ok( ret, "setting fls index 127 (unallocated) failed with error %lu\n", GetLastError() );
589
590 SetLastError( 0xdeadbeef );
591 val = pFlsGetValue( 127 );
592 ok( val == (void*) 0x217, "fls index 127 (unallocated) wrong value %p\n", val );
593 ok( GetLastError() == ERROR_SUCCESS,
594 "getting fls index 127 (unallocated) failed with error %lu\n", GetLastError() );
595
596 if (pRtlFlsGetValue)
597 {
598 val = (void *)0xdeadbeef;
599 status = pRtlFlsGetValue(127, &val);
600 ok( !status, "Got unexpected status %#lx.\n", status );
601 ok( val == (void*)0x217, "Got unexpected val %p.\n", val );
602 }
603
604 /* FlsFree, FlsGetValue, and FlsSetValue out of bounds should return
605 * ERROR_INVALID_PARAMETER
606 */
607 SetLastError( 0xdeadbeef );
608 ret = pFlsFree( 128 );
609 ok( !ret, "freeing fls index 128 (out of bounds) succeeded\n" );
610 ok( GetLastError() == ERROR_INVALID_PARAMETER,
611 "freeing fls index 128 (out of bounds) wrong error %lu\n", GetLastError() );
612
613 SetLastError( 0xdeadbeef );
614 ret = pFlsSetValue( 128, (void*) 0x217 );
615 ok( ret || GetLastError() == ERROR_INVALID_PARAMETER,
616 "setting fls index 128 (out of bounds) wrong error %lu\n", GetLastError() );
617
618 SetLastError( 0xdeadbeef );
619 val = pFlsGetValue( 128 );
620 ok( GetLastError() == ERROR_INVALID_PARAMETER || val == (void *)0x217,
621 "getting fls index 128 (out of bounds) wrong error %lu\n", GetLastError() );
622
623 /* Test index 0 */
624 SetLastError( 0xdeadbeef );
625 val = pFlsGetValue( 0 );
626 ok( !val, "fls index 0 set to %p\n", val );
627 ok( GetLastError() == ERROR_INVALID_PARAMETER, "setting fls index wrong error %lu\n", GetLastError() );
628 if (pRtlFlsGetValue)
629 {
630 val = (void *)0xdeadbeef;
631 status = pRtlFlsGetValue(0, &val);
632 ok( status == STATUS_INVALID_PARAMETER, "Got unexpected status %#lx.\n", status );
633 ok( val == (void*)0xdeadbeef, "Got unexpected val %p.\n", val );
634 }
635
636 SetLastError( 0xdeadbeef );
637 ret = pFlsSetValue( 0, (void *)0xdeadbeef );
638 ok( !ret, "setting fls index 0 succeeded\n" );
639 ok( GetLastError() == ERROR_INVALID_PARAMETER, "setting fls index wrong error %lu\n", GetLastError() );
640 if (pRtlFlsSetValue)
641 {
642 status = pRtlFlsSetValue( 0, (void *)0xdeadbeef );
643 ok( status == STATUS_INVALID_PARAMETER, "Got unexpected status %#lx.\n", status );
644 }
645 SetLastError( 0xdeadbeef );
646 val = pFlsGetValue( 0 );
647 ok( !val, "fls index 0 wrong value %p\n", val );
648 ok( GetLastError() == ERROR_INVALID_PARAMETER, "setting fls index wrong error %lu\n", GetLastError() );
649
650 /* Test creating an FLS index */
651 fls = pFlsAlloc( NULL );
652 ok( fls != FLS_OUT_OF_INDEXES, "FlsAlloc failed\n" );
653 ok( fls != 0, "fls index 0 allocated\n" );
654 val = pFlsGetValue( fls );
655 ok( !val, "fls index %lu wrong value %p\n", fls, val );
656 SetLastError( 0xdeadbeef );
657 ret = pFlsSetValue( fls, (void *)0xdeadbeef );
658 ok( ret, "setting fls index %lu failed\n", fls );
659 ok( GetLastError() == 0xdeadbeef, "setting fls index wrong error %lu\n", GetLastError() );
660 SetLastError( 0xdeadbeef );
661 val = pFlsGetValue( fls );
662 ok( val == (void *)0xdeadbeef, "fls index %lu wrong value %p\n", fls, val );
663 ok( GetLastError() == ERROR_SUCCESS,
664 "getting fls index %lu failed with error %lu\n", fls, GetLastError() );
665 pFlsFree( fls );
666
667 /* Undefined behavior: verify the value is NULL after it the slot is freed */
668 SetLastError( 0xdeadbeef );
669 val = pFlsGetValue( fls );
670 ok( val == NULL, "fls index %lu wrong value %p\n", fls, val );
671 ok( GetLastError() == ERROR_SUCCESS,
672 "getting fls index %lu failed with error %lu\n", fls, GetLastError() );
673
674 /* Undefined behavior: verify the value is settable after the slot is freed */
675 ret = pFlsSetValue( fls, (void *)0xdeadbabe );
676 ok( ret, "setting fls index %lu failed\n", fls );
677 val = pFlsGetValue( fls );
678 ok( val == (void *)0xdeadbabe, "fls index %lu wrong value %p\n", fls, val );
679
680 /* Try to create the same FLS index again, and verify that is initialized to NULL */
681 fls_2 = pFlsAlloc( NULL );
682 ok( fls != FLS_OUT_OF_INDEXES, "FlsAlloc failed with error %lu\n", GetLastError() );
683 /* If this fails it is not an API error, but the test will be inconclusive */
684 ok( fls_2 == fls, "different FLS index allocated, was %lu, now %lu\n", fls, fls_2 );
685
686 SetLastError( 0xdeadbeef );
687 val = pFlsGetValue( fls_2 );
688 ok( val == NULL || val == (void *)0xdeadbabe, "fls index %lu wrong value %p\n", fls, val );
689 ok( GetLastError() == ERROR_SUCCESS,
690 "getting fls index %lu failed with error %lu\n", fls_2, GetLastError() );
691 pFlsFree( fls_2 );
692}
693
694static void test_FiberLocalStorageCallback(PFLS_CALLBACK_FUNCTION cbfunc)
695{
696 DWORD fls;
697 BOOL ret;
698 void* val, *val2;
699
700 if (!pFlsAlloc || !pFlsSetValue || !pFlsGetValue || !pFlsFree)
701 {
702 win_skip( "Fiber Local Storage not supported\n" );
703 return;
704 }
705
706 /* Test that the callback is executed */
707 cbCount = 0;
708 fls = pFlsAlloc( cbfunc );
709 ok( fls != FLS_OUT_OF_INDEXES, "FlsAlloc failed with error %lu\n", GetLastError() );
710
711 val = (void*) 0x1587;
712 fls_value_to_set = val;
713 ret = pFlsSetValue( fls, val );
714 ok(ret, "FlsSetValue failed with error %lu\n", GetLastError() );
715
716 val2 = pFlsGetValue( fls );
717 ok(val == val2, "FlsGetValue returned %p, expected %p\n", val2, val);
718
719 ret = pFlsFree( fls );
720 ok(ret, "FlsFree failed with error %lu\n", GetLastError() );
721 ok( cbCount == 1, "Wrong callback count: %d\n", cbCount );
722
723 /* Test that callback is not executed if value is NULL */
724 cbCount = 0;
725 fls = pFlsAlloc( cbfunc );
726 ok( fls != FLS_OUT_OF_INDEXES, "FlsAlloc failed with error %lu\n", GetLastError() );
727
728 ret = pFlsSetValue( fls, NULL );
729 ok( ret, "FlsSetValue failed with error %lu\n", GetLastError() );
730
731 pFlsFree( fls );
732 ok( ret, "FlsFree failed with error %lu\n", GetLastError() );
733 ok( cbCount == 0, "Wrong callback count: %d\n", cbCount );
734}
735
736static void test_FiberLocalStorageWithFibers(PFLS_CALLBACK_FUNCTION cbfunc)
737{
738 void* val1 = (void*) 0x314;
739 void* val2 = (void*) 0x152;
740 BOOL ret;
741
742 if (!pFlsAlloc || !pFlsFree || !pFlsSetValue || !pFlsGetValue)
743 {
744 win_skip( "Fiber Local Storage not supported\n" );
745 return;
746 }
747
748 fls_index_to_set = pFlsAlloc(cbfunc);
749 ok(fls_index_to_set != FLS_OUT_OF_INDEXES, "FlsAlloc failed with error %lu\n", GetLastError());
750
751 test_ConvertThreadToFiber();
752
753 fiberCount = 0;
754 cbCount = 0;
755 fibers[1] = pCreateFiber(0,FiberMainProc,&testparam);
756 fibers[2] = pCreateFiber(0,FiberMainProc,&testparam);
757 ok(fibers[1] != NULL, "CreateFiber failed with error %lu\n", GetLastError());
758 ok(fibers[2] != NULL, "CreateFiber failed with error %lu\n", GetLastError());
759 ok(fiberCount == 0, "Wrong fiber count: %d\n", fiberCount);
760 ok(cbCount == 0, "Wrong callback count: %d\n", cbCount);
761
762 fiberCount = 0;
763 cbCount = 0;
764 fls_value_to_set = val1;
765 pSwitchToFiber(fibers[1]);
766 ok(fiberCount == 1, "Wrong fiber count: %d\n", fiberCount);
767 ok(cbCount == 0, "Wrong callback count: %d\n", cbCount);
768
769 fiberCount = 0;
770 cbCount = 0;
771 fls_value_to_set = val2;
772 pSwitchToFiber(fibers[2]);
773 ok(fiberCount == 1, "Wrong fiber count: %d\n", fiberCount);
774 ok(cbCount == 0, "Wrong callback count: %d\n", cbCount);
775
776 fls_value_to_set = val2;
777 ret = pFlsSetValue(fls_index_to_set, fls_value_to_set);
778 ok(ret, "FlsSetValue failed\n");
779 ok(val2 == pFlsGetValue(fls_index_to_set), "FlsGetValue failed\n");
780
781 fiberCount = 0;
782 cbCount = 0;
783 fls_value_to_set = val1;
784 pDeleteFiber(fibers[1]);
785 ok(fiberCount == 0, "Wrong fiber count: %d\n", fiberCount);
786 ok(cbCount == 1, "Wrong callback count: %d\n", cbCount);
787
788 fiberCount = 0;
789 cbCount = 0;
790 fls_value_to_set = val2;
791 pFlsFree(fls_index_to_set);
792 ok(fiberCount == 0, "Wrong fiber count: %d\n", fiberCount);
793 ok(cbCount == 2, "Wrong callback count: %d\n", cbCount);
794
795 fiberCount = 0;
796 cbCount = 0;
797 fls_value_to_set = val1;
798 pDeleteFiber(fibers[2]);
799 ok(fiberCount == 0, "Wrong fiber count: %d\n", fiberCount);
800 ok(cbCount == 0, "Wrong callback count: %d\n", cbCount);
801
802 test_ConvertFiberToThread();
803}
804
805#define check_current_actctx_is(e,t) check_current_actctx_is_(__LINE__, e, t)
806static void check_current_actctx_is_(int line, HANDLE expected_actctx, BOOL todo)
807{
808 HANDLE cur_actctx;
809 BOOL ret;
810
811 cur_actctx = (void*)0xdeadbeef;
812 ret = GetCurrentActCtx(&cur_actctx);
813 ok_(__FILE__, line)(ret, "thread GetCurrentActCtx failed, %lu\n", GetLastError());
814
815 todo_wine_if(todo)
816 ok_(__FILE__, line)(cur_actctx == expected_actctx, "got %p, expected %p\n", cur_actctx, expected_actctx);
817
818 ReleaseActCtx(cur_actctx);
819}
820
821static DWORD WINAPI subthread_actctx_func(void *actctx)
822{
823 HANDLE fiber;
824 BOOL ret;
825
826 check_current_actctx_is(actctx, FALSE);
827
828 fiber = pConvertThreadToFiber(NULL);
829 ok(fiber != NULL, "ConvertThreadToFiber returned error %lu\n", GetLastError());
830 check_current_actctx_is(actctx, FALSE);
831 fibers[2] = fiber;
832
833 SwitchToFiber(fibers[0]);
834 check_current_actctx_is(actctx, FALSE);
835
836 ok(fibers[2] == fiber, "fibers[2]: expected %p, got %p\n", fiber, fibers[2]);
837 fibers[2] = NULL;
838 ret = pConvertFiberToThread();
839 ok(ret, "ConvertFiberToThread returned error %lu\n", GetLastError());
840 check_current_actctx_is(actctx, FALSE);
841
842 return 0;
843}
844
845static void WINAPI fiber_actctx_func(void *actctx)
846{
847 ULONG_PTR cookie;
848 DWORD tid, wait;
849 HANDLE thread;
850 BOOL ret;
851
852 check_current_actctx_is(NULL, FALSE);
853
854 ret = ActivateActCtx(actctx, &cookie);
855 ok(ret, "ActivateActCtx returned error %lu\n", GetLastError());
856 check_current_actctx_is(actctx, FALSE);
857
858 SwitchToFiber(fibers[0]);
859 check_current_actctx_is(actctx, FALSE);
860
861 thread = CreateThread(NULL, 0, subthread_actctx_func, actctx, 0, &tid);
862 ok(thread != NULL, "CreateThread returned error %lu\n", GetLastError());
863
864 wait = WaitForSingleObject(thread, INFINITE);
865 ok(wait == WAIT_OBJECT_0, "WaitForSingleObject returned %lu (last error: %lu)\n",
866 wait, GetLastError());
867 CloseHandle(thread);
868
869 ret = DeactivateActCtx(0, cookie);
870 ok(ret, "DeactivateActCtx returned error %lu\n", GetLastError());
871 check_current_actctx_is(NULL, FALSE);
872
873 SwitchToFiber(fibers[0]);
874 ok(0, "unreachable\n");
875}
876
877/* Test that activation context is switched on SwitchToFiber() call */
878static void subtest_fiber_actctx_switch(HANDLE current_actctx, HANDLE child_actctx)
879{
880 fibers[1] = pCreateFiber(0, fiber_actctx_func, child_actctx);
881 ok(fibers[1] != NULL, "CreateFiber returned error %lu\n", GetLastError());
882 check_current_actctx_is(current_actctx, FALSE);
883
884 SwitchToFiber(fibers[1]);
885 check_current_actctx_is(current_actctx, FALSE);
886
887 SwitchToFiber(fibers[1]);
888 check_current_actctx_is(current_actctx, FALSE);
889
890 SwitchToFiber(fibers[2]);
891 check_current_actctx_is(current_actctx, FALSE);
892 ok(fibers[2] == NULL, "expected fiber to be deleted (got %p)\n", fibers[2]);
893
894 DeleteFiber(fibers[1]);
895 fibers[1] = NULL;
896}
897
898static void WINAPI exit_thread_fiber_func(void *unused)
899{
900 BOOL ret;
901
902 ret = pConvertFiberToThread();
903 ok(ret, "ConvertFiberToThread returned error %lu\n", GetLastError());
904
905 ExitThread(16);
906}
907
908static DWORD WINAPI thread_actctx_func_early_exit(void *actctx)
909{
910 HANDLE exit_thread_fiber;
911
912 check_current_actctx_is(actctx, FALSE);
913
914 fibers[1] = pConvertThreadToFiber(NULL);
915 ok(fibers[1] != NULL, "ConvertThreadToFiber returned error %lu\n", GetLastError());
916 check_current_actctx_is(actctx, FALSE);
917
918 exit_thread_fiber = pCreateFiber(0, exit_thread_fiber_func, NULL);
919 ok(exit_thread_fiber != NULL, "CreateFiber returned error %lu\n", GetLastError());
920
921 /* exit thread, but keep current fiber */
922 SwitchToFiber(exit_thread_fiber);
923 check_current_actctx_is(actctx, FALSE);
924
925 SwitchToFiber(fibers[0]);
926 ok(0, "unreachable\n");
927 return 17;
928}
929
930/* Test that fiber activation context stack is preserved regardless of creator thread's lifetime */
931static void subtest_fiber_actctx_preservation(HANDLE current_actctx, HANDLE child_actctx)
932{
933 ULONG_PTR cookie;
934 DWORD tid, wait;
935 HANDLE thread;
936 BOOL ret;
937
938 ret = ActivateActCtx(child_actctx, &cookie);
939 ok(ret, "ActivateActCtx returned error %lu\n", GetLastError());
940 check_current_actctx_is(child_actctx, FALSE);
941
942 thread = CreateThread(NULL, 0, thread_actctx_func_early_exit, child_actctx, 0, &tid);
943 ok(thread != NULL, "CreateThread returned error %lu\n", GetLastError());
944
945 ret = DeactivateActCtx(0, cookie);
946 ok(ret, "DeactivateActCtx returned error %lu\n", GetLastError());
947 check_current_actctx_is(current_actctx, FALSE);
948
949 wait = WaitForSingleObject(thread, INFINITE);
950 ok(wait == WAIT_OBJECT_0, "WaitForSingleObject returned %lu (last error: %lu)\n",
951 wait, GetLastError());
952 CloseHandle(thread);
953
954 /* The exited thread has been converted to a fiber */
955#if defined(__REACTOS__) && defined(_M_AMD64)
956 skip("FIXME: SwitchToFiber() is unimplemented on AMD64 ReactOS\n");
957#else
958 SwitchToFiber(fibers[1]);
959 check_current_actctx_is(current_actctx, FALSE);
960
961 DeleteFiber(fibers[1]);
962 fibers[1] = NULL;
963#endif
964}
965
966static HANDLE create_actctx_from_module_manifest(void)
967{
968 ACTCTXW actctx;
969
970 memset(&actctx, 0, sizeof(ACTCTXW));
971 actctx.cbSize = sizeof(actctx);
972 actctx.dwFlags = ACTCTX_FLAG_HMODULE_VALID | ACTCTX_FLAG_RESOURCE_NAME_VALID;
973 actctx.lpResourceName = MAKEINTRESOURCEW(124);
974 actctx.hModule = GetModuleHandleW(NULL);
975
976 return CreateActCtxW(&actctx);
977}
978
979static void test_fiber_actctx(void)
980{
981 ULONG_PTR cookies[3];
982 HANDLE actctxs[3];
983 size_t i, j;
984 BOOL ret;
985
986#ifdef __REACTOS__
987 if (GetNTVersion() < _WIN32_WINNT_VISTA && !is_reactos()) {
988 skip("test_fiber_actctx() crashes and isn't valid on WS03.\n");
989 return;
990 }
991#endif
992 for (i = 0; i < ARRAY_SIZE(actctxs); i++)
993 {
994 actctxs[i] = create_actctx_from_module_manifest();
995 ok(actctxs[i] != INVALID_HANDLE_VALUE, "failed to create context, error %lu\n", GetLastError());
996 for (j = 0; j < i; j++)
997 {
998 ok(actctxs[i] != actctxs[j],
999 "actctxs[%Iu] (%p) and actctxs[%Iu] (%p) should not alias\n",
1000 i, actctxs[i], j, actctxs[j]);
1001 }
1002 }
1003
1004 ret = ActivateActCtx(actctxs[0], &cookies[0]);
1005 ok(ret, "ActivateActCtx returned error %lu\n", GetLastError());
1006 check_current_actctx_is(actctxs[0], FALSE);
1007
1008 test_ConvertThreadToFiber();
1009 check_current_actctx_is(actctxs[0], FALSE);
1010
1011 ret = ActivateActCtx(actctxs[1], &cookies[1]);
1012 ok(ret, "ActivateActCtx returned error %lu\n", GetLastError());
1013 check_current_actctx_is(actctxs[1], FALSE);
1014
1015 subtest_fiber_actctx_switch(actctxs[1], actctxs[2]);
1016 subtest_fiber_actctx_preservation(actctxs[1], actctxs[2]);
1017
1018 ret = DeactivateActCtx(0, cookies[1]);
1019 ok(ret, "DeactivateActCtx returned error %lu\n", GetLastError());
1020 check_current_actctx_is(actctxs[0], FALSE);
1021
1022 test_ConvertFiberToThread();
1023 check_current_actctx_is(actctxs[0], FALSE);
1024
1025 ret = DeactivateActCtx(0, cookies[0]);
1026 ok(ret, "DeactivateActCtx returned error %lu\n", GetLastError());
1027 check_current_actctx_is(NULL, FALSE);
1028
1029 for (i = ARRAY_SIZE(actctxs); i > 0; )
1030 ReleaseActCtx(actctxs[--i]);
1031}
1032
1033
1034static void WINAPI fls_exit_deadlock_callback(void *arg)
1035{
1036 if (arg == (void *)1)
1037 Sleep(INFINITE);
1038 if (arg == (void *)2)
1039 /* Unfortunately this test won't affect the exit code if it fails, but
1040 * at least it will print a failure message. */
1041 ok(RtlDllShutdownInProgress(), "expected DLL shutdown\n");
1042}
1043
1044static DWORD CALLBACK fls_exit_deadlock_thread(void *arg)
1045{
1046 FlsSetValue((DWORD_PTR)arg, (void *)1);
1047 return 0;
1048}
1049
1050static void fls_exit_deadlock_child(void)
1051{
1052 DWORD index = FlsAlloc(fls_exit_deadlock_callback);
1053 FlsSetValue(index, (void *)2);
1054 CreateThread(NULL, 0, fls_exit_deadlock_thread, (void *)(DWORD_PTR)index, 0, NULL);
1055 Sleep(100);
1056 ExitProcess(0);
1057}
1058
1059static void test_fls_exit_deadlock(void)
1060{
1061 char **argv, cmdline[MAX_PATH];
1062 PROCESS_INFORMATION pi;
1063 STARTUPINFOA si = {0};
1064 BOOL ret;
1065
1066 /* Regression test for the following deadlock:
1067 *
1068 * Thread A Thread B
1069 * -----------------------------
1070 * ExitThread
1071 * acquire FLS lock
1072 * call FLS callback
1073 * ExitProcess
1074 * terminate thread A
1075 * acquire FLS lock
1076 *
1077 * Thread B deadlocks on acquiring the FLS lock (in order to itself call its
1078 * FLS callbacks.)
1079 */
1080
1081 winetest_get_mainargs(&argv);
1082 sprintf(cmdline, "%s %s fls_exit_deadlock", argv[0], argv[1]);
1083 ret = CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
1084 ok(ret, "failed to create child, error %lu\n", GetLastError());
1085 ret = WaitForSingleObject(pi.hProcess, 1000);
1086 ok(!ret, "wait failed\n");
1087 CloseHandle(pi.hProcess);
1088 CloseHandle(pi.hThread);
1089}
1090
1091START_TEST(fiber)
1092{
1093 char **argv;
1094 int argc;
1095
1096 argc = winetest_get_mainargs(&argv);
1097
1098 if (argc == 3 && !strcmp(argv[2], "fls_exit_deadlock"))
1099 {
1100 fls_exit_deadlock_child();
1101 return;
1102 }
1103
1104 init_funcs();
1105
1106 if (!pCreateFiber)
1107 {
1108 win_skip( "Fibers not supported by win95\n" );
1109 return;
1110 }
1111
1112 test_FiberHandling();
1113 test_FiberLocalStorage();
1114 test_FiberLocalStorageCallback(FiberLocalStorageProc);
1115 test_FiberLocalStorageWithFibers(FiberLocalStorageProc);
1116 test_fiber_actctx();
1117 test_fls_exit_deadlock();
1118}