//------------------------------------------------------------------------------
//	Desc:	Class for doing file directory operations.
// Tabs:	3
//
// Copyright (c) 1998-2007 Novell, Inc. All Rights Reserved.
//
// 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; version 2.1
// of the License.
//
// 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
// Library 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, contact Novell, Inc.
//
// To contact Novell about this file by physical or electronic mail, 
// you may find current contact information at www.novell.com.
//
// $Id$
//------------------------------------------------------------------------------

#include "ftksys.h"

#define ERR_NO_FILES_FOUND                      0xFF
#define ERR_INVALID_PATH                        0x9C

#if defined( FLM_WIN)

	#define F_IO_FA_NORMAL			FILE_ATTRIBUTE_NORMAL		// Normal file
	#define F_IO_FA_RDONLY			FILE_ATTRIBUTE_READONLY		// Read only attribute
	#define F_IO_FA_HIDDEN			FILE_ATTRIBUTE_HIDDEN		// Hidden file
	#define F_IO_FA_SYSTEM			FILE_ATTRIBUTE_SYSTEM		// System file
	#define F_IO_FA_VOLUME			FILE_ATTRIBUTE_VOLUME		// Volume label
	#define F_IO_FA_DIRECTORY		FILE_ATTRIBUTE_DIRECTORY	// Directory
	#define F_IO_FA_ARCHIVE			FILE_ATTRIBUTE_ARCHIVE		// Archive
	
	FSTATIC FLMBOOL f_fileMeetsFindCriteria(
		F_IO_FIND_DATA *		pFindData);

#elif defined( FLM_UNIX) || defined( FLM_LIBC_NLM)

	#define F_IO_FA_NORMAL			0x01	// Normal file, no attributes
	#define F_IO_FA_RDONLY			0x02	// Read only attribute
	#define F_IO_FA_HIDDEN			0x04	// Hidden file
	#define F_IO_FA_SYSTEM			0x08	// System file
	#define F_IO_FA_VOLUME			0x10	// Volume label
	#define F_IO_FA_DIRECTORY		0x20	// Directory
	#define F_IO_FA_ARCHIVE			0x40	// Archive
	
	FSTATIC int Find1(
		char *				FindTemplate,
		F_IO_FIND_DATA *	DirInfo);

	FSTATIC int Find2(
		F_IO_FIND_DATA *	DirStuff);

	FSTATIC FLMBYTE ReturnAttributes(
		mode_t		FileMode,
		char *		pszFileName);

	FSTATIC int RetrieveFileStat(
		char *			FilePath,
		struct stat	*	StatusRec);

#elif !defined( FLM_RING_ZERO_NLM)

	#error Platform not supported

#endif

RCODE f_fileFindFirst(
	char *				pszSearchPath,
	FLMUINT				uiSearchAttrib,
	F_IO_FIND_DATA	*	find_data,
	char *				pszFoundPath,
	FLMUINT *			puiFoundAttrib);

RCODE f_fileFindNext(
	F_IO_FIND_DATA *	pFindData,
	char *				pszFoundPath,
	FLMUINT *			puiFoundAttrib);

void f_fileFindClose(
	F_IO_FIND_DATA *		pFindData);

/****************************************************************************
Desc:
****************************************************************************/
RCODE f_allocDirHdl(
	F_DirHdl **				ppDirHdl)
{
	if( (*ppDirHdl = f_new F_DirHdl) == NULL)
	{
		return( RC_SET( NE_FLM_MEM));
	}
	
	return( NE_FLM_OK);
}
		
/****************************************************************************
Desc:
****************************************************************************/
F_DirHdl::F_DirHdl()
{
	m_rc = NE_FLM_OK;
	m_bFirstTime = TRUE;
	m_bFindOpen = FALSE;
	m_uiAttrib = 0;
	m_szPattern[ 0] = '\0';
}

/****************************************************************************
Desc:
****************************************************************************/
F_DirHdl::~F_DirHdl()
{
#ifndef FLM_RING_ZERO_NLM
	if( m_bFindOpen)
	{
		f_fileFindClose( &m_FindData);
	}
#endif
}

/****************************************************************************
Desc:
****************************************************************************/
const char * FTKAPI F_DirHdl::currentItemName( void)
{
	const char *	pszName = NULL;
	
#ifndef FLM_RING_ZERO_NLM

	if( RC_OK( m_rc))
	{
		pszName = m_szFileName;
	}
	
#else

	FLMUINT			uiLength;

	if( RC_OK( m_rc))
	{
		if( !m_FindData.pCurrentItem)
		{
			return( NULL);
		}
		
		uiLength = sizeof( m_FindData.ucTempBuffer) - 1;
		if( m_FindData.pCurrentItem->DFileNameLength < uiLength)
		{
			uiLength = m_FindData.pCurrentItem->DFileNameLength;
		}
		
		f_strncpy( m_FindData.ucTempBuffer, 
			(const char *)m_FindData.pCurrentItem->DFileName, uiLength);
		m_FindData.ucTempBuffer[ uiLength] = 0;
		pszName = m_FindData.ucTempBuffer;
	}
	
#endif

	return( pszName);
}

/****************************************************************************
Desc:
****************************************************************************/
FINLINE void FTKAPI F_DirHdl::currentItemPath(
	char *		pszPath)
{
	if( RC_OK( m_rc))
	{
		f_strcpy( pszPath, m_szDirectoryPath);
#ifdef FLM_RING_ZERO_NLM
		f_pathAppend( pszPath, currentItemName());
#else
		f_pathAppend( pszPath, m_szFileName);
#endif
	}
}

/****************************************************************************
Desc:
****************************************************************************/
FLMBOOL FTKAPI F_DirHdl::currentItemIsDir( void)
{
#if defined( FLM_WIN) || defined( FLM_UNIX) || defined( FLM_LIBC_NLM)

	return( ((m_uiAttrib & F_IO_FA_DIRECTORY)
						 ? TRUE
						 : FALSE));
						 
#elif defined( FLM_RING_ZERO_NLM)

	if( !m_FindData.pCurrentItem)
	{
		return( FALSE);
	}

	return( (m_FindData.pCurrentItem->DFileAttributes & SUBDIRECTORY_BIT)
							? TRUE 
							: FALSE);
							
#else

	#error Platform not supported
		
#endif
}

/****************************************************************************
Desc:
****************************************************************************/
FLMUINT64 FTKAPI F_DirHdl::currentItemSize( void)
{
	FLMUINT64	ui64Size = 0;

	if( RC_OK( m_rc))
	{
#if defined( FLM_WIN)
		ui64Size = (((FLMUINT64)m_FindData.findBuffer.nFileSizeHigh) << 32) + 
						m_FindData.findBuffer.nFileSizeLow;
#elif defined( FLM_UNIX) || defined ( FLM_LIBC_NLM)
		ui64Size = m_FindData.FileStat.st_size;
#elif defined( FLM_RING_ZERO_NLM)
		char		szTmpPath[ F_PATH_MAX_SIZE];

		currentItemPath( szTmpPath);
		(void)f_getFileSysPtr()->getFileSize( szTmpPath, &ui64Size);
#endif
	}
	return( ui64Size);
}

/****************************************************************************
Desc:	Get the next item in a directory
****************************************************************************/
#if defined( FLM_WIN) || defined( FLM_UNIX) || defined( FLM_LIBC_NLM)
RCODE FTKAPI F_DirHdl::next( void)
{
	char					szFoundPath[ F_PATH_MAX_SIZE];
	char					szDummyPath[ F_PATH_MAX_SIZE];
	FLMUINT				uiSearchAttributes;
	FLMUINT				uiFoundAttrib;
	IF_FileSystem *	pFileSystem = f_getFileSysPtr();

	if( RC_BAD( m_rc))
	{
		goto Exit;
	}

	uiSearchAttributes =
		F_IO_FA_NORMAL | F_IO_FA_RDONLY | F_IO_FA_ARCHIVE | F_IO_FA_DIRECTORY;

	for( ;;)
	{
		if( m_bFirstTime)
		{
			m_bFirstTime = FALSE;

			if( RC_BAD( m_rc = f_fileFindFirst( m_szDirectoryPath, 
				uiSearchAttributes, &m_FindData, szFoundPath, &uiFoundAttrib)))
			{
				goto Exit;
			}
			
			m_bFindOpen = TRUE;
			m_uiAttrib = uiFoundAttrib;
		}
		else
		{
			if( RC_BAD( m_rc = f_fileFindNext( &m_FindData, 
				szFoundPath, &uiFoundAttrib)))
			{
				goto Exit;
			}
			
			m_uiAttrib = uiFoundAttrib;
		}

		if( RC_BAD( m_rc = pFileSystem->pathReduce( szFoundPath, 
			szDummyPath, m_szFileName)))
		{
			goto Exit;
		}

		if( pFileSystem->doesFileMatch( m_szFileName, m_szPattern))
		{
			break;
		}
	}

Exit:

	return( m_rc);
}
#endif

/****************************************************************************
Desc:	Get the next item in a directory
****************************************************************************/
#if defined( FLM_RING_ZERO_NLM)
RCODE FTKAPI F_DirHdl::next( void)
{
	LONG					lError = 0;
	IF_FileSystem *	pFileSystem = f_getFileSysPtr();
	
	if( RC_BAD( m_rc))
	{
		goto Exit;
	}

	for( ;;)
	{
		if( (lError = DirectorySearch( 0, m_FindData.lVolumeNumber, 
			m_FindData.lDirectoryNumber, LONGNameSpace, 
			m_FindData.lCurrentEntryNumber, (BYTE *)"\x02\xFF*",
			-1, &m_FindData.pCurrentItem, 
			&m_FindData.lCurrentEntryNumber)) != 0)
		{
			if( (lError == ERR_NO_FILES_FOUND) || (lError == ERR_INVALID_PATH))
			{
				m_rc = RC_SET( NE_FLM_IO_NO_MORE_FILES);
			}
			else
			{
				m_rc = f_mapPlatformError( lError, NE_FLM_READING_FILE);
			}
			
			break;
		}

		if( pFileSystem->doesFileMatch( 
			(const char *)m_FindData.pCurrentItem->DFileName, m_szPattern))
		{
			break;
		}
	}
	
Exit:

	return( m_rc);
}
#endif

/****************************************************************************
Desc:	Open a directory
****************************************************************************/
#ifndef FLM_RING_ZERO_NLM
RCODE FTKAPI F_DirHdl::openDir(
	const char *	pszDirName,
	const char *	pszPattern)
{
	RCODE		rc = NE_FLM_OK;

	m_rc = NE_FLM_OK;
	m_bFirstTime = TRUE;
	m_bFindOpen = FALSE;
	m_uiAttrib = 0;

	f_memset( &m_FindData, 0, sizeof( m_FindData));
	f_strcpy( m_szDirectoryPath, pszDirName);

	if( pszPattern)
	{
		if( f_strlen( pszPattern) >= (FLMINT)sizeof( m_szPattern))
		{
			rc = RC_SET( NE_FLM_MEM);
			goto Exit;
		}

		f_strcpy( m_szPattern, pszPattern);
	}
	else
	{
		m_szPattern[ 0] = 0;
	}

Exit:

	return( rc);
}
#endif

/****************************************************************************
Desc:		Open a directory
Notes:
			1. DOS file names, not long file names !  If we want to support long
				file names, then increase the size of the filename buffer and change 
				the name space.
			2. '*.*' doesn't work as a pattern.  '*' seems to do the trick.
			3. These Netware APIs are case sensitive.  If you want to specify a 
				a pattern like "*.db"  make sure that the files you are looking for
				were created with lowercase "db" extensions.
				
				The path needs to match the case also.  For example, 
				sys:\_netware won't work.  SYS:\_NETWARE will.
			4. Server names are not supported by ConvertPathString
				'Connecting to remote servers' is not supported by this code.
****************************************************************************/
#ifdef FLM_RING_ZERO_NLM
RCODE FTKAPI F_DirHdl::openDir(
	const char *	pszDirName,
	const char *	pszPattern)
{
	RCODE			rc = NE_FLM_OK;
	LONG			lError = 0;
	LONG			unused;	
	FLMBYTE		pseudoLNamePath[ F_PATH_MAX_SIZE + 1];
	FLMBYTE		LNamePath[ F_PATH_MAX_SIZE];
	LONG			lLNamePathCount;

	m_rc = NE_FLM_OK;
	m_bFirstTime = TRUE;
	m_bFindOpen = FALSE;
	m_uiAttrib = 0;
	f_strcpy( m_szDirectoryPath, pszDirName);
	
	f_memset( &m_FindData, 0, sizeof( m_FindData));
	m_FindData.lVolumeNumber = F_NW_DEFAULT_VOLUME_NUMBER;
	m_FindData.lCurrentEntryNumber = 0xFFFFFFFF;
	m_FindData.lDirectoryNumber = 0xFFFFFFFF;
	
	LNamePath[0] = 0;
	lLNamePathCount = 0;

	f_strcpy( (char *)&pseudoLNamePath[1], pszDirName);
	pseudoLNamePath[ 0] = (FLMBYTE)f_strlen( (const char *)&pseudoLNamePath[ 1]);
	
	if( (lError = ConvertPathString( 0, 0, pseudoLNamePath, 
		&m_FindData.lVolumeNumber,
		&unused, (BYTE *)LNamePath, &lLNamePathCount)) != 0)
	{
		goto Exit;
	}
											
	if( (lError = MapPathToDirectoryNumber( 0, m_FindData.lVolumeNumber, 0, 
		(BYTE *)LNamePath, lLNamePathCount, LONGNameSpace, 
		&m_FindData.lDirectoryNumber, &unused)) != 0)
	{
		goto Exit;
	}
		
	if( pszPattern)
	{
		if( f_strlen( pszPattern) >= (FLMINT)sizeof( m_szPattern))
		{
			rc = RC_SET( NE_FLM_MEM);
			goto Exit;
		}

		f_strcpy( m_szPattern, pszPattern);
	}
	else
	{
		m_szPattern[ 0] = 0;
	}
	
Exit:
	
	if( lError != 0)
	{
		m_rc = f_mapPlatformError( lError, NE_FLM_OPENING_FILE);
	}

	return( m_rc);
}
#endif

/****************************************************************************
Desc:	Create a directory (and parent directories if necessary).
****************************************************************************/
#if defined( FLM_WIN) || defined( FLM_UNIX) || defined( FLM_LIBC_NLM)
RCODE FTKAPI F_DirHdl::createDir(
	const char *	pszDirPath)
{
	RCODE					rc = NE_FLM_OK;
	char *				pszParentDir = NULL;
	IF_FileSystem *	pFileSystem = f_getFileSysPtr();

	if( RC_BAD( rc = f_alloc( F_PATH_MAX_SIZE, &pszParentDir)))
	{
		goto Exit;
	}

	// Discover the parent directory of the given one

	if( RC_BAD( rc = pFileSystem->pathReduce( pszDirPath, 
		pszParentDir, NULL)))
	{
		goto Exit;
	}

	// If pathReduce couldn't reduce the path at all, then an
	// invalid path was supplied.

	if( f_strcmp( pszDirPath, pszParentDir) == 0)
	{
		rc = RC_SET( NE_FLM_IO_INVALID_FILENAME);
		goto Exit;
	}

	// If a parent directory was found, and it doesn't already exist, create it

	if( *pszParentDir)
	{
		// If the "parent" is actually a regular file we need to return an error

		if( RC_OK( pFileSystem->doesFileExist( pszParentDir)))
		{
			if( !pFileSystem->isDir( pszParentDir))
			{
				rc = RC_SET( NE_FLM_IO_ACCESS_DENIED);
				goto Exit;
			}
		}
		else if( RC_BAD( rc = createDir( pszParentDir)))
		{
			goto Exit;
		}
	}

#if defined( FLM_WIN)

	if( !CreateDirectory((LPTSTR)pszDirPath, NULL))
	{
		rc = f_mapPlatformError( GetLastError(), NE_FLM_CREATING_FILE);
	}

#elif defined( FLM_UNIX) || defined( FLM_LIBC_NLM)

	if( mkdir( (char *)pszDirPath, 0777) == -1)
	{
		rc = f_mapPlatformError( errno, NE_FLM_CREATING_FILE);
	}

#endif

Exit:

	if( pszParentDir)
	{
		f_free( &pszParentDir);
	}
	
	return( rc);
}
#endif

/****************************************************************************
Desc:
****************************************************************************/
#if defined( FLM_RING_ZERO_NLM)
RCODE FTKAPI F_DirHdl::createDir(
	const char *	pszDirPath)
{
	RCODE				rc = NE_FLM_OK;
	FLMBYTE			pucPseudoLNamePath[ F_PATH_MAX_SIZE + 1];
	FLMBYTE			pucLNamePath[ F_PATH_MAX_SIZE];
	LONG				lVolumeID;
	LONG				lPathID;
	LONG				lLNamePathCount;
	LONG				lNewDirectoryID;
	void *			pNotUsed;
	LONG				lErrorCode;
	
	f_strcpy( (char *)&pucPseudoLNamePath[1], pszDirPath);
	pucPseudoLNamePath[0] = (FLMBYTE)f_strlen( pszDirPath);
	
	if( (lErrorCode = ConvertPathString( 0, 0, pucPseudoLNamePath, &lVolumeID,		
		&lPathID, pucLNamePath, &lLNamePathCount)) != 0)
	{
		goto Exit;
	}

	if( (lErrorCode = CreateDirectory( 0, lVolumeID, lPathID, pucLNamePath,
		lLNamePathCount, LONGNameSpace, MaximumDirectoryAccessBits, 
		&lNewDirectoryID, &pNotUsed)) != 0)
	{
		goto Exit;
	}

Exit:

	if( lErrorCode)
	{
		rc = f_mapPlatformError( lErrorCode, NE_FLM_CREATING_FILE);
	}
	
	return( rc);
}
#endif

/****************************************************************************
Desc:		Remove a directory
Notes:	The directory must be empty.
****************************************************************************/
RCODE FTKAPI F_DirHdl::removeDir(
	const char *	pszDirName)
{
#if defined( FLM_WIN)

	if( !RemoveDirectory((LPTSTR)pszDirName))
	{
		return( f_mapPlatformError( GetLastError(), NE_FLM_IO_DELETING_FILE));
	}

	return( NE_FLM_OK);

#elif defined( FLM_UNIX) || defined( FLM_LIBC_NLM)

	 if( rmdir( (char *)pszDirName) == -1)
	 {
		 return( f_mapPlatformError( errno, NE_FLM_IO_DELETING_FILE));
	 }

    return( NE_FLM_OK);
	 
#elif defined( FLM_RING_ZERO_NLM)

	return( f_netwareRemoveDir( pszDirName));

#endif
}

/****************************************************************************
Desc:
****************************************************************************/
#if defined( FLM_RING_ZERO_NLM)
RCODE f_netwareRemoveDir( 
	const char *		pszDirName)
{
	RCODE			rc = NE_FLM_OK;
	FLMBYTE		pucPseudoLNamePath[ F_PATH_MAX_SIZE + 1];
	FLMBYTE		pucLNamePath[ F_PATH_MAX_SIZE];
	LONG			lVolumeID;
	LONG			lPathID;
	LONG			lLNamePathCount;
	LONG			lErrorCode;
	
	f_strcpy( (char *)&pucPseudoLNamePath[1], pszDirName);
	pucPseudoLNamePath[0] = (FLMBYTE)f_strlen( pszDirName);
	
	if( (lErrorCode = ConvertPathString( 0, 0, pucPseudoLNamePath, &lVolumeID,		
		&lPathID, pucLNamePath, &lLNamePathCount)) != 0)
	{
		goto Exit;
	}

	if( (lErrorCode = DeleteDirectory( 0, lVolumeID, lPathID, pucLNamePath,
		lLNamePathCount, LONGNameSpace)) != 0)
	{
		goto Exit;
	}

Exit:

	if( lErrorCode)
	{
		rc = f_mapPlatformError( lErrorCode, NE_FLM_IO_DELETING_FILE);
	}
	
	return( rc);
}
#endif
	
/****************************************************************************
Desc:		Find the first file that matches the supplied criteria
****************************************************************************/
#ifdef FLM_WIN
RCODE f_fileFindFirst(
	char *				pszSearchPath,
   FLMUINT				uiSearchAttrib,
	F_IO_FIND_DATA	*	pFindData,
   char *				pszFoundPath,
	FLMUINT *			puiFoundAttrib)
{
	RCODE					rc = NE_FLM_OK;
	char 					szTmpPath[ F_PATH_MAX_SIZE];
   char *				pszWildCard = "*.*";
	IF_FileSystem *	pFileSystem = f_getFileSysPtr();

	f_memset( pFindData, 0, sizeof( F_IO_FIND_DATA));
	pFindData->findHandle = INVALID_HANDLE_VALUE;
	pFindData->uiSearchAttrib = uiSearchAttrib;

	if( !pszSearchPath)
	{
		rc = RC_SET( NE_FLM_IO_PATH_NOT_FOUND);
		goto Exit;
	}

	f_strcpy( pFindData->szSearchPath, pszSearchPath);

	if( uiSearchAttrib & F_IO_FA_NORMAL )
	{
		uiSearchAttrib |= F_IO_FA_ARCHIVE;
	}

	f_strcpy( szTmpPath, pszSearchPath);
	
	if( RC_BAD( rc = pFileSystem->pathAppend( szTmpPath, pszWildCard)))
	{
		goto Exit;
	}

   if( (pFindData->findHandle = FindFirstFile( (LPTSTR)szTmpPath,
	      &(pFindData->findBuffer))) == INVALID_HANDLE_VALUE)
   {
		rc = f_mapPlatformError( GetLastError(), NE_FLM_OPENING_FILE);
		goto Exit;
	}

	// Loop until a file with correct attributes is found

	for( ;;)
	{
		if( f_fileMeetsFindCriteria( pFindData))
		{
			break;
		}

		if( FindNextFile( pFindData->findHandle,
			&(pFindData->findBuffer)) == FALSE)
		{
			rc = f_mapPlatformError( GetLastError(), NE_FLM_READING_FILE);
			goto Exit;
		}
	}

	// Append the file name to the path name

	f_strcpy( pszFoundPath, pFindData->szSearchPath);
	
	if( RC_BAD( rc = pFileSystem->pathAppend( pszFoundPath, 
		(char *)pFindData->findBuffer.cFileName)))
	{
		goto Exit;
	}

	// Return the found file attribute

   *puiFoundAttrib = pFindData->findBuffer.dwFileAttributes;

Exit:

	if( RC_BAD( rc) && pFindData &&
		pFindData->findHandle != INVALID_HANDLE_VALUE)
	{
		f_fileFindClose( pFindData);
	}

	return( rc);
}
#endif

/****************************************************************************
Desc:		Find the first file that matches the supplied criteria
****************************************************************************/
#if defined( FLM_UNIX) || defined( FLM_LIBC_NLM)
RCODE f_fileFindFirst(
	char *				pszSearchPath,
   FLMUINT				uiSearchAttrib,
	F_IO_FIND_DATA	*	pFindData,
   char *				pszFoundPath,
	FLMUINT *			puiFoundAttrib)
{
	RCODE					rc = NE_FLM_OK;
	char 					szTmpPath[ F_PATH_MAX_SIZE];
	FSTATIC char		pszWildCard[] = {'*',0};
	int					iRetVal;
	IF_FileSystem *	pFileSystem = f_getFileSysPtr();

	if( !pszSearchPath)
	{
		rc = RC_SET( NE_FLM_IO_PATH_NOT_FOUND);
		goto Exit;
	}

	f_strcpy( szTmpPath, pszSearchPath);
	if( RC_BAD( rc = pFileSystem->pathAppend( szTmpPath, pszWildCard)))
	{
		goto Exit;
	}

	f_memset( pFindData, 0, sizeof( F_IO_FIND_DATA));
	if( uiSearchAttrib & F_IO_FA_DIRECTORY)
	{
		pFindData->mode_flag |= S_IFDIR;
	}

	if( uiSearchAttrib & F_IO_FA_RDONLY)
	{
		pFindData->mode_flag |= S_IREAD;
	}

	iRetVal = Find1( (char*)szTmpPath, pFindData);

	if( iRetVal != 0)
	{
		// If there were no more files found then return no more files
		// instead of mapping to error path not found or io error.
		// To return no more files ret_val is ENOENT (set in Find2)
		// and errno is not set

		if( iRetVal == ENOENT && errno == 0)
		{
			rc = RC_SET( NE_FLM_IO_NO_MORE_FILES);
		}
		else
		{
			rc = f_mapPlatformError( errno, NE_FLM_READING_FILE);
		}
		
		goto Exit;
	}

	// filter out ".." (PARENT) and "." (CURRENT) directories
	
	if( uiSearchAttrib & F_IO_FA_DIRECTORY )
	{
		while( (f_strcmp( pFindData->name, "..") == 0) ||
			   (f_strcmp( pFindData->name, ".") == 0))
		{
			if( (iRetVal = Find2( pFindData)) != 0)
			{
				// If there were no more files found then return no more files
				// instead of mapping to error path not found or io error.
				// To return no more files ret_val is ENOENT (set in Find2)
				// and errno is not set
				
				if( iRetVal == ENOENT && errno == 0)
				{
					rc = RC_SET( NE_FLM_IO_NO_MORE_FILES);
				}
				else
				{
					rc = f_mapPlatformError( errno, NE_FLM_READING_FILE);
				}
				
				goto Exit;
			}
		}
	}

	// Append the file name to the path name
	
	f_strcpy( pszFoundPath, pszSearchPath);
	
	if( RC_BAD( rc = pFileSystem->pathAppend( pszFoundPath, 
		(char *)pFindData->name)))
	{
		goto Exit;
	}

	*puiFoundAttrib = (FLMUINT)ReturnAttributes(
			pFindData->FileStat.st_mode, pszFoundPath);

	// Save the search path in the NE_FLM_IO_FIND_DATA struct
	// for a find next call

	f_strcpy( pFindData->search_path, pszSearchPath);

Exit:

	return( rc);
}
#endif

/****************************************************************************
Desc:		Find the next file that matches the supplied criteria
****************************************************************************/
#ifdef FLM_WIN
RCODE f_fileFindNext(
	F_IO_FIND_DATA *	pFindData,
	char *				pszFoundPath,
	FLMUINT *			puiFoundAttrib)
{
	RCODE					rc = NE_FLM_OK;
	IF_FileSystem *	pFileSystem = f_getFileSysPtr();

   if( FindNextFile( pFindData->findHandle,
		&(pFindData->findBuffer)) == FALSE)
	{
		rc = f_mapPlatformError( GetLastError(), NE_FLM_READING_FILE);
		goto Exit;
	}

	// Loop until a file with correct attributes is found

	for( ;;)
	{
		if( f_fileMeetsFindCriteria( pFindData))
		{
			break;
		}

		if( FindNextFile( pFindData->findHandle,
			&(pFindData->findBuffer)) == FALSE)
		{
			rc = f_mapPlatformError( GetLastError(), NE_FLM_READING_FILE);
			goto Exit;
		}
	}

	// Append the file name to the path name

	f_strcpy( pszFoundPath, pFindData->szSearchPath);
	
	if( RC_BAD( rc = pFileSystem->pathAppend( pszFoundPath, 
		(char *)pFindData->findBuffer.cFileName)))
	{
		goto Exit;
	}

	// Return the found file attribute

   *puiFoundAttrib = pFindData->findBuffer.dwFileAttributes;

Exit:

   return( rc);
}
#endif

/****************************************************************************
Desc:		Find the next file that matches the supplied criteria
****************************************************************************/
#if defined( FLM_UNIX) || defined( FLM_LIBC_NLM)
RCODE f_fileFindNext(
	F_IO_FIND_DATA *	pFindData,
	char *				pszFoundPath,
	FLMUINT *			puiFoundAttrib)
{
	RCODE					rc = NE_FLM_OK;
	IF_FileSystem *	pFileSystem = f_getFileSysPtr();
	int					iRetVal;

	if( (iRetVal =  Find2( pFindData)) != 0)
	{
		// If there were no more files found then return no more files
		// instead of mapping to error path not found or io error.
		// To return no more files ret_val is ENOENT (set in Find2)
		// and errno is not set
		
		if( iRetVal == ENOENT && errno == 0)
		{
			return( RC_SET( NE_FLM_IO_NO_MORE_FILES));
		}
		
		return( f_mapPlatformError( errno, NE_FLM_READING_FILE));
	}

	// Append the file name to the path name
	
	f_strcpy( pszFoundPath, pFindData->search_path);
	
	if( RC_BAD( rc = pFileSystem->pathAppend( pszFoundPath, 
		(char *)pFindData->name)))
	{
		goto Exit;
	}

	*puiFoundAttrib = (FLMUINT)ReturnAttributes(
			pFindData->FileStat.st_mode, pszFoundPath);

Exit:

   return( rc);
}
#endif

/****************************************************************************
Desc:		Releases any memory allocated to an F_IO_FIND_DATA structure
****************************************************************************/
#ifdef FLM_WIN
void f_fileFindClose(
	F_IO_FIND_DATA *	pFindData)
{
	// Don't call it on an already closed or invalid handle.

	if( pFindData->findHandle != INVALID_HANDLE_VALUE)
	{
		FindClose( pFindData->findHandle );
		pFindData->findHandle = INVALID_HANDLE_VALUE;
	}
}
#endif

/****************************************************************************
Desc:
****************************************************************************/
#if defined( FLM_UNIX) || defined ( FLM_LIBC_NLM)
void f_fileFindClose(
	F_IO_FIND_DATA *	pFindData)
{
	if( pFindData->globbuf.gl_pathv)
	{
		pFindData->globbuf.gl_offs = 0;
		globfree( &pFindData->globbuf);
		pFindData->globbuf.gl_pathv = 0;
	}
}
#endif

/****************************************************************************
Desc:		Find the next file that matches the supplied criteria
****************************************************************************/
#ifdef FLM_WIN
FSTATIC FLMBOOL f_fileMeetsFindCriteria(
	F_IO_FIND_DATA *		pFindData)
{
	// Fail ".." (PARENT) and "." (CURRENT) directories.  Then,
	// if the file found possesses any of the search attributes, it's
	// a match.

	if( !((f_strcmp( pFindData->findBuffer.cFileName, "..") == 0) ||
    (f_strcmp( pFindData->findBuffer.cFileName, ".") == 0) ||
	 (!(pFindData->uiSearchAttrib & F_IO_FA_DIRECTORY) &&
		(pFindData->findBuffer.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))))
	{
		if( (pFindData->findBuffer.dwFileAttributes &
				pFindData->uiSearchAttrib) ||
			((pFindData->uiSearchAttrib & F_IO_FA_NORMAL) &&
				(pFindData->findBuffer.dwFileAttributes == 0)))
		{
			return( TRUE);
		}
	}

	return( FALSE);
}
#endif

/****************************************************************************
Desc:		Search for file names matching FindTemplate (UNIX)
****************************************************************************/
#if defined( FLM_UNIX) || defined( FLM_LIBC_NLM)
FSTATIC int Find1(
	char *				FindTemplate,
	F_IO_FIND_DATA *	DirInfo)
{
	char  	MaskNam[ F_PATH_MAX_SIZE];
	char  	*PathSeparator;
	FLMINT	uiFindLen;
	FLMINT	uiLen;
#ifdef FLM_LIBC_NLM
	char  	szPosixNam[ F_PATH_MAX_SIZE];
	FLMINT	uiCount;				
#endif

	// If supplied template is illegal, return immediately

	if( (FindTemplate == NULL) || (uiFindLen = f_strlen( FindTemplate)) == 0)
	{
		return( EINVAL);
	}

	// Now separate the template into a PATH and a template MASK
	// If no separating slash character found, use current directory
	// as path!

	f_strcpy( DirInfo->full_path, FindTemplate);
	
#ifdef FLM_LIBC_NLM
	if( (( PathSeparator = strrchr( DirInfo->full_path, '/')) == NULL) &&
		( PathSeparator = strrchr( DirInfo->full_path, '\\')) == NULL)
#else
	if( (PathSeparator = strrchr( DirInfo->full_path, '/')) == NULL)
#endif
	{
		(void) getcwd( DirInfo->full_path, F_PATH_MAX_SIZE);
		uiLen = f_strlen( DirInfo->full_path );
		DirInfo->full_path[uiLen] = '/';
		DirInfo->full_path[uiLen+1] = '\0';
		(void) f_strcat( DirInfo->full_path, FindTemplate );
		PathSeparator = strrchr( DirInfo->full_path, '/');
	}

	// Copy the template MASK, and null terminate the PATH
	
	f_strcpy( MaskNam, PathSeparator + 1);
	
	if( ! f_strlen(MaskNam))
	{
		(void) f_strcpy( MaskNam, "*");
	}

	*PathSeparator = '\0';

	// Use ROOT directory if PATH is empty
	
	if( ! f_strlen(DirInfo->full_path))
	{
		(void) f_strcpy( DirInfo->full_path, "/");
	}

	f_strcpy( DirInfo->dirpath, DirInfo->full_path );

	// Open the specified directory.  Return immediately
	// if error detected!

	errno = 0;
	DirInfo->globbuf.gl_pathv = 0;

#ifdef FLM_LIBC_NLM
	// glob does not seem to be able to handle a non-posix path
	// on NetWare.
	for( uiCount = 0; uiCount <= uiFindLen; uiCount++)
	{
		if( FindTemplate[ uiCount] == '\\')
		{
			szPosixNam[ uiCount] = '/';
		}
		else
		{
			szPosixNam[ uiCount] = FindTemplate[ uiCount];
		}
	}
	if( glob( szPosixNam, GLOB_NOSORT, 0, &DirInfo->globbuf) != 0 &&
		 !DirInfo->globbuf.gl_pathc)
#else
	if( glob( FindTemplate, GLOB_NOSORT, 0, &DirInfo->globbuf) != 0 &&
		 !DirInfo->globbuf.gl_pathc)
#endif
	{
		globfree(&DirInfo->globbuf);
		DirInfo->globbuf.gl_pathv = 0;
		return ENOENT;
	}
	
	// Call Find2 to get the 1st matching file

	return( Find2(DirInfo) );
}
#endif


/****************************************************************************
Desc:		Search for file names matching FindTemplate (UNIX)
****************************************************************************/
#if defined( FLM_UNIX) || defined( FLM_LIBC_NLM)
FSTATIC int Find2(
	F_IO_FIND_DATA *		DirStuff)
{
	int			stat;
	glob_t *		pglob = &DirStuff->globbuf;
	char *		pszTmp;
	char *		pszLastSlash;

	errno = 0;

	for( ;;)
	{
		if( pglob->gl_offs == pglob->gl_pathc)
		{
			pglob->gl_offs = 0;
			globfree(pglob);
			pglob->gl_pathv = 0;
			return ENOENT;
		}
		
		// Get status of file

		f_strcpy(DirStuff->full_path, pglob->gl_pathv[pglob->gl_offs++]);
		if( (stat = RetrieveFileStat( DirStuff->full_path,
											 &DirStuff->FileStat)) != 0 )
		{
			// If file name just read from directory is NO
			// longer there (deleted by another process)
			// then just advance to the next file in
			// directory!

			if( stat == ENOENT)
			{
				continue;
			}
			else
			{
				break;
			}
		}

		// If we don't want directories, and current entry
		// is a directory, then skip it!

		if( (! S_ISDIR(DirStuff->mode_flag)) &&
			  S_ISDIR(DirStuff->FileStat.st_mode))
		{
			continue;
		}

		// If we only want regular files and file is NOT
		// regular, then skip it!  This means there is no
		// way to retrieve named pipes, sockets, or links!

		if ( (DirStuff->mode_flag == F_IO_FA_NORMAL) &&
			  (! S_ISREG(DirStuff->FileStat.st_mode)) )
		{
			continue;
		}

		pszTmp = &DirStuff->full_path[ 0];
		pszLastSlash = NULL;
		while( *pszTmp)
		{
			if( *pszTmp == '/')
			{
				pszLastSlash = pszTmp;
			}
			pszTmp++;
		}

		if( pszLastSlash)
		{
			f_strcpy( DirStuff->name, &pszLastSlash[ 1]);
		}
		else
		{
			f_strcpy( DirStuff->name, DirStuff->full_path);
		}
		stat = 0;
		break;
	}
	
	return( stat);
}
#endif

/****************************************************************************
Desc: Return file's attributes (UNIX)
****************************************************************************/
#if defined( FLM_UNIX) || defined( FLM_LIBC_NLM)
FSTATIC FLMBYTE ReturnAttributes(
	mode_t	FileMode,
	char *	fileName)
{
	FLMBYTE  IOmode = 0;

	// Return the found file attribute
	
	if( S_ISDIR( FileMode ) )
	{
		IOmode |= F_IO_FA_DIRECTORY;
	}
	else
	{
		if( access( (char *)fileName, (int)(R_OK | W_OK)) == 0)
		{
			IOmode |= F_IO_FA_NORMAL;
		}
		else if( access( (char *)fileName, (int)R_OK ) == 0)
		{
			IOmode |= F_IO_FA_RDONLY;
		}
	}

	return( IOmode);
}
#endif

/****************************************************************************
Desc: Return file's attributes (UNIX) || (NetWare)
****************************************************************************/
#if defined( FLM_UNIX) || defined( FLM_LIBC_NLM)
FSTATIC int RetrieveFileStat(
	char *			FilePath,
	struct stat	*	StatusRec)
{
	// Get status of last file read from directory, using the standard
	// UNIX stat call

	errno = 0;
	if( stat( FilePath, StatusRec ) == -1)
	{
		if( errno == ENOENT || errno == ELOOP)
		{
			// Get status of symbolic link rather than referenced file!

			errno = 0;
			if( lstat( FilePath, StatusRec ) == -1)
			{
				return( errno);
			}
		}
		else
		{
			return( errno);
		}
	}

	return( 0);
}
#endif
