// Defines the member functions for the memory mapping facility.

#include "ace/Mem_Map.h"

#if !defined (__ACE_INLINE__)
#include "ace/Mem_Map.inl"
#endif /* __ACE_INLINE__ */

#include "ace/OS_NS_sys_stat.h"
#include "ace/OS_NS_fcntl.h"
#include "ace/OS_NS_string.h"
#include "ace/Log_Category.h"
#include "ace/Truncate.h"
#if defined (ACE_HAS_ALLOC_HOOKS)
# include "ace/Malloc_Base.h"
#endif /* ACE_HAS_ALLOC_HOOKS */

ACE_BEGIN_VERSIONED_NAMESPACE_DECL

ACE_ALLOC_HOOK_DEFINE(ACE_Mem_Map)

void
ACE_Mem_Map::dump (void) const
{
#if defined (ACE_HAS_DUMP)
  ACE_TRACE ("ACE_Mem_Map::dump");

  ACELIB_DEBUG ((LM_DEBUG, ACE_BEGIN_DUMP, this));
  ACELIB_DEBUG ((LM_DEBUG,  ACE_TEXT ("base_addr_ = %x"), this->base_addr_));
  ACELIB_DEBUG ((LM_DEBUG,  ACE_TEXT ("\nfilename_ = %s"), this->filename_));
  ACELIB_DEBUG ((LM_DEBUG,  ACE_TEXT ("\nlength_ = %d"), this->length_));
  ACELIB_DEBUG ((LM_DEBUG,  ACE_TEXT ("\nhandle_ = %d"), this->handle_));
  ACELIB_DEBUG ((LM_DEBUG,  ACE_TEXT ("\nfile_mapping_ = %d"), this->file_mapping_));
  ACELIB_DEBUG ((LM_DEBUG,  ACE_TEXT ("\nclose_handle_ = %d"), this->close_handle_));
  ACELIB_DEBUG ((LM_DEBUG, ACE_END_DUMP));
#endif /* ACE_HAS_DUMP */
}

int
ACE_Mem_Map::close (void)
{
  ACE_TRACE ("ACE_Mem_Map::close");

  this->unmap ();

  return this->close_handle ();
}

ACE_Mem_Map::~ACE_Mem_Map (void)
{
  ACE_TRACE ("ACE_Mem_Map::~ACE_Mem_Map");

  this->close ();
}

// This function does the dirty work of actually calling ACE_OS::mmap
// to map the file into memory.

int
ACE_Mem_Map::map_it (ACE_HANDLE handle,
                     size_t length_request,
                     int prot,
                     int share,
                     void *addr,
                     ACE_OFF_T offset,
                     LPSECURITY_ATTRIBUTES sa)
{
  ACE_TRACE ("ACE_Mem_Map::map_it");

#if defined (ACE_LACKS_AUTO_MMAP_REPLACEMENT)
  // If the system does not replace any previous mappings, then
  // unmap() before (potentially) mapping to the same location.
  int const unmap_result = this->unmap ();
  if (unmap_result != 0)
    return unmap_result;
#endif /* ACE_LACKS_AUTO_MMAP_REPLACEMENT */

  this->base_addr_ = addr;
  this->handle_ = handle;

  // mmap through character device doens't care about it's size
  // So map with /dev/* is done with a special case.
  ACE_stat current_file_type;
  int result = ACE_OS::fstat (this->handle_, &current_file_type);

  if (result == -1)
    {
      // Something wrong found, bail out.
      return -1;
    }
  else if ((current_file_type.st_mode & S_IFMT) == S_IFCHR)
    {
      // Set length to length_request
      this->length_ = length_request;
    }
  else if ((current_file_type.st_mode & S_IFMT) == S_IFREG)
    {
      // Get the current filesize
      ACE_OFF_T const current_file_length = ACE_OS::filesize (this->handle_);

      // Flag to indicate if we need to extend the back store
      bool extend_backing_store = false;

      // File length requested by user
      ACE_OFF_T requested_file_length = 0;

      // Check <length_request>
      if (length_request == static_cast<size_t> (-1))
        {
          // Set length to file_request or size_t max.
          this->length_ = ACE_Utils::truncate_cast<size_t> (current_file_length - offset);
#if defined (ACE_MMAP_NO_ZERO)
          if (this->length_ == 0)
            {
              this->length_ = ACE_OS::getpagesize ();
            }
#endif /* ACE_MMAP_NO_ZERO */
        }
      else
        {
          // Make sure that we have not been asked to do the impossible.
          if (static_cast<ACE_UINT64> (length_request)
              + static_cast<ACE_UINT64> (offset)
              > static_cast<ACE_UINT64> (ACE_Numeric_Limits<ACE_OFF_T>::max ()))
            return -1;

          // File length implicitly requested by user
          requested_file_length = static_cast<ACE_OFF_T> (length_request) + offset;

          // Check to see if we need to extend the backing store
          if (requested_file_length > current_file_length)
            {
              // If the length of the mapped region is less than the
              // length of the file then we force a complete new remapping
              // by setting the descriptor to ACE_INVALID_HANDLE (closing
              // down the descriptor if necessary).
              this->close_filemapping_handle ();

              // Remember to extend the backing store
              extend_backing_store = true;
            }

          // Set length to length_request
          this->length_ = length_request;
        }

      // Check if we need to extend the backing store.
      if (extend_backing_store)
        {
          // Remember than write increases the size by one.
          ACE_OFF_T null_byte_position = 0;
          if (requested_file_length > 0)
            {
              // This will make the file size <requested_file_length>
              null_byte_position = requested_file_length - 1;
            }

          if (ACE_OS::pwrite (this->handle_,
                              "",
                              1,
                              null_byte_position) == -1)
            return -1;
        }
      }
    else
      // Unmappable file type.
      return -1;

  this->base_addr_ = ACE_OS::mmap (this->base_addr_,
                                     this->length_,
                                     prot,
                                     share,
                                     this->handle_,
                                     offset,
                                     &this->file_mapping_,
                                     sa);

  return this->base_addr_ == MAP_FAILED ? -1 : 0;
}

int
ACE_Mem_Map::open (const ACE_TCHAR *file_name,
                   int flags,
                   mode_t perms,
                   LPSECURITY_ATTRIBUTES sa)
{
  ACE_TRACE ("ACE_Mem_Map::open");

#if defined (INTEGRITY)  || defined (__QNXNTO__) || defined (ACE_VXWORKS)
  this->handle_ = ACE_OS::shm_open (file_name, flags, perms, sa);
#elif defined (ACE_OPENVMS)
  ACE_OSCALL (::open (file_name, flags, perms, "shr=get,put,upd"), ACE_HANDLE, -1, this->handle_);
#else
  this->handle_ = ACE_OS::open (file_name, flags, perms, sa);
#endif /* INTEGRITY */

  if (this->handle_ == ACE_INVALID_HANDLE)
    return -1;
  else
    {
      ACE_OS::strsncpy (this->filename_,
                        file_name,
                        MAXPATHLEN);

      this->close_handle_ = true;
      return 0;
    }
}

int
ACE_Mem_Map::map (const ACE_TCHAR *file_name,
                  size_t len,
                  int flags,
                  mode_t mode,
                  int prot,
                  int share,
                  void *addr,
                  ACE_OFF_T offset,
                  LPSECURITY_ATTRIBUTES sa)
{
  ACE_TRACE ("ACE_Mem_Map::map");
  this->length_ = 0;

  if (this->open (file_name,
                  flags,
                  mode,
                  sa) == -1)
    return -1;
  else
    return this->map_it (this->handle (),
                         len,
                         prot,
                         share,
                         addr,
                         offset,
                         sa);
}

ACE_Mem_Map::ACE_Mem_Map (void)
  : base_addr_ (MAP_FAILED),
    length_ (0),
    handle_ (ACE_INVALID_HANDLE),
    file_mapping_ (ACE_INVALID_HANDLE),
    close_handle_ (false)
{
  ACE_TRACE ("ACE_Mem_Map::ACE_Mem_Map");
  ACE_OS::memset (this->filename_, 0, sizeof this->filename_);
}

// Map a file specified by FILE_NAME.

ACE_Mem_Map::ACE_Mem_Map (const ACE_TCHAR *file_name,
                          size_t len,
                          int flags,
                          mode_t mode,
                          int prot,
                          int share,
                          void *addr,
                          ACE_OFF_T offset,
                          LPSECURITY_ATTRIBUTES sa)
  : base_addr_ (MAP_FAILED),
    length_ (0),
    handle_ (ACE_INVALID_HANDLE),
    file_mapping_ (ACE_INVALID_HANDLE),
    close_handle_ (false)
{
  ACE_TRACE ("ACE_Mem_Map::ACE_Mem_Map");
  if (this->map (file_name,
                 len,
                 flags,
                 mode,
                 prot,
                 share,
                 addr,
                 offset,
                 sa) < 0)
    ACELIB_ERROR ((LM_ERROR,
                ACE_TEXT ("%p\n"),
                ACE_TEXT ("ACE_Mem_Map::ACE_Mem_Map")));
}

// Map a file from an open file descriptor HANDLE.  This function will
// lookup the length of the file if it is not given.

ACE_Mem_Map::ACE_Mem_Map (ACE_HANDLE handle,
                          size_t len,
                          int prot,
                          int share,
                          void *addr,
                          ACE_OFF_T offset,
                          LPSECURITY_ATTRIBUTES sa)
  : base_addr_ (MAP_FAILED),
    length_ (0),
    handle_ (ACE_INVALID_HANDLE),
    file_mapping_ (ACE_INVALID_HANDLE),
    close_handle_ (false)
{
  ACE_TRACE ("ACE_Mem_Map::ACE_Mem_Map");

  ACE_OS::memset (this->filename_,
                  0,
                  sizeof this->filename_);
  if (this->map (handle,
                 len,
                 prot,
                 share,
                 addr,
                 offset,
                 sa) < 0)
    ACELIB_ERROR ((LM_ERROR,
                ACE_TEXT ("%p\n"),
                ACE_TEXT ("ACE_Mem_Map::ACE_Mem_Map")));
}

// Close down and remove the file from the file system.

int
ACE_Mem_Map::remove (void)
{
  ACE_TRACE ("ACE_Mem_Map::remove");

  ACE_OS::ftruncate (this->handle_, 0);
  this->close ();

  if (this->filename_[0] != '\0')
#if defined (INTEGRITY) || defined (__QNXNTO__) || defined (ACE_VXWORKS)
  return ACE_OS::shm_unlink (this->filename_);
#else
  return ACE_OS::unlink (this->filename_);
#endif /* __QNXNTO__ */

  else
    return 0;
}

ACE_END_VERSIONED_NAMESPACE_DECL
