/* mman.c
 *
 * emulation of
 *   - getpagesize()
 *   - mlockall()
 *   - mmap() -> newMmap()
 *   - mprotect()
 *   - msync()
 *   - munmap(), newMunmap()
 *   - munlockall()
 *
 * (c) 1996 by Dirk Ohme
 *
 * valid definitions:
 *   - DEBUG       debugging mode
 */

/*---| definitions |---------------------------------------------------------*/
        /*
        ** block size, must be something of 2^n
        */
#define MMAP_BLOCKSIZE  0x1000

        /*
        ** extension shift factor: factor is used at first allocation size
        **                         for specifying the extension
        **                         ( 2 = two-times, 4 = four-times, etc. )
        */
#define MMAP_EXTENSION  4

/*---| includes |------------------------------------------------------------*/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#ifdef OS2
#  include <io.h>
#else
#  include <fcntl.h>
#  include <sys/types.h>
#  include <unistd.h>
#endif
#include <memory.h>

#include "errno.h"
#include "mman.h"

/*---| local types |---------------------------------------------------------*/
typedef struct {                                 /* internal mmap list       */
        int        ciBlock;                      /*   - block counter        */
        int        cbBlock;                      /*   - block size           */
        caddr_t    pVarAddr;                     /*   - referencing pointer  */
        caddr_t    pMemAddr;                     /*   - address location     */
        int        cbMemSize;                    /*   - address size         */
        int        hFile;                        /*   - associated file      */
        int        cbOffset;                     /*   - offset in file       */
        int        fProtection;                  /*   - protection mode      */
        void      *pNext;                        /*   - pointer to next      */
} MmapList, *PMmapList;                          /*--------------------------*/

/*---| local variables |-----------------------------------------------------*/
static PMmapList   pMmapListRoot  = NULL;        /* root of mmap list        */
static int         ciMmapBlock    = 0;           /* index counter            */


/*---------------------------------------------------------------------------
 * name:     getpagesize()
 * input:    --none--
 * output:   int        - size of system page in bytes
 * function: returns number of bytes of a system page
 *---------------------------------------------------------------------------
 */
int getpagesize( void )
{
        /*
        ** return something ;-)
        */
#ifdef DEBUG
        fprintf( stderr, "> mman::getpagesize()\n" );
        fprintf( stderr, "< mman::getpagesize() - %d\n", MMAP_BLOCKSIZE );
#endif
        return MMAP_BLOCKSIZE;

} /* getpagesize() */

/*---------------------------------------------------------------------------
 * name:     mprotect()
 * input:    pAddr      - address for which to set parameters
 *           cbLen      - length of address space to set parameters
 *           fProtection- protection flags
 *                           PROT_READ      address space can be read
 *                           PROT_WRITE     address space can be written
 *                           PROT_EXEC      address space can be executed
 *                           PROT_NONE      address space cannot be accessed
 * output:   int   == 0 : no error
 *                 != 0 : error, error code is in errno
 *                           EACCES         protection flag is invalid
 *                           EINVAL         invalid address space pointer
 *                           ENOMEM         address space not valid
 * function: sets the protection of a address space
 *---------------------------------------------------------------------------
 */
int mprotect ( caddr_t pAddr, int cbLen, int fProtection )
{
        PMmapList  pMmapList;
        int        ciHits;

        /*
        ** scan list for possible entries
        */
#ifdef DEBUG
        fprintf( stderr,
                 "> mman::mprotect(0x%08X, %d, 0x%04X)\n",
                 pAddr, cbLen, fProtection );
#endif
        ciHits    = 0;
        pMmapList = pMmapListRoot;
        while( NULL != pMmapList )
        {
                /*
                ** check for entries lying between pAddr and pAddr+cbLen
                */
                if( (int) (pMmapList->pMemAddr) >= (int) pAddr &&
                    (int) (pMmapList->pMemAddr) <= (int) pAddr + cbLen )
                {
                        /*
                        ** set new protection mode
                        */
#ifdef DEBUG
                        fprintf( stderr,
                                 "    set protection 0x%04X on record %d\n",
                                 fProtection, pMmapList->ciBlock );
#endif
                        pMmapList->fProtection = fProtection;
                        ciHits++;
                }

                /*
                ** next entry
                */
                pMmapList = (PMmapList) pMmapList->pNext;

        } /* while */

        /*
        ** check for success
        */
        if( 0 == ciHits )
        {
                errno = EINVAL;
#ifdef DEBUG
                fprintf( stderr, "< mman::mprotect() - -1\n" );
#endif
                return -1;
        }
#ifdef DEBUG
        fprintf( stderr, "< mman::mprotect() - 0\n" );
#endif
        return (errno = 0);

} /* mprotect() */

/*---------------------------------------------------------------------------
 * name:     mlockall()
 * input:    fFlags     - operation mode on address spaces
 *                           MCL_CURRENT    lock current mappings
 *                           MCL_FUTURE     lock current mappings
 * output:   int   == 0 - success
 *                 != 1 - error, error code set in errno
 *                           EAGAIN         insufficient ressources
 *                                          (already locked?)
 *                           EINVAL         invalid flags
 *                           EPERM          not allowed to do locking
 * function: sets locking mode
 *---------------------------------------------------------------------------
 */
int mlockall( int fFlags )
{
        /*
        ** dummy, return success
        */
#ifdef DEBUG
        fprintf( stderr, "> mman::mlockall(0x%04X)\n", fFlags );
        fprintf( stderr, "< mman::mlockall() - 0\n" );
#endif
        return (errno = 0);

} /* mlockall() */

/*---------------------------------------------------------------------------
 * name:     newMmap()
 * input:    pVarAddr   - pointer to variable used for block addressing
 *           pAddr      - suggestion for address to be taken (or freed)
 *           cbLen      - size to be allocated
 *           fProtection- protection flags
 *                           PROT_READ      address space can be read
 *                           PROT_WRITE     address space can be written
 *                           PROT_EXEC      address space can be executed
 *                           PROT_NONE      address space cannot be accessed
 *           fFlags     - flags
 *                           MAP_SHARED     address space can be shared
 *                           MAP_PRIVATE    address space cannot be shared
 *                           MAP_TYPE       mask for type of mapping
 *                           MAP_FIXED      pAddr is a fix parameter, no
 *                                          suggestion
 *           hFile      - file handle
 *           cbOffset   - offset in file
 * output:   caddr_t    - pointer to address space (on MAP_FIX equals pAddr)
 *                        == NULL on errors, error code set in errno
 *                           EACCES         protection flag is invalid
 *                           EAGAIN         one or more spaces can't be locked
 *                           EBADF          invalid file hande (not open)
 *                           EINVAL         invalid flags
 *                           ENODEV         invalid file handle (wrong type)
 *                           ENOMEM         address space not valid
 *                           ENXIO          invalid address space / size
 *                           EPERM          not allowed to do locking
 * function: maps a file at a given offset into an address space
 *---------------------------------------------------------------------------
 */
caddr_t newMmap( caddr_t pVarAddr,    caddr_t pAddr,  size_t cbLen,
                 int     fProtection, int     fFlags, int    hFile,
                 off_t   cbOffset )
{
        PMmapList  pMmapList;
        PMmapList  pMmapRef;
        int        cbBlock;

        /*
        ** check file handle by setting offset
        */
#ifdef DEBUG
        fprintf( stderr,
                 "> mman::newMmap(0x%08X, 0x%08X, %d, 0x%04X, 0x%04X, %d, %d)\n",
                 pVarAddr, pAddr, cbLen, fProtection, fFlags, hFile, cbOffset );
#endif
        if( 0 > lseek(hFile, cbOffset, SEEK_SET) )
        {
                errno = EBADF;
#ifdef DEBUG
                fprintf( stderr, "< mman::newMmap() - (caddr_t) -1\n" );
#endif
                return (caddr_t) -1;
        }

        /*
        ** remove previous mappings
        */
        munmap( pAddr, cbLen );
        if( NULL != pVarAddr )
                *pVarAddr = NULL;

        /*
        ** calculate number of bytes in block
        */
        cbBlock  = (cbLen + MMAP_BLOCKSIZE) & (-1 ^ (MMAP_BLOCKSIZE - 1));
        cbBlock *= MMAP_EXTENSION;
#ifdef DEBUG
        fprintf( stderr, "    %d bytes reserved for block\n", cbBlock );
#endif

        /*
        ** check for blocks with adequate settings (size > current)
        */
        pMmapRef  = NULL;
        pMmapList = pMmapListRoot;
        while( NULL != pMmapList )
        {
                if( hFile    ==  pMmapList->hFile &&
                    cbOffset ==  pMmapList->cbOffset &&
                    cbLen    <=  pMmapList->cbBlock )
                {
                        pMmapRef = pMmapList;
                        cbBlock  = pMmapList->cbBlock;
                }

                pMmapList = (caddr_t) pMmapList->pNext;

        } /* while */
#ifdef DEBUG
        if( NULL == pMmapRef )
                fprintf( stderr, "    no overlapping block\n" );
        else
                fprintf( stderr,
                         "    overlapping block %d\n",
                         pMmapRef->ciBlock );
#endif

        /*
        ** insert new entry at top of list
        */
        if( NULL == (pMmapList = malloc(sizeof(MmapList))) )
        {
                errno = ENOMEM;
#ifdef DEBUG
                fprintf( stderr, "< mman::newMmap() - (caddr_t) -1\n" );
#endif
                return (caddr_t) -1;
        }
        if( NULL == pMmapRef )
        {
                if( NULL == (pMmapList->pMemAddr = malloc(cbBlock)) )
                {
                        free( pMmapList );
                        errno = ENOMEM;
#ifdef DEBUG
                        fprintf( stderr, "< mman::newMmap() - (caddr_t) -1\n" );
#endif
                        return (caddr_t) -1;
                }
                pMmapList->cbBlock     = cbBlock;
        }
        else
        {
                pMmapList->pMemAddr    = pMmapRef->pMemAddr;
                pMmapList->cbBlock     = pMmapRef->cbBlock;
        }
        if( NULL != pMmapListRoot )
                pMmapList->pNext = pMmapListRoot;
        else
                pMmapList->pNext = NULL;
        pMmapListRoot = pMmapList;

        /*
        ** set entries in list
        */
        pMmapList->ciBlock     = ++ciMmapBlock;
        pMmapList->pVarAddr    = pVarAddr;
        pMmapList->cbMemSize   = cbLen;
        pMmapList->hFile       = hFile;
        pMmapList->cbOffset    = cbOffset;
        pMmapList->fProtection = fProtection;
#ifdef DEBUG
        fprintf( stderr, "    new block %d at 0x%08X\n",
                 ciMmapBlock, pMmapList->pMemAddr );
        fprintf( stderr, "      ref. var.   = 0x%08X\n", pMmapList->pVarAddr );
        fprintf( stderr, "      blocksize   = %10d\n",   pMmapList->cbBlock );
        fprintf( stderr, "      memory size = %10d\n",   pMmapList->cbMemSize );
        fprintf( stderr, "      protection  = 0x%04X\n", pMmapList->fProtection );
        fprintf( stderr, "      flags       = 0x%04X\n", fFlags );
        fprintf( stderr, "      file handle = %10d\n",   pMmapList->hFile );
        fprintf( stderr, "      file offset = %10d\n",   pMmapList->cbOffset );
#endif

        /*
        ** read file contents into memory
        */
#ifdef DEBUG
        fprintf( stderr, "    lseek(%d, %d, SEEK_SET) -> ", hFile, cbOffset );
        fflush( stderr );
#endif
        cbOffset = lseek( hFile, pMmapList->cbOffset, SEEK_SET );
#ifdef DEBUG
        fprintf( stderr, "%d\n", cbOffset );
#endif
        read( pMmapList->hFile, pMmapList->pMemAddr, pMmapList->cbMemSize );

        /*
        ** return success
        */
        errno = 0;
#ifdef DEBUG
        fprintf( stderr, "< mman::newMmap() - 0x%08X\n", pMmapList->pMemAddr );
#endif
        if( NULL != pVarAddr )
                *pVarAddr = pMmapList->pMemAddr;
        return pMmapList->pMemAddr;

} /* newMmap() */

/*---------------------------------------------------------------------------
 * name:     msync()
 * input:    pAddr      - pointer to address space
 *           cbLen      - size of address space
 *           fFlags     - flags describing kind of synchronization
 *                           MS_ASYNC       return immediately
 *                           MS_INVALIDATE  flush caches
 * output:   int   == 0 - success
 *                 != 0 - error, error code set in errno
 *                           EINVAL         pointer to invalid addr. space
 * function: synchronizes address space with disk spaces, i.e. writes all
 *           address spaces to disk
 *---------------------------------------------------------------------------
 */
int msync( caddr_t pAddr, int cbLen, int fFlags )
{
        PMmapList  pMmapList;

        /*
        ** go through list
        */
#ifdef DEBUG
        fprintf( stderr,
                 "> mman::msync(0x%08X, %d, 0x%04X)\n",
                 pAddr, cbLen, fFlags );
#endif
        pMmapList = pMmapListRoot;
        while( NULL != pMmapList )
        {
                if( NULL == pAddr ||
                    ((int) (pMmapList->pMemAddr) >= (int) pAddr &&
                     (int) (pMmapList->pMemAddr) <= (int) pAddr + cbLen) &&
                    (0 != (pMmapList->fProtection & PROT_WRITE)) )
                {
#ifdef DEBUG
                        fprintf( stderr, "    writing block %d\n",
                                 pMmapList->ciBlock );
#endif
                        lseek( pMmapList->hFile,
                               pMmapList->cbOffset,
                               SEEK_SET );
                        write( pMmapList->hFile,
                               pMmapList->pMemAddr,
                               pMmapList->cbMemSize );
                } /* if */
                pMmapList = (PMmapList) pMmapList->pNext;

        } /* while */

        /*
        ** return success
        */
#ifdef DEBUG
        fprintf( stderr, "< mman::msync() - 0\n" );
#endif
        return (errno = 0);

} /* msync() */

/*---------------------------------------------------------------------------
 * name:     munlockall()
 * input:    --none--
 * output:   int   == 0 - success
 *                 != 0 - error, error code set in errno
 *                           EPERM          not allowed to do unlocking
 * function: unlocks all address spaces
 *---------------------------------------------------------------------------
 */
int munlockall( void )
{
        /*
        ** dummy, return success
        */
#ifdef DEBUG
        fprintf( stderr, "> mman::munlockall()\n" );
        fprintf( stderr, "< mman::munlockall() - 0\n" );
#endif
        return (errno = 0);

} /* munlockall() */

/*---------------------------------------------------------------------------
 * name:     munmap()
 * input:    pAddr      - pointer to address space
 *                           -1             unmap all
 *           cbLen      - size of address space
 * output:   int   == 0 - success
 *                 != 0 - error, error code set in errno
 *                           EINVAL         pointer to invalid addr. space
 * function: frees an address space
 *---------------------------------------------------------------------------
 */
int munmap( caddr_t pAddr, int cbLen )
{
        PMmapList  pMmapList;
        PMmapList  pMmapPrev;
        PMmapList  pMmapTemp;
        int        ciHits;
        int        ciRefs;

        /*
        ** perform synchronization
        */
#ifdef DEBUG
        fprintf( stderr, "> mman::munmap(0x%08X, %d)\n", pAddr, cbLen );
#endif
        msync( NULL, 0, MS_ASYNC | MS_INVALIDATE );

        /*
        ** scan list for possible entries
        */
        pMmapList = pMmapListRoot;
        pMmapPrev = NULL;
        while( NULL != pMmapList )
        {
                /*
                ** remove entries lying between pAddr and pAddr+cbLen
                */
                if( (caddr_t) -1 == pAddr ||
                    ((int) (pMmapList->pMemAddr) >= (int) pAddr &&
                     (int) (pMmapList->pMemAddr) <= (int) pAddr + cbLen) )
                {
                        /*
                        ** check for references
                        */
                        ciRefs    = 0;
                        pMmapTemp =  pMmapListRoot;
                        while( NULL != pMmapTemp )
                        {
                                if( pMmapList->pMemAddr == pMmapTemp->pMemAddr )
                                        ciRefs++;
                                pMmapTemp = (PMmapList) pMmapTemp->pNext;
                        } /* while */
#ifdef DEBUG
                        fprintf( stderr,
                                 "    number of references = %d\n",
                                 ciRefs );
#endif

                        /*
                        ** free allocated address space
                        */
                        if( 1 >= ciRefs )
                        {
#ifdef DEBUG
                                fprintf( stderr,
                                         "    freeing block %d with memory\n",
                                         pMmapList->ciBlock );
#endif
                                free( pMmapList->pMemAddr );
                        }
#ifdef DEBUG
                        else
                        {
                                fprintf( stderr,
                                         "    freeing block %d w/o memory\n",
                                         pMmapList->ciBlock );
                        }
#endif

                        /*
                        ** adjust internal mmap list
                        */
                        if( NULL == pMmapPrev )
                                pMmapListRoot    = (PMmapList)pMmapList->pNext;
                        else
                                pMmapPrev->pNext = (PMmapList)pMmapList->pNext;
                        pMmapTemp = pMmapList;
                        pMmapList = pMmapList->pNext;

                        /*
                        ** free list space
                        */
                        if( NULL != pMmapTemp->pVarAddr )
                        {
#ifdef DEBUG
                                fprintf( stderr,
                                         "    NULLing variable at 0x%08X\n",
                                         pMmapTemp->pVarAddr );
#endif
                                *(pMmapTemp->pVarAddr) = NULL;
                        }
                        free( pMmapTemp );
                        ciHits++;
                }
                else
                {
                        /*
                        ** next entry
                        */
                        pMmapPrev = pMmapList;
                        pMmapList = (PMmapList) pMmapList->pNext;
                }

        } /* while */

        /*
        ** check for success
        */
        if( 0 == ciHits )
        {
                errno = EINVAL;
#ifdef DEBUG
                fprintf( stderr, "< mman::munmap() - -1\n" );
#endif
                return -1;
        }
#ifdef DEBUG
        fprintf( stderr, "< mman::munmap() - 0\n" );
#endif
        return (errno = 0);

} /* munmap() */

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