/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */

/* 
 * win32.c --
 *
 *	Interface routines for nsthreads using WIN32 functions.
 *
 */

#include "thread.h"
#include <io.h>

/*
 * The following structure maintains the Win32-specific state of a
 * process thread and mutex and condition waits pointers.
 */

typedef struct WinThread {
    struct WinThread *nextPtr;
    struct WinThread *wakeupPtr;
    HANDLE	      self;
    HANDLE	      event;
    int		      condwait;
    void	     *slots[NS_THREAD_MAXTLS];
} WinThread;

typedef struct ThreadArg {
    HANDLE  self;
    void   *arg;
} ThreadArg;
    
/*
 * The following structure defines a mutex as a spinlock and
 * wait queue.  The custom lock code provides the speed of
 * a Win32 CriticalSection with the TryLock of a Mutex handle
 * (TryEnterCriticalSection is not yet available on all Win32
 * platforms).
 */

typedef struct {
    int		locked;
    LONG	spinlock;
    WinThread  *waitPtr;
} Lock;
  
/*
 * The following structure defines a condition variable as
 * a spinlock and wait queue.
 */

typedef struct {
    LONG	  spinlock;
    WinThread	 *waitPtr;
} Cond;

/*
 * The following structure is used to maintain state during
 * an open directory search.
 */

typedef struct Search {
    long handle;
    struct _finddata_t fdata;
    struct dirent ent;
} Search;

/*
 * Static functions defined in this file.
 */

static void	Wakeup(WinThread *wPtr, char *func);
static void	Queue(WinThread **waitPtrPtr, WinThread *wPtr);
static Cond    *GetCond(Ns_Cond *cond);
static unsigned __stdcall ThreadMain(void *arg);

#define SPINLOCK(lPtr) \
    while(InterlockedExchange((lPtr), 1)) Sleep(0)
#define SPINUNLOCK(lPtr) \
    InterlockedExchange((lPtr), 0)
#define GETWINTHREAD()	TlsGetValue(tlskey)

/*
 * The following single Tls key is used to store the nsthread
 * structure.  It's initialized in DllMain.
 */

static DWORD		tlskey;


/*
 *----------------------------------------------------------------------
 *
 * DllMain --
 *
 *	Thread library DLL main, managing each thread's WinThread
 *	structure and the master critical section lock.
 *
 * Results:
 *	TRUE.
 *
 * Side effects:
 *	On error will abort process.
 *
 *----------------------------------------------------------------------
 */

BOOL APIENTRY
DllMain(HANDLE hModule, DWORD why, LPVOID lpReserved)
{
    WinThread *wPtr;

    switch (why) {
    case DLL_PROCESS_ATTACH:
	NsThreads_LibInit();
	/* FALLTHROUGH */

    case DLL_THREAD_ATTACH:
	wPtr = ns_calloc(1, sizeof(WinThread));
	wPtr->event = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (wPtr->event == NULL) {
	    NsThreadFatal("DllMain", "CreateEvent", GetLastError());
	}
	if (!TlsSetValue(tlskey, wPtr)) {
	    NsThreadFatal("DllMain", "TlsSetValue", GetLastError());
	}
	break;

    case DLL_THREAD_DETACH:
	/*
	 * Note this code does not execute for the final thread on
	 * exit because the TLS callbacks may invoke code from an
	 * unloaded DLL, e.g., Tcl.
	 */

	wPtr = TlsGetValue(tlskey);
	if (wPtr) {
	    NsCleanupTls(wPtr->slots);
	    if (!CloseHandle(wPtr->event)) {
	        NsThreadFatal("DllMain", "CloseHandle", GetLastError());
	    }
	    if (!TlsSetValue(tlskey, NULL)) {
	        NsThreadFatal("DllMain", "TlsSetValue", GetLastError());
	    }
	    ns_free(wPtr);
	}
	break;

    case DLL_PROCESS_DETACH:
	if (!TlsFree(tlskey)) {
	    NsThreadFatal("DllMain", "TlsFree", GetLastError());
	}
	break;
    }
    return TRUE;
}


/*
 *----------------------------------------------------------------------
 *
 * NsInitThreads --
 *
 *	Core threads init.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
NsInitThreads(void)
{
    tlskey = TlsAlloc();
    if (tlskey == 0xFFFFFFFF) {
	NsThreadFatal("NsInitThreads", "TlsAlloc", GetLastError());
    }
}


/*
 *----------------------------------------------------------------------
 *
 * NsGetStack --
 *
 *	Return stack info.
 *
 * Results:
 *	Unsupport, always returns 0.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
NsGetStack(void **addrPtr, size_t *sizePtr)
{
    return 0;
}


/*
 *----------------------------------------------------------------------
 *
 * NsGetTls --
 *
 *	Return the TLS slots for this thread.
 *
 * Results:
 *	Pointer to slots array.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void **
NsGetTls(void)
{
    WinThread *wPtr = GETWINTHREAD();

    return wPtr->slots;
}


/*
 *----------------------------------------------------------------------
 *
 * NsThreadLibName --
 *
 *	Return the string name of the thread library.
 *
 * Results:
 *	Pointer to static string.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char *
NsThreadLibName(void)
{
    return "win32";
}


/*
 *----------------------------------------------------------------------
 *
 * NsLockAlloc --
 *
 *	Allocate and initialize a mutex lock.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void *
NsLockAlloc(void)
{
    return ns_calloc(1, sizeof(Lock));
}


/*
 *----------------------------------------------------------------------
 *
 * NsLockFree --
 *
 *	Free a mutex lock.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
NsLockFree(void *lock)
{
    ns_free(lock);
}


/*
 *----------------------------------------------------------------------
 *
 * NsLockSet --
 *
 *	Set a mutex lock.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May wait wakeup event if lock already held.
 *
 *----------------------------------------------------------------------
 */

void
NsLockSet(void *lock)
{
    WinThread *wPtr = NULL;
    Lock *lockPtr = lock;
    int locked;

    do {
	SPINLOCK(&lockPtr->spinlock);
	locked = lockPtr->locked;
	if (!locked) {
	    lockPtr->locked = 1;
	} else {
	    if (wPtr == NULL) {
		wPtr = GETWINTHREAD();
	    }
	    Queue(&lockPtr->waitPtr, wPtr);
	}
	SPINUNLOCK(&lockPtr->spinlock);
	if (locked && WaitForSingleObject(wPtr->event, INFINITE) != WAIT_OBJECT_0) {
	    NsThreadFatal("NsLockTry", "WaitForSingleObject", GetLastError());
	}
    } while (locked);
}


/*
 *----------------------------------------------------------------------
 *
 * NsLockTry --
 *
 *	Try to set a mutex lock once.
 *
 * Results:
 *	1 if lock set, 0 otherwise.
 *
 * Side effects:
 * 	None.
 *
 *----------------------------------------------------------------------
 */

int
NsLockTry(Lock *lock)
{
    Lock *lockPtr = lock;
    int busy;

    SPINLOCK(&lockPtr->spinlock);
    busy = lockPtr->locked;
    if (!busy) {
	lockPtr->locked = 1;
    }
    SPINUNLOCK(&lockPtr->spinlock);
    if (busy) {
	return 0;
    }
    return 1;
}


/*
 *----------------------------------------------------------------------
 *
 * NsLockUnset --
 *
 *	Unset a mutex lock.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May signal wakeup event for a waiting thread.
 *
 *----------------------------------------------------------------------
 */

void
NsLockUnset(void *lock)
{
    Lock *lockPtr = lock;
    WinThread *wPtr;

    SPINLOCK(&lockPtr->spinlock);
    lockPtr->locked = 0;
    wPtr = lockPtr->waitPtr;
    if (wPtr != NULL) {
	lockPtr->waitPtr = wPtr->nextPtr;
	wPtr->nextPtr = NULL;
    }
    SPINUNLOCK(&lockPtr->spinlock);

    /*
     * NB: It's safe to send the Wakeup() signal after spin unlock
     * because the waiting thread is in an infiniate wait.
     */

    if (wPtr != NULL) {
	Wakeup(wPtr, "NsLockUnset");
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CondInit --
 *
 *	Initialize a condition variable.  Note that this function is rarely
 *	called directly as condition variables are now self initialized
 *	when first accessed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CondInit(Ns_Cond *cond)
{
    *cond = ns_calloc(1, sizeof(Cond));
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CondDestroy --
 *
 *	Destroy a previously initialized condition variable.  Note this
 *	function is almost never called as condition variables
 *	normally exist until the process exits.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CondDestroy(Ns_Cond *cond)
{
    if (*cond != NULL) {
    	ns_free(*cond);
    	*cond = NULL;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CondSignal --
 *
 *	Signal a condition variable, releasing a single thread if one
 *	is waiting.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A single waiting thread may be resumed.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CondSignal(Ns_Cond *cond)
{
    Cond         *condPtr = GetCond(cond);
    WinThread	 *wPtr;

    SPINLOCK(&condPtr->spinlock);
    wPtr = condPtr->waitPtr;
    if (wPtr != NULL) {
	condPtr->waitPtr = wPtr->nextPtr;
	wPtr->nextPtr = NULL;
	wPtr->condwait = 0;

	/*
	 * NB: Unlike with NsLockUnset, the Wakeup() must be done
	 * before the spin unlock as the other thread may have 
	 * been in a timed wait which just timed out.
	 */

	Wakeup(wPtr, "Ns_CondSignal");
    }
    SPINUNLOCK(&condPtr->spinlock);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CondBroadcast --
 *
 *	Broadcast a condition, resuming all waiting threads, if any.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	First thread, if any, is awoken.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CondBroadcast(Ns_Cond *cond)
{
    Cond         *condPtr = GetCond(cond);
    WinThread	 *wPtr;

    SPINLOCK(&condPtr->spinlock);

    /*
     * Set each thread to wake up the next thread on the
     * waiting list.  This results in a rolling wakeup
     * which should reduce lock contention as the threads
     * are awoken.
     */

    wPtr = condPtr->waitPtr;
    while (wPtr != NULL) {
	wPtr->wakeupPtr = wPtr->nextPtr;
	wPtr->nextPtr = NULL;
	wPtr->condwait = 0;
	wPtr = wPtr->wakeupPtr;
    }

    /*
     * Wake up the first thread to start the rolling 
     * wakeup.
     */

    wPtr = condPtr->waitPtr;
    if (wPtr != NULL) {
	condPtr->waitPtr = NULL;
	/* NB: See Wakeup() comment in Ns_CondSignal(). */
	Wakeup(wPtr, "Ns_CondBroadcast");
    }

    SPINUNLOCK(&condPtr->spinlock);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CondWait --
 *
 *	Wait indefinitely for a condition to be signaled.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_CondWait(Ns_Cond *cond, Ns_Mutex *mutex)
{
    (void) Ns_CondTimedWait(cond, mutex, NULL);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_CondTimedWait --
 *
 *	Wait for a condition to be signaled up to a given absolute time
 *	out.  This code is very tricky to avoid the race condition
 *	between locking and unlocking the coordinating mutex and catching
 *	a wakeup signal.  Be sure you understand how condition variables
 *	work before screwing around with this code.
 *
 * Results:
 *	NS_OK on signal being received within the timeout period,
 *	otherwise NS_TIMEOUT.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Ns_CondTimedWait(Ns_Cond *cond, Ns_Mutex *mutex, Ns_Time *timePtr)
{
    int		    status;
    Cond           *condPtr;
    WinThread	   *wPtr, **waitPtrPtr;
    Ns_Time 	    now, wait;
    DWORD	    msec, w;

    /*
     * Convert to relative wait time and verify.
     */

    if (timePtr == NULL) {
	msec = INFINITE;
    } else {
	Ns_GetTime(&now);
	Ns_DiffTime(timePtr, &now, &wait);
	wait.usec /= 1000;
	if (wait.sec < 0 || (wait.sec == 0 && wait.usec <= 0)) {
	    return NS_TIMEOUT;
	}
	msec = wait.sec * 1000 + wait.usec;
    }

    /*
     * Lock the condition and add this thread to the end of
     * the wait list.
     */

    condPtr = GetCond(cond);
    wPtr = GETWINTHREAD();
    SPINLOCK(&condPtr->spinlock);
    wPtr->condwait = 1;
    Queue(&condPtr->waitPtr, wPtr);
    SPINUNLOCK(&condPtr->spinlock);

    /*
     * Release the outer mutex and wait for the signal to arrive
     * or timeout.
     */

    Ns_MutexUnlock(mutex);
    w = WaitForSingleObject(wPtr->event, msec);
    if (w != WAIT_OBJECT_0 && w != WAIT_TIMEOUT) {
	NsThreadFatal("Ns_CondTimedWait", "WaitForSingleObject", GetLastError());
    }

    /*
     * Lock the condition and check if wakeup was signalled.  Note
     * that the signal may have arrived as the event was timing
     * out so the return of WaitForSingleObject can't be relied on.
     * If there was no wakeup, remove this thread from the list.
     */

    SPINLOCK(&condPtr->spinlock);
    if (!wPtr->condwait) {
	status = NS_OK;
    } else {
	status = NS_TIMEOUT;
	waitPtrPtr = &condPtr->waitPtr;
	while (*waitPtrPtr != wPtr) {
	    waitPtrPtr = &(*waitPtrPtr)->nextPtr;
	}
	*waitPtrPtr = wPtr->nextPtr;
	wPtr->nextPtr = NULL;
	wPtr->condwait = 0;
    }

    /*
     * Wakeup the next thread in a rolling broadcast if necessary.
     * Note, as with Ns_CondSignal, the wakeup must be sent while
     * the spin lock is held.
     */

    if (wPtr->wakeupPtr != NULL) {
	Wakeup(wPtr->wakeupPtr, "Ns_CondTimedWait");
	wPtr->wakeupPtr = NULL;
    }
    SPINUNLOCK(&condPtr->spinlock);

    /*
     * Re-aquire the outer lock and return.
     */

    Ns_MutexLock(mutex);
    return status;
}


/*
 *----------------------------------------------------------------------
 *
 * NsCreateThread --
 *
 *	WinThread specific thread create function called by
 *	Ns_ThreadCreate.  Note the use of _beginthreadex.  CreateThread
 *	does not initialize the C runtime library fully and could
 *	lead to memory leaks on thread exit.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Depends on thread startup routine.
 *
 *----------------------------------------------------------------------
 */

void
NsCreateThread(void *arg, long stacksize, Ns_Thread *resultPtr)
{
    ThreadArg *argPtr;
    unsigned hdl, tid, flags;

    flags = (resultPtr ? CREATE_SUSPENDED : 0);
    argPtr = ns_malloc(sizeof(ThreadArg));
    argPtr->arg = arg;
    argPtr->self = NULL;
    hdl = _beginthreadex(NULL, stacksize, ThreadMain, argPtr, flags, &tid);
    if (hdl == 0) {
    	NsThreadFatal("NsCreateThread", "_beginthreadex", errno);
    }
    if (resultPtr == NULL) {
	CloseHandle((HANDLE) hdl);
    } else {
	argPtr->self = (HANDLE) hdl;
	ResumeThread(argPtr->self);
	*resultPtr = (Ns_Thread) hdl;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadExit --
 *
 *	Terminate a thread.  Note the use of _endthreadex instead of
 *	ExitThread which, as mentioned above, is correct.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Thread will clean itself up via the DllMain thread detach code.
 *
 *----------------------------------------------------------------------
 */

void
Ns_ThreadExit(void *arg)
{
    _endthreadex((unsigned) arg);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadJoin --
 *
 *	Wait for exit of a non-detached thread.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Requested thread is destroyed after join.
 *
 *----------------------------------------------------------------------
 */

void
Ns_ThreadJoin(Ns_Thread *thread, void **argPtr)
{
    HANDLE hdl = (HANDLE) *thread;
    LONG exitcode;

    if (WaitForSingleObject(hdl, INFINITE) != WAIT_OBJECT_0) {
	NsThreadFatal("Ns_ThreadJoin", "WaitForSingleObject", GetLastError());
    }
    if (!GetExitCodeThread(hdl, &exitcode)) {
	NsThreadFatal("Ns_ThreadJoin", "GetExitCodeThread", GetLastError());
    }
    if (!CloseHandle(hdl)) {
	NsThreadFatal("Ns_ThreadJoin", "CloseHandle", GetLastError());
    }
    if (argPtr != NULL) {
	*argPtr = (void *) exitcode;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadYield --
 *
 *	Yield the cpu to another thread.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Ns_ThreadYield(void)
{
    Sleep(0);
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadId --
 *
 *	Return the numeric thread id.
 *
 * Results:
 *	Integer thread id.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Ns_ThreadId(void)
{
    return (int) GetCurrentThreadId();
}


/*
 *----------------------------------------------------------------------
 *
 * Ns_ThreadSelf --
 *
 *	Return thread handle suitable for Ns_ThreadJoin.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Value at threadPtr is updated with thread's handle.
 *
 *----------------------------------------------------------------------
 */

void
Ns_ThreadSelf(Ns_Thread *threadPtr)
{
    WinThread *wPtr = GETWINTHREAD();

    *threadPtr = (Ns_Thread) wPtr->self;
}


/*
 *----------------------------------------------------------------------
 *
 * ThreadMain --
 *
 *	Win32 thread startup which simply calls NsThreadMain.
 *
 * Results:
 *	Does not return.
 *
 * Side effects:
 *	NsThreadMain will call Ns_ThreadExit.
 *
 *----------------------------------------------------------------------
 */

static unsigned __stdcall
ThreadMain(void *arg)
{
    WinThread *wPtr = GETWINTHREAD();
    ThreadArg *argPtr = arg;

    wPtr->self = argPtr->self;
    arg = argPtr->arg;
    ns_free(argPtr);
    NsThreadMain(arg);
    /* NOT REACHED */
    return 0;
}


/*
 *----------------------------------------------------------------------
 *
 * Queue --
 *
 *	Add a thread on a mutex or condition wait queue.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Thread wakeup event is reset in case it's holding
 *	a lingering wakeup.
 *
 *----------------------------------------------------------------------
 */

static void
Queue(WinThread **waitPtrPtr, WinThread *wPtr)
{
    while (*waitPtrPtr != NULL) {
	waitPtrPtr = &(*waitPtrPtr)->nextPtr;
    }
    *waitPtrPtr = wPtr;
    wPtr->nextPtr = wPtr->wakeupPtr = NULL;
    if (!ResetEvent(wPtr->event)) {
	NsThreadFatal("Queue", "ResetEvent", GetLastError());
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Wakeup --
 *
 *	Wakeup a thread waiting on a mutex or condition queue.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Thread wakeup event is set.
 *
 *----------------------------------------------------------------------
 */

static void
Wakeup(WinThread *wPtr, char *func)
{
    if (!SetEvent(wPtr->event)) {
	NsThreadFatal(func, "SetEvent", GetLastError());
    }
}


/*
 *----------------------------------------------------------------------
 *
 * GetCond --
 *
 *	Return the Cond struct for given Ns_Cond, initializing
 *	if necessary.
 *
 * Results:
 *	Pointer to Cond.
 *
 * Side effects:
 *	Ns_Cond may be updated.
 *
 *----------------------------------------------------------------------
 */

static Cond *
GetCond(Ns_Cond *cond)
{
    if (*cond == NULL) {
	Ns_MasterLock();
	if (*cond == NULL) {
	    Ns_CondInit(cond);
	}
	Ns_MasterUnlock();
    }
    return (Cond *) *cond;
}


/*
 *----------------------------------------------------------------------
 *
 * opendir --
 *
 *	Start a directory search.
 *
 * Results:
 *	Pointer to DIR.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

DIR *
opendir(char *pathname)
{
    Search *sPtr;
    char pattern[PATH_MAX];

    if (strlen(pathname) > PATH_MAX - 3) {
	errno = EINVAL;
	return NULL;
    }
    sprintf(pattern, "%s/*", pathname);
    sPtr = ns_malloc(sizeof(Search));
    sPtr->handle = _findfirst(pattern, &sPtr->fdata);
    if (sPtr->handle == -1) {
	ns_free(sPtr);
	return NULL;
    }
    sPtr->ent.d_name = NULL;
    return (DIR *) sPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * closedir --
 *
 *	Closes and active directory search.
 *
 * Results:
 *	0.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
closedir(DIR *dp)
{
    Search *sPtr = (Search *) dp;

    _findclose(sPtr->handle);
    ns_free(sPtr);
    return 0;
}


/*
 *----------------------------------------------------------------------
 *
 * readdir --
 *
 *	Returns the next file in an active directory search.
 *
 * Results:
 *	Pointer to thread-local struct dirent.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

struct dirent  *
readdir(DIR * dp)
{
    Search *sPtr = (Search *) dp;

    if (sPtr->ent.d_name != NULL
	    && _findnext(sPtr->handle, &sPtr->fdata) != 0) {
	return NULL;
    }
    sPtr->ent.d_name = sPtr->fdata.name;
    return &sPtr->ent;
}
