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