/* 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 --- */
