// Filename: subprocessWindowBuffer.cxx
// Created by:  drose (11Jul09)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "subprocessWindowBuffer.h"
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>

#include <iostream>
using namespace std;

const char SubprocessWindowBuffer::
_magic_number[SubprocessWindowBuffer::magic_number_length] = "pNdaSWB";

////////////////////////////////////////////////////////////////////
//     Function: SubprocessWindowBuffer::operator new
//       Access: Private
//  Description: Placement operator.  Returns addr, a trivial
//               pass-through.
////////////////////////////////////////////////////////////////////
void *SubprocessWindowBuffer::
operator new(size_t, void *addr) {
  return addr;
}

////////////////////////////////////////////////////////////////////
//     Function: SubprocessWindowBuffer::Constructor
//       Access: Private
//  Description: This constructor is private; it is not intended to be
//               called directly.  It is used in make_buffer() to
//               create a temporary local object, to determine the
//               required mmap_size for a given window size.
////////////////////////////////////////////////////////////////////
SubprocessWindowBuffer::
SubprocessWindowBuffer(int x_size, int y_size) {
  memcpy(_this_magic, _magic_number, magic_number_length);
  _x_size = x_size;
  _y_size = y_size;
  _row_size = _x_size * 4;
  _framebuffer_size = _row_size * y_size;
  _event_in = 0;
  _event_out = 0;
  _last_written = 0;
  _last_read = 0;

  _mmap_size = sizeof(*this) + _framebuffer_size;
}

////////////////////////////////////////////////////////////////////
//     Function: SubprocessWindowBuffer::Copy Constructor
//       Access: Private
//  Description: 
////////////////////////////////////////////////////////////////////
SubprocessWindowBuffer::
SubprocessWindowBuffer(const SubprocessWindowBuffer &copy) :
  _mmap_size(copy._mmap_size),
  _x_size(copy._x_size),
  _y_size(copy._y_size),
  _row_size(copy._row_size),
  _framebuffer_size(copy._framebuffer_size)
{
  memcpy(_this_magic, _magic_number, magic_number_length);
  _event_in = 0;
  _event_out = 0;
  _last_written = 0;
  _last_read = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: SubprocessWindowBuffer::Destructor
//       Access: Private
//  Description: 
////////////////////////////////////////////////////////////////////
SubprocessWindowBuffer::
~SubprocessWindowBuffer() {
}

////////////////////////////////////////////////////////////////////
//     Function: SubprocessWindowBuffer::new_buffer
//       Access: Public, Static
//  Description: Call this method to create a new buffer in shared
//               memory space.  Supply the desired size of the window.
//
//               This method will create the required shared-memory
//               buffer and return a SubprocessWindowBuffer allocated
//               within that shared memory, or NULL if there is a
//               failure allocating sufficient shared memory.
//
//               It also creates a temporary file on disk and returns
//               fd, mmap_size, and filename, which the caller must
//               retain and eventually pass to destroy_buffer().  The
//               filename should be passed to the child process to
//               open with open_buffer().
////////////////////////////////////////////////////////////////////
SubprocessWindowBuffer *SubprocessWindowBuffer::
new_buffer(int &fd, size_t &mmap_size, string &filename,
           int x_size, int y_size) {
  mmap_size = 0;
  fd = -1;

  filename = tmpnam(NULL);

  fd = open(filename.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600);
  if (fd == -1) {
    perror(filename.c_str());
    return NULL;
  }

  // Create a temporary object to determine the required size.
  SubprocessWindowBuffer temp(x_size, y_size);
  mmap_size = temp._mmap_size;

  // Ensure the disk file is large enough.
  size_t zero_size = 1024;
  char zero[zero_size];
  memset(zero, 0, zero_size);
  for (size_t bi = 0; bi < mmap_size; bi += zero_size) {
    write(fd, zero, zero_size);
  }

  void *shared_mem = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
                          MAP_SHARED, fd, 0);
  if (shared_mem == (void *)-1) {
    // Failure to map.
    close(fd);
    fd = -1;
    mmap_size = 0;
    return NULL;
  }

  // Now create the actual object in the shared-memory buffer.
  return new(shared_mem) SubprocessWindowBuffer(temp);
}

////////////////////////////////////////////////////////////////////
//     Function: SubprocessWindowBuffer::destroy_buffer
//       Access: Public, Static
//  Description: Destroys a buffer object created via a previous call
//               to new_buffer().  This destructs objects within the
//               buffer, unmaps the shared memory, and closes the file
//               descriptor.
////////////////////////////////////////////////////////////////////
void SubprocessWindowBuffer::
destroy_buffer(int fd, size_t mmap_size, const string &filename,
               SubprocessWindowBuffer *buffer) {
  buffer->~SubprocessWindowBuffer();
  close_buffer(fd, mmap_size, filename, buffer);

  // Now we can unlink the file.
  unlink(filename.c_str());
}

////////////////////////////////////////////////////////////////////
//     Function: SubprocessWindowBuffer::open_buffer
//       Access: Public, Static
//  Description: Call this method to open a reference to an existing
//               buffer in shared memory space.  Supply the temporary
//               filename returned by new_buffer(), above (presumably
//               from the parent process).
//
//               This method will mmap the required shared-memory
//               buffer and return a SubprocessWindowBuffer allocated
//               within that shared memory, or NULL if there is some
//               failure.  The caller must retain fd, mmap_size, and
//               filename and eventually pass all three to
//               close_buffer().
////////////////////////////////////////////////////////////////////
SubprocessWindowBuffer *SubprocessWindowBuffer::
open_buffer(int &fd, size_t &mmap_size, const string &filename) {
  mmap_size = 0;

  fd = open(filename.c_str(), O_RDWR);
  if (fd == -1) {
    perror(filename.c_str());
    return NULL;
  }

  // Check that the disk file is large enough.
  off_t file_size = lseek(fd, 0, SEEK_END);
  if (file_size < sizeof(SubprocessWindowBuffer)) {
    cerr << filename << " not large enough.\n";
    close(fd);
    fd = -1;
    return NULL;
  }

  // First, map enough memory to read the buffer object.
  size_t initial_size = sizeof(SubprocessWindowBuffer);
  void *shared_mem = mmap(NULL, initial_size, PROT_READ,
                          MAP_SHARED, fd, 0);
  if (shared_mem == (void *)-1) {
    perror("mmap");
    cerr << "Couldn't map.\n";
    close(fd);
    fd = -1;
    return NULL;
  }

  SubprocessWindowBuffer *temp = (SubprocessWindowBuffer *)shared_mem;
  if (!temp->verify_magic_number()) {
    cerr << "Not a subprocess window buffer: " << filename << "\n";
    munmap(shared_mem, initial_size);
    close(fd);
    fd = -1;
    return NULL;
  }
    

  mmap_size = temp->_mmap_size;

  // Now unmap that and remap the proper-size buffer.
  munmap(shared_mem, initial_size);

  if (file_size < mmap_size) {
    cerr << filename << " not large enough.\n";
    close(fd);
    fd = -1;
    return NULL;
  }

  shared_mem = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
                    MAP_SHARED, fd, 0);
  if (shared_mem == (void *)-1) {
    perror("mmap");
    cerr << "Couldn't map 2.\n";
    return NULL;
  }

  // Now that we've successfully opened and mapped the file, we can
  // safely delete it from the file system.

  // Actually, unlinking it now prevents us from detaching and
  // reattaching to the same file later.  Boo.
  //  unlink(filename.c_str());

  SubprocessWindowBuffer *buffer = (SubprocessWindowBuffer *)shared_mem;
  assert(buffer->_mmap_size == mmap_size);
  return buffer;
}

////////////////////////////////////////////////////////////////////
//     Function: SubprocessWindowBuffer::close_buffer
//       Access: Public, Static
//  Description: Closes a buffer object created via a previous call
//               to open_buffer().  This unmaps the shared memory
//               and closes the file descriptor, but does not molest
//               the shared buffer itself.
////////////////////////////////////////////////////////////////////
void SubprocessWindowBuffer::
close_buffer(int fd, size_t mmap_size, const string &filename,
             SubprocessWindowBuffer *buffer) {
  munmap((void *)buffer, mmap_size);
  close(fd);
}

////////////////////////////////////////////////////////////////////
//     Function: SubprocessWindowBuffer::verify_magic_number
//       Access: Public
//  Description: Returns true if the buffer's magic number matches,
//               false otherwise.
////////////////////////////////////////////////////////////////////
bool SubprocessWindowBuffer::
verify_magic_number() const {
  return (memcmp(_this_magic, _magic_number, magic_number_length) == 0);
}
