/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <private/primpl.h>
#include <string.h>
#include <prshm.h>
#include <prerr.h>
#include <prmem.h>

#if defined(PR_HAVE_WIN32_NAMED_SHARED_MEMORY)

extern PRLogModuleInfo *_pr_shm_lm;

/*
 * NSPR-to-NT access right mapping table for file-mapping objects.
 *
 * The OR of these three access masks must equal FILE_MAP_ALL_ACCESS.
 * This is because if a file-mapping object with the specified name
 * exists, CreateFileMapping requests full access to the existing
 * object.
 */
static DWORD filemapAccessTable[] = {
    FILE_MAP_ALL_ACCESS & ~FILE_MAP_WRITE, /* read */
    FILE_MAP_ALL_ACCESS & ~FILE_MAP_READ, /* write */
    0  /* execute */
};

extern PRSharedMemory * _MD_OpenSharedMemory(
    const char *name,
    PRSize      size,
    PRIntn      flags,
    PRIntn      mode
)
{
    char        ipcname[PR_IPC_NAME_SIZE];
    PRStatus    rc = PR_SUCCESS;
    DWORD dwHi, dwLo;
    PRSharedMemory *shm;
    DWORD flProtect = ( PAGE_READWRITE );
    SECURITY_ATTRIBUTES sa;
    LPSECURITY_ATTRIBUTES lpSA = NULL;
    PSECURITY_DESCRIPTOR pSD = NULL;
    PACL pACL = NULL;

    rc = _PR_MakeNativeIPCName( name, ipcname, PR_IPC_NAME_SIZE, _PRIPCShm );
    if ( PR_FAILURE == rc )
    {
        PR_SetError(PR_UNKNOWN_ERROR, 0 );
        PR_LOG(_pr_shm_lm, PR_LOG_DEBUG, ( "PR_OpenSharedMemory: name is invalid"));
        return(NULL);
    }

    shm = PR_NEWZAP( PRSharedMemory );
    if ( NULL == shm )
    {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0 );
        PR_LOG(_pr_shm_lm, PR_LOG_DEBUG, ( "PR_OpenSharedMemory: New PRSharedMemory out of memory"));
        return(NULL);
    }

    shm->ipcname = PR_MALLOC( (PRUint32) (strlen( ipcname ) + 1) );
    if ( NULL == shm->ipcname )
    {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0 );
        PR_LOG(_pr_shm_lm, PR_LOG_DEBUG, ( "PR_OpenSharedMemory: New shm->ipcname out of memory"));
        PR_DELETE(shm);
        return(NULL);
    }

    /* copy args to struct */
    strcpy( shm->ipcname, ipcname );
    shm->size = size;
    shm->mode = mode;
    shm->flags = flags;
    shm->ident = _PR_SHM_IDENT;

    if (flags & PR_SHM_CREATE ) {
        dwHi = (DWORD) (((PRUint64) shm->size >> 32) & 0xffffffff);
        dwLo = (DWORD) (shm->size & 0xffffffff);

        if (_PR_NT_MakeSecurityDescriptorACL(mode, filemapAccessTable,
                                             &pSD, &pACL) == PR_SUCCESS) {
            sa.nLength = sizeof(sa);
            sa.lpSecurityDescriptor = pSD;
            sa.bInheritHandle = FALSE;
            lpSA = &sa;
        }
#ifdef WINCE
        {
            /*
             * This is assuming that the name will never be larger than
             * MAX_PATH.  Should we dynamically allocate?
             */
            PRUnichar wideIpcName[MAX_PATH];
            MultiByteToWideChar(CP_ACP, 0, shm->ipcname, -1,
                                wideIpcName, MAX_PATH);
            shm->handle = CreateFileMappingW(
                              (HANDLE)-1,
                              lpSA,
                              flProtect,
                              dwHi,
                              dwLo,
                              wideIpcName);
        }
#else
        shm->handle = CreateFileMappingA(
                          (HANDLE)-1,
                          lpSA,
                          flProtect,
                          dwHi,
                          dwLo,
                          shm->ipcname);
#endif
        if (lpSA != NULL) {
            _PR_NT_FreeSecurityDescriptorACL(pSD, pACL);
        }

        if ( NULL == shm->handle ) {
            PR_LOG(_pr_shm_lm, PR_LOG_DEBUG,
                   ( "PR_OpenSharedMemory: CreateFileMapping() failed: %s",
                     shm->ipcname ));
            _PR_MD_MAP_DEFAULT_ERROR( GetLastError());
            PR_FREEIF( shm->ipcname )
            PR_DELETE( shm );
            return(NULL);
        } else {
            if (( flags & PR_SHM_EXCL) && ( GetLastError() == ERROR_ALREADY_EXISTS ))  {
                PR_LOG(_pr_shm_lm, PR_LOG_DEBUG,
                       ( "PR_OpenSharedMemory: Request exclusive & already exists",
                         shm->ipcname ));
                PR_SetError( PR_FILE_EXISTS_ERROR, ERROR_ALREADY_EXISTS );
                CloseHandle( shm->handle );
                PR_FREEIF( shm->ipcname )
                PR_DELETE( shm );
                return(NULL);
            } else {
                PR_LOG(_pr_shm_lm, PR_LOG_DEBUG,
                       ( "PR_OpenSharedMemory: CreateFileMapping() success: %s, handle: %d",
                         shm->ipcname, shm->handle ));
                return(shm);
            }
        }
    } else {
#ifdef WINCE
        PR_SetError( PR_NOT_IMPLEMENTED_ERROR, 0 );
        shm->handle = NULL;  /* OpenFileMapping not supported */
#else
        shm->handle = OpenFileMapping( FILE_MAP_WRITE, TRUE, shm->ipcname );
#endif
        if ( NULL == shm->handle ) {
            _PR_MD_MAP_DEFAULT_ERROR( GetLastError());
            PR_LOG(_pr_shm_lm, PR_LOG_DEBUG,
                   ( "PR_OpenSharedMemory: OpenFileMapping() failed: %s, error: %d",
                     shm->ipcname, PR_GetOSError()));
            PR_FREEIF( shm->ipcname );
            PR_DELETE( shm );
            return(NULL);
        } else {
            PR_LOG(_pr_shm_lm, PR_LOG_DEBUG,
                   ( "PR_OpenSharedMemory: OpenFileMapping() success: %s, handle: %d",
                     shm->ipcname, shm->handle ));
            return(shm);
        }
    }
    /* returns from separate paths */
}

extern void * _MD_AttachSharedMemory( PRSharedMemory *shm, PRIntn flags )
{
    PRUint32    access = FILE_MAP_WRITE;
    void        *addr;

    PR_ASSERT( shm->ident == _PR_SHM_IDENT );

    if ( PR_SHM_READONLY & flags ) {
        access = FILE_MAP_READ;
    }

    addr = MapViewOfFile( shm->handle,
                          access,
                          0, 0,
                          shm->size );

    if ( NULL == addr ) {
        _PR_MD_MAP_DEFAULT_ERROR( GetLastError());
        PR_LOG( _pr_shm_lm, PR_LOG_ERROR,
                ("_MD_AttachSharedMemory: MapViewOfFile() failed. OSerror: %d", PR_GetOSError()));
    }

    return( addr );
} /* end _MD_ATTACH_SHARED_MEMORY() */


extern PRStatus _MD_DetachSharedMemory( PRSharedMemory *shm, void *addr )
{
    PRStatus rc = PR_SUCCESS;
    BOOL        wrc;

    PR_ASSERT( shm->ident == _PR_SHM_IDENT );

    wrc = UnmapViewOfFile( addr );
    if ( FALSE == wrc )
    {
        _PR_MD_MAP_DEFAULT_ERROR( GetLastError());
        PR_LOG( _pr_shm_lm, PR_LOG_ERROR,
                ("_MD_DetachSharedMemory: UnmapViewOfFile() failed. OSerror: %d", PR_GetOSError()));
        rc = PR_FAILURE;
    }

    return( rc );
}


extern PRStatus _MD_CloseSharedMemory( PRSharedMemory *shm )
{
    PRStatus rc = PR_SUCCESS;
    BOOL wrc;

    PR_ASSERT( shm->ident == _PR_SHM_IDENT );

    wrc = CloseHandle( shm->handle );
    if ( FALSE == wrc )
    {
        _PR_MD_MAP_DEFAULT_ERROR( GetLastError());
        PR_LOG( _pr_shm_lm, PR_LOG_ERROR,
                ("_MD_CloseSharedMemory: CloseHandle() failed. OSerror: %d", PR_GetOSError()));
        rc = PR_FAILURE;
    }
    PR_FREEIF( shm->ipcname );
    PR_DELETE( shm );

    return( rc );
} /* end _MD_CLOSE_SHARED_MEMORY() */

extern PRStatus _MD_DeleteSharedMemory( const char *name )
{
    return( PR_SUCCESS );
}


/*
** Windows implementation of anonymous memory (file) map
*/
extern PRLogModuleInfo *_pr_shma_lm;

extern PRFileMap* _md_OpenAnonFileMap(
    const char *dirName,
    PRSize      size,
    PRFileMapProtect prot
)
{
    PRFileMap   *fm;
    HANDLE      hFileMap;

    fm = PR_CreateFileMap( (PRFileDesc*)-1, size, prot );
    if ( NULL == fm )  {
        PR_LOG( _pr_shma_lm, PR_LOG_DEBUG,
                ("_md_OpenAnonFileMap(): PR_CreateFileMap(): failed"));
        goto Finished;
    }

    /*
    ** Make fm->md.hFileMap inheritable. We can't use
    ** GetHandleInformation and SetHandleInformation
    ** because these two functions fail with
    ** ERROR_CALL_NOT_IMPLEMENTED on Win95.
    */
    if (DuplicateHandle(GetCurrentProcess(), fm->md.hFileMap,
                        GetCurrentProcess(), &hFileMap,
                        0, TRUE /* inheritable */,
                        DUPLICATE_SAME_ACCESS) == FALSE) {
        PR_SetError( PR_UNKNOWN_ERROR, GetLastError() );
        PR_LOG( _pr_shma_lm, PR_LOG_DEBUG,
                ("_md_OpenAnonFileMap(): DuplicateHandle(): failed"));
        PR_CloseFileMap( fm );
        fm = NULL;
        goto Finished;
    }
    CloseHandle(fm->md.hFileMap);
    fm->md.hFileMap = hFileMap;

Finished:
    return(fm);
} /* end md_OpenAnonFileMap() */

/*
** _md_ExportFileMapAsString()
**
*/
extern PRStatus _md_ExportFileMapAsString(
    PRFileMap *fm,
    PRSize    bufSize,
    char      *buf
)
{
    PRIntn  written;

    written = PR_snprintf( buf, (PRUint32) bufSize, "%d:%" PR_PRIdOSFD ":%ld",
                           (PRIntn)fm->prot, (PROsfd)fm->md.hFileMap, (PRInt32)fm->md.dwAccess );

    PR_LOG( _pr_shma_lm, PR_LOG_DEBUG,
            ("_md_ExportFileMapAsString(): prot: %x, hFileMap: %x, dwAccess: %x",
             fm->prot, fm->md.hFileMap, fm->md.dwAccess ));

    return((written == -1)? PR_FAILURE : PR_SUCCESS);
} /* end _md_ExportFileMapAsString() */


/*
** _md_ImportFileMapFromString()
**
*/
extern PRFileMap * _md_ImportFileMapFromString(
    const char *fmstring
)
{
    PRIntn  prot;
    PROsfd hFileMap;
    PRInt32 dwAccess;
    PRFileMap *fm = NULL;

    PR_sscanf( fmstring, "%d:%" PR_SCNdOSFD ":%ld",
               &prot, &hFileMap, &dwAccess );

    fm = PR_NEWZAP(PRFileMap);
    if ( NULL == fm ) {
        PR_LOG( _pr_shma_lm, PR_LOG_DEBUG,
                ("_md_ImportFileMapFromString(): PR_NEWZAP(): Failed"));
        return(fm);
    }

    fm->prot = (PRFileMapProtect)prot;
    fm->md.hFileMap = (HANDLE)hFileMap;
    fm->md.dwAccess = (DWORD)dwAccess;
    fm->fd = (PRFileDesc*)-1;

    PR_LOG( _pr_shma_lm, PR_LOG_DEBUG,
            ("_md_ImportFileMapFromString(): fm: %p, prot: %d, hFileMap: %8.8x, dwAccess: %8.8x, fd: %x",
             fm, prot, fm->md.hFileMap, fm->md.dwAccess, fm->fd));
    return(fm);
} /* end _md_ImportFileMapFromString() */

#else
Error! Why is PR_HAVE_WIN32_NAMED_SHARED_MEMORY not defined?
#endif /* PR_HAVE_WIN32_NAMED_SHARED_MEMORY */
/* --- end w32shm.c --- */
