/*
 * dirent.c
 *
 * emulation of
 *
 *   - closedir()
 *   - opendir()
 *   - readdir()
 *
 * (c) 1996 by Dirk Ohme
 */

/*---| includes |------------------------------------------------------------*/
#define INCL_DOSFILEMGR
#include <os2.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "errno.h"
#include "dirent.h"

/*---| structures |----------------------------------------------------------*/
typedef struct DIR_s {                          /* directory search struct   */
        int              ciNumber;              /*   - number of record      */
        char            *pszPath;               /*   - dir. search string    */
        HDIR             hDirSearch;            /*   - search handle         */
        FILEFINDBUF3     sBuffer;               /*   - search buffer         */
        struct dirent    sDirEnt;               /*   - current entry         */
} DIR_t;                                        /*---------------------------*/

/*---| local prototypes |----------------------------------------------------*/

/*---------------------------------------------------------------------------
 * name:        closedir()
 * input:       pDIR            pointer to directory search structure
 * output:      int     == 0    success
 *                      != 0    on error, error code set in errno
 *                      EBADF           bad pointer
 * function:    ends access to an open directory structure
 *---------------------------------------------------------------------------
 */
int closedir( DIR *pDir )
{
        /*
        ** check for correct pointer
        */
        if( NULL == pDir )
        {
                errno = EBADF;
                return -1;
        }

        /*
        ** free memory
        */
        if( HDIR_CREATE != ((DIR_t *) pDir)->hDirSearch )
                DosFindClose( ((DIR_t *) pDir)->hDirSearch );
        if( NULL != ((DIR_t *) pDir)->pszPath )
                free( ((DIR_t *) pDir)->pszPath );
        free( pDir );

        /*
        ** return success
        */
        return (errno = 0);

} /* closedir() */

/*---------------------------------------------------------------------------
 * name:        opendir()
 * input:       pszPath         path and directory to be accessed
 * output:      DIR *   != NULL success, pointer to dir access structure
 *                      == NULL error, error code set in errno
 *                      EACCES          no permission to read directory
 *                      EBADF           bad pointer
 *                      EMFILE          too many file descriptors open
 *                      ENAMETOOLONG    path in 'pszPath' too long
 *                      ENFILE          too many file descriptors open
 *                      ENOENT          directory does not exist
 *                      ENOTDIR         invalid path specifier
 * function:    starts access to a directory
 *---------------------------------------------------------------------------
 */
DIR *opendir( char *pszPath )
{
        DIR_t           *pDir;
        char            *psz;
        int              cb;

        /*
        ** check pointers
        */
        if( NULL == pszPath || '\0' == *pszPath )
        {
                errno = EBADF;
                return NULL;
        }
        if( MAXNAMLEN < (cb=strlen(pszPath)) )
        {
                errno = ENAMETOOLONG;
                return NULL;
        }

        /*
        ** allocate memory for structure
        */
        if( NULL == (pDir=malloc(sizeof(DIR_t))) ||
            NULL == (pDir->pszPath=malloc(cb + 3)) )
        {
                if( NULL != pDir )
                        free( pDir );
                errno = EACCES;
                return NULL;
        }

        /*
        ** translate path:
        **   - convert '/' to '\'
        **   - add terminating '\' to end of path
        **   - add '*' for search mask (DosFind...)
        */
        cb = 0;
        while( '\0' != *pszPath )
        {
                switch( *pszPath )
                {
                        case '/':
                                pDir->pszPath[cb++] = '\\';
                                break;
                        default:
                                pDir->pszPath[cb++] = *pszPath;
                } /* switch */

                pszPath++;

        } /* while */
        if( '\\' != pDir->pszPath[cb - 1] )
                pDir->pszPath[cb++] = '\\';
        pDir->pszPath[cb++] = '*';
        pDir->pszPath[cb]   = '\0';

        /*
        ** initiate search
        */
        cb               = 1;
        pDir->ciNumber   = 0;
        pDir->hDirSearch = HDIR_CREATE;
        memset( &(pDir->sBuffer), 0, sizeof(FILEFINDBUF3) );
        memset( &(pDir->sDirEnt), 0, sizeof(struct dirent) );
        if( 0 != DosFindFirst( pDir->pszPath, &(pDir->hDirSearch),
                               FILE_NORMAL | FILE_READONLY  | FILE_ARCHIVED |
                               FILE_HIDDEN | FILE_DIRECTORY | FILE_SYSTEM,
                               (PVOID) &(pDir->sBuffer), sizeof(FILEFINDBUF3),
                               (ULONG *) &cb, FIL_STANDARD ) )
        {
                free( pDir->pszPath );
                free( pDir );
                errno = ENOTDIR;
                return NULL;
        }

        /*
        ** return success
        */
        return (void *) pDir;

} /* opendir() */

/*---------------------------------------------------------------------------
 * name:        readdir()
 * input:       pDIR            pointer to directory search structure
 * output:      struct dirent   pointer to current entry
 *                      EACCES          no permission to read directory
 *                      EBADF           bad pointer
 *                      EMFILE          too many file descriptors open
 *                      ENAMETOOLONG    path in 'pszPath' too long
 *                      ENFILE          too many file descriptors open
 *                      ENOENT          directory does not exist
 *                      ENOTDIR         invalid path specifier
 * function:    returns the next entry of the directory
 *---------------------------------------------------------------------------
 */
struct dirent *readdir( DIR *pDir )
{
        /*
        ** check for correct pointer
        */
        if( NULL == pDir )
        {
                errno = EBADF;
                return NULL;
        }

        /*
        ** check for search status
        */
        if( HDIR_CREATE == ((DIR_t *) pDir)->hDirSearch )
        {
                errno = ENOENT;
                return NULL;
        }

        /*
        ** check for '.' and '..'
        */
        ((DIR_t *) pDir)->ciNumber++;
        if( 1 == ((DIR_t *) pDir)->ciNumber &&
            0 != strcmp(".", ((DIR_t *) pDir)->sBuffer.achName) )
        {
                /*---| simulate an '.' entry |---*/
                ((DIR_t *) pDir)->sDirEnt.d_off     = 0;
                ((DIR_t *) pDir)->sDirEnt.d_ino     = 0;
                ((DIR_t *) pDir)->sDirEnt.d_fileno  =
                                                   ((DIR_t *) pDir)->ciNumber;
                ((DIR_t *) pDir)->sDirEnt.d_reclen  = sizeof( struct dirent );
                ((DIR_t *) pDir)->sDirEnt.d_namlen  = 1;
                ((DIR_t *) pDir)->sDirEnt.d_name[0] = '.';
                ((DIR_t *) pDir)->sDirEnt.d_name[1] = '\0';
        }
        else if( 2 == ((DIR_t *) pDir)->ciNumber &&
                 0 != strcmp("..", ((DIR_t *) pDir)->sBuffer.achName) )
        {
                /*---| simulate an '..' entry |---*/
                ((DIR_t *) pDir)->sDirEnt.d_off     = 0;
                ((DIR_t *) pDir)->sDirEnt.d_ino     = 0;
                ((DIR_t *) pDir)->sDirEnt.d_fileno  =
                                                   ((DIR_t *) pDir)->ciNumber;
                ((DIR_t *) pDir)->sDirEnt.d_reclen  = sizeof( struct dirent );
                ((DIR_t *) pDir)->sDirEnt.d_namlen  = 2;
                ((DIR_t *) pDir)->sDirEnt.d_name[0] = '.';
                ((DIR_t *) pDir)->sDirEnt.d_name[1] = '.';
                ((DIR_t *) pDir)->sDirEnt.d_name[2] = '\0';
        }
        else
        {
                int             cb;

                /*---| copy entry |---*/
                ((DIR_t *) pDir)->sDirEnt.d_off     = 0;
                ((DIR_t *) pDir)->sDirEnt.d_ino     = 0;
                ((DIR_t *) pDir)->sDirEnt.d_fileno  =
                                                   ((DIR_t *) pDir)->ciNumber;
                ((DIR_t *) pDir)->sDirEnt.d_reclen  = sizeof( struct dirent );
                ((DIR_t *) pDir)->sDirEnt.d_namlen  =
                                            ((DIR_t *) pDir)->sBuffer.cchName;
                strcpy( ((DIR_t *) pDir)->sDirEnt.d_name,
                        ((DIR_t *) pDir)->sBuffer.achName );

                /*---| find next entry |---*/
                cb = 1;
                if( 0 != DosFindNext( ((DIR_t *) pDir)->hDirSearch,
                                      (PVOID) &(((DIR_t *) pDir)->sBuffer),
                                      sizeof(FILEFINDBUF3), (ULONG *) &cb ) )
                {
                        DosFindClose( ((DIR_t *) pDir)->hDirSearch );
                        ((DIR_t *) pDir)->hDirSearch = HDIR_CREATE;
                }

        }

        /*
        ** return success
        */
        errno = 0;
        return &(((DIR_t *) pDir)->sDirEnt);

} /* readdir() */

/*===| end of file |=========================================================*/
