// @(#)root/base:$Name:  $:$Id: TMapFile.cxx,v 1.11 2003/04/03 13:46:49 brun Exp $
// Author: Fons Rademakers   08/07/97

/*************************************************************************
 * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/
#ifdef WIN32
#pragma optimize("",off)
#endif

//////////////////////////////////////////////////////////////////////////
//                                                                      //
// TMapFile                                                             //
//                                                                      //
// This class implements a shared memory region mapped to a file.       //
// Objects can be placed into this shared memory area using the Add()   //
// member function. To actually place a copy of the object is shared    //
// memory call Update() also whenever the mapped object(s) change(s)    //
// call Update() to put a fresh copy in the shared memory. This extra   //
// step is necessary since it is not possible to share objects with     //
// virtual pointers between processes (the vtbl ptr points to the       //
// originators unique address space and can not be used by the          //
// consumer process(es)). Consumer processes can map the memory region  //
// from this file and access the objects stored in it via the Get()     //
// method (which returns a copy of the object stored in the shared      //
// memory with correct vtbl ptr set). Only objects of classes with a    //
// Streamer() member function defined can be shared.                    //
//                                                                      //
// I know the current implementation is not ideal (you need to copy to  //
// and from the shared memory file) but the main problem is with the    //
// class' virtual_table pointer. This pointer points to a table unique  //
// for every process. Therefore, different options are:                 //
//   1) One could allocate an object directly in shared memory in the   //
//      producer, but the consumer still has to copy the object from    //
//      shared memory into a local object which has the correct vtbl    //
//      pointer for that process (copy ctor's can be used for creating  //
//      the local copy).                                                //
//   2) Another possibility is to only allow objects without virtual    //
//      functions in shared memory (like simple C structs), or to       //
//      forbid (how?) the consumer from calling any virtual functions   //
//      of the objects in shared memory.                                //
//   3) A last option is to copy the object internals to shared memory  //
//      and copy them again from there. This is what is done in the     //
//      TMapFile (using the object Streamer() to make a deep copy).     //
// Option 1) saves one copy, but requires solid copy ctor's (along the  //
// full inheritance chain) to rebuild the object in the consumer. Most  //
// classes don't provide these copy ctor's, especially not when objects //
// contain collections, etc. 2) is too limiting or dangerous (calling   //
// accidentally a virtual function will segv). So since we have a       //
// robust Streamer mechanism I opted for 3).                            //
//                                                                      //
//////////////////////////////////////////////////////////////////////////


#ifdef WIN32
#  include <windows.h>
#  include <process.h>
#  ifdef GetObject
#    undef GetObject
#  endif
#  define HAVE_SEMOP

# ifdef CreateSemaphore
#   undef CreateSemaphore
# endif

# ifdef AcquireSemaphore
#   undef AcquireSemaphore;
# endif

# ifdef ReleaseSemaphore
#   undef ReleaseSemaphore
# endif

# ifdef DeleteSemaphore
#   undef DeleteSemaphore
# endif

#else
#  define INVALID_HANDLE_VALUE -1
#endif

#include <fcntl.h>
#include <errno.h>

#include "TMapFile.h"
#include "TKeyMapFile.h"
#include "TDirectory.h"
#include "TBrowser.h"
#include "TString.h"
#include "TSystem.h"
#include "TClass.h"
#include "TMath.h"

#if defined(R__UNIX) && !defined(R__MACOSX) && !defined(R__WINGCC)
#define HAVE_SEMOP
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#if defined(R__HPUX) || (defined (R__ALPHA) && !defined(R__FBSD)) || \
    defined (R__SOLARIS) || defined(R__AIX) || defined(R__HIUX) || \
    defined (R__MKLINUX) || __GLIBC_MINOR__ > 0
union semun {
   int val;                      // value for SETVAL
   struct semid_ds *buf;         // buffer for IPC_STAT & IPC_SET
   ushort *array;                // array for GETALL & SETALL
};
#endif
#if defined(R__LINUX) || defined(R__LYNXOS) || defined(R__HURD)
#  define       SEM_A   0200     // alter permission
#  define       SEM_R   0400     // read permission
#endif
#endif


Long_t TMapFile::fgMapAddress = 0;
void  *TMapFile::fgMmallocDesc = 0;

void *gMmallocDesc = 0;



//______________________________________________________________________________
TMapRec::TMapRec(const char *name, const TObject *obj, Int_t size, void *buf)
{
   fName      = StrDup(name);
   fClassName = 0;
   fObject    = (TObject*)obj;
   fBuffer    = buf;
   fBufSize   = size;
   fNext      = 0;
}

//______________________________________________________________________________
TMapRec::~TMapRec()
{
   delete [] fName;
   delete [] fClassName;
}

//______________________________________________________________________________
TObject *TMapRec::GetObject() const
{
   // This method returns a pointer to the original object. NOTE: this pointer
   // is only valid in the process that produces the shared memory file. In a
   // consumer process this pointer is illegal! Be careful.

   return fObject;
}




ClassImp(TMapFile)

//______________________________________________________________________________
TMapFile::TMapFile()
{
   // Default ctor. Does not much except setting some basic values.

   fFd          = -1;
   fMmallocDesc = 0;
   fFirst       = 0;
}

//______________________________________________________________________________
TMapFile::TMapFile(const char *name, const char *title, Option_t *option,
                   Int_t size, TMapFile *&newMapFile)
{
   // Create a memory mapped file. This opens a file (to which the
   // memory will be mapped) and attaches a memory region to it.
   // Option can be either: "NEW", "CREATE", "RECREATE", "UPDATE" or
   // "READ" (see TFile). The default open mode is "READ". The size
   // argument specifies the maximum size of shared memory file in bytes.
   // This protected ctor is called via the static Create() method.

#ifndef WIN32
   fFd          = -1;
   fSemaphore   = -1;
#else
   fFd          = (Int_t) INVALID_HANDLE_VALUE;
   fSemaphore   = (Int_t) INVALID_HANDLE_VALUE;
#endif
   fMmallocDesc = 0;
   fSize        = size;
   fFirst       = 0;
   fOffset      = 0;
   fVersion     = gROOT->GetVersionInt();
   fTitle       = StrDup(title);
   fOption      = StrDup(option);
   fDirectory   = 0;
   fBrowseList  = 0;
   fGetting     = 0;
   fWritten     = 0;
   fSumBuffer   = 0;
   fSum2Buffer  = 0;

   char  *cleanup = 0;
   Bool_t create  = kFALSE;
   Bool_t recreate, update, read;

   {
      TString opt = option;

      if (!opt.CompareTo("NEW", TString::kIgnoreCase) ||
          !opt.CompareTo("CREATE", TString::kIgnoreCase))
          create = kTRUE;
      recreate = opt.CompareTo("RECREATE", TString::kIgnoreCase)
                 ? kFALSE : kTRUE;
      update   = opt.CompareTo("UPDATE", TString::kIgnoreCase)
                 ? kFALSE : kTRUE;
      read     = opt.CompareTo("READ", TString::kIgnoreCase)
                 ? kFALSE : kTRUE;
      if (!create && !recreate && !update && !read) {
         read    = kTRUE;
         delete [] fOption;
         fOption = StrDup("READ");
      }
   }

   const char *fname;
   if ((fname = gSystem->ExpandPathName(name))) {
      fName = StrDup(fname);
      delete [] (char*)fname;
      fname = fName;
   } else {
      Error("TMapFile", "error expanding path %s", fname);
      goto zombie;
   }

   if (recreate) {
      if (!gSystem->AccessPathName(fname, kFileExists))
         gSystem->Unlink(fname);
      recreate = kFALSE;
      create   = kTRUE;
      delete [] fOption;
      fOption  = StrDup("CREATE");
   }
   if (create && !gSystem->AccessPathName(fname, kFileExists)) {
      Error("TMapFile", "file %s already exists", fname);
      goto zombie;
   }
   if (update) {
      if (gSystem->AccessPathName(fname, kFileExists)) {
         update = kFALSE;
         create = kTRUE;
      }
      if (update && gSystem->AccessPathName(fname, kWritePermission)) {
         Error("TMapFile", "no write permission, could not open file %s", fname);
         goto zombie;
      }
   }
   if (read) {
      if (gSystem->AccessPathName(fname, kFileExists)) {
         Error("TMapFile", "file %s does not exist", fname);
         goto zombie;
      }
      if (gSystem->AccessPathName(fname, kReadPermission)) {
         Error("TMapFile", "no read permission, could not open file %s", fname);
         goto zombie;
      }
   }

   // Open file to which memory will be mapped
   if (create || update) {
#ifndef WIN32
      fFd = open(fname, O_RDWR | O_CREAT, 0644);
#else
      fFd = (Int_t) CreateFile(fname,                    // pointer to name of the file
                     GENERIC_WRITE | GENERIC_READ,       // access (read-write) mode
                     FILE_SHARE_WRITE | FILE_SHARE_READ, // share mode
                     NULL,                               // pointer to security attributes
                     OPEN_ALWAYS,                        // how to create
                     FILE_ATTRIBUTE_TEMPORARY,           // file attributes
                     (HANDLE) NULL);                     // handle to file with attributes to copy
#endif
      if (fFd == (Int_t)INVALID_HANDLE_VALUE) {
         SysError("TMapFile", "file %s can not be opened", fname);
         goto zombie;
      }
      fWritable = kTRUE;
   } else {
#ifndef WIN32
      fFd = open(fname, O_RDONLY);
#else
      fFd = (Int_t) CreateFile(fname,                    // pointer to name of the file
                     GENERIC_READ,                       // access (read-write) mode
                     FILE_SHARE_WRITE | FILE_SHARE_READ, // share mode
                     NULL,                               // pointer to security attributes
                     OPEN_EXISTING,                      // how to create
                     FILE_ATTRIBUTE_TEMPORARY,           // file attributes
                     (HANDLE) NULL);                     // handle to file with attributes to copy
#endif
      if (fFd == (Int_t)INVALID_HANDLE_VALUE) {
         SysError("TMapFile", "file %s can not be opened for reading", fname);
         goto zombie;
      }
      fWritable = kFALSE;
   }

   // Attach memory region to file.
   void *mapto;
   TMapFile *mapfil;

   if (((mapto = MapToAddress()) == (void *)-1) ||
#ifndef WIN32
       ((fMmallocDesc = mmalloc_attach(fFd, mapto, fSize)) == 0)) {
#else
       ((fMmallocDesc = mmalloc_attach((HANDLE) fFd, mapto, fSize)) == 0)) {
#endif

      if (mapto == (void *)-1) {
         Error("TMapFile", "no memory mapped file capability available\n"
                           "Use rootn.exe or link application against \"-lNew\"");
      } else {
         if (fMmallocDesc == 0 && fWritable)
            Error("TMapFile", "mapped file not in mmalloc format or\n"
                              "already open in RW mode by another process");
         if (fMmallocDesc == 0 && !fWritable)
            Error("TMapFile", "mapped file not in mmalloc format");
      }
#ifndef WIN32
      close(fFd);
#else
      CloseHandle((HANDLE) fFd);
#endif
      fFd = -1;
      if (create)
         gSystem->Unlink(fname);
      goto zombie;

   } else if ((mapfil = (TMapFile *) mmalloc_getkey(fMmallocDesc, 0)) != 0) {

      // File contains mmalloc heap. If we are in write mode and mapped
      // file already connected in write mode switch to read-only mode.
      // Check if ROOT versions are compatible.
      // If so update mapped version of TMapFile to reflect current
      // situation (only if not opened in READ mode).
      if (mapfil->fVersion != fVersion) {
         Error("TMapFile", "map file %s (%d) incompatible with current ROOT version (%d)",
               fname, mapfil->fVersion, fVersion);
         mmalloc_detach(fMmallocDesc);
#ifndef WIN32
         close(fFd);
#else
         CloseHandle((HANDLE) fFd);
#endif
         fFd = -1;
         fMmallocDesc = 0;
         goto zombie;
      }

      if (mapfil->fWritable && fWritable) {
         Warning("TMapFile", "map file already open in write mode, opening in read-only mode");
         fWritable = kFALSE;
      }

      fBaseAddr = mapfil->fBaseAddr;
      fSize     = mapfil->fSize;

      if (fWritable) {
         // create new TMapFile object in mapped heap to get correct vtbl ptr
         CreateSemaphore();
         gMmallocDesc = fMmallocDesc;
         TMapFile *mf = new TMapFile(*mapfil);
         mf->fFd        = fFd;
         mf->fWritable  = kTRUE;
         cleanup        = mf->fOption;
         mf->fOption    = StrDup(fOption);
         mf->fSemaphore = fSemaphore;
#ifdef WIN32
         mf->CreateSemaphore(fSemaphore);
#endif
         mmalloc_setkey(fMmallocDesc, 0, mf);
         gMmallocDesc = 0;
         mapfil = mf;
      } else {
         gMmallocDesc = 0;    // make sure we are in sbrk heap
         fOffset      = ((struct mdesc *) fMmallocDesc)->offset;
         TMapFile *mf = new TMapFile(*mapfil, fOffset);
         delete [] mf->fOption;
         mf->fFd          = fFd;
         mf->fOption      = StrDup("READ");
         mf->fMmallocDesc = fMmallocDesc;
         mf->fWritable    = kFALSE;
         mapfil = mf;
      }

      // store shadow mapfile (it contains the real fFd in case map
      // is not writable)
      fVersion  = -1;   // make this the shadow map file
      gROOT->GetListOfMappedFiles()->AddLast(this);

   } else {

      // New file. If the file is writable create a new copy of the
      // TMapFile which will now be allocated on the memory mapped heap.
      if (!fWritable) {
         Error("TMapFile", "map file is not writable");
         mmalloc_detach(fMmallocDesc);
#ifndef WIN32
         close(fFd);
#else
         CloseHandle((HANDLE) fFd);
#endif
         fFd = -1;
         fMmallocDesc = 0;
         goto zombie;
      }

      fBaseAddr = (ULong_t)((struct mdesc *) fMmallocDesc)->base;

      CreateSemaphore();

      gMmallocDesc = fMmallocDesc;

      mapfil = new TMapFile(*this);
      mmalloc_setkey(fMmallocDesc, 0, mapfil);

      gMmallocDesc = 0;

      // store shadow mapfile
      fVersion  = -1;   // make this the shadow map file
      gROOT->GetListOfMappedFiles()->AddLast(this);

   }

   mapfil->InitDirectory();
   gROOT->GetListOfMappedFiles()->AddFirst(mapfil);

   delete [] cleanup;

   newMapFile = mapfil;

   return;

zombie:
   // error in file opening occured, make this object a zombie
   MakeZombie();
   newMapFile   = this;
   gMmallocDesc = 0;
}

//______________________________________________________________________________
TMapFile::TMapFile(const TMapFile &f, Long_t offset) : TObject(f)
{
   // Private copy ctor. Used by the the ctor to create a new version
   // of TMapFile in the memory mapped heap. It's main purpose is to
   // correctly create the string data members.

   fFd          = f.fFd;
   fVersion     = f.fVersion;
   fName        = StrDup((char *)((long)f.fName + offset));
   fTitle       = StrDup((char *)((long)f.fTitle + offset));
   fOption      = StrDup((char *)((long)f.fOption + offset));
   fMmallocDesc = f.fMmallocDesc;
   fBaseAddr    = f.fBaseAddr;
   fSize        = f.fSize;
   fFirst       = f.fFirst;
   fLast        = f.fLast;
   fWritable    = f.fWritable;
   fSemaphore   = f.fSemaphore;
   fOffset      = offset;
   fDirectory   = 0;
   fBrowseList  = 0;
   fGetting     = 0;
   fWritten     = f.fWritten;
   fSumBuffer   = f.fSumBuffer;
   fSum2Buffer  = f.fSum2Buffer;
#ifdef WIN32
   CreateSemaphore(fSemaphore);
#endif
}

//______________________________________________________________________________
TMapFile::~TMapFile()
{
   // TMapFiles may not be deleted, since we want to keep the complete
   // TMapFile object in the mapped file for later re-use. To enforce this
   // the delete operator has been made private. Use Close() to properly
   // terminate a TMapFile (also done via the TROOT dtor).

   if (fDirectory == gDirectory) gDirectory = gROOT;
   delete fDirectory; fDirectory = 0;
   if (fBrowseList) fBrowseList->Delete();
   delete fBrowseList; fBrowseList = 0;

   // if shadow map file we are done here
   if (fVersion == -1)
      return;

   // Writable mapfile is allocated in mapped memory. This object should
   // not be deleted by ::operator delete(), because it is needed if we
   // want to connect later to the file again.
   if (fWritable)
      TObject::SetDtorOnly(this);

   Close("dtor");

   fgMmallocDesc = fMmallocDesc;
}

//______________________________________________________________________________
void TMapFile::InitDirectory()
{
   // Create the directory associated to this mapfile

   gDirectory = 0;
   fDirectory = new TDirectory();
   fDirectory->SetName(GetName());
   fDirectory->SetTitle(GetTitle());
   fDirectory->Build();
   fDirectory->SetMother(this);
   gDirectory = fDirectory;
}

//______________________________________________________________________________
void TMapFile::Add(const TObject *obj, const char *name)
{
   // Add an object to the list of objects to be stored in shared memory.
   // To place the object actually into shared memory call Update().

   if (!fWritable || !fMmallocDesc) return;

   Bool_t lock = fGetting != obj ? kTRUE : kFALSE;

   if (lock)
      AcquireSemaphore();

   gMmallocDesc = fMmallocDesc;

   const char *n;
   if (name && *name)
      n = name;
   else
      n = obj->GetName();

   if (Remove(n, kFALSE))
      ; //Warning("Add", "replaced object with same name %s", n);

   TMapRec *mr = new TMapRec(n, obj, 0, 0);
   if (!fFirst) {
      fFirst = mr;
      fLast  = mr;
   } else {
      fLast->fNext = mr;
      fLast        = mr;
   }

   gMmallocDesc = 0;

   if (lock)
      ReleaseSemaphore();
}

//______________________________________________________________________________
void TMapFile::Update(TObject *obj)
{
   // Update an object (or all objects, if obj == 0) in shared memory.

   if (!fWritable || !fMmallocDesc) return;

   AcquireSemaphore();

   gMmallocDesc = fMmallocDesc;

   Bool_t all = (obj == 0) ? kTRUE : kFALSE;

   TMapRec *mr = fFirst;
   while (mr) {
      if (all || mr->fObject == obj) {
         TBuffer *b;
         if (!mr->fBufSize) {
            b = new TBuffer(TBuffer::kWrite, GetBestBuffer());
            mr->fClassName = StrDup(mr->fObject->ClassName());
         } else
            b = new TBuffer(TBuffer::kWrite, mr->fBufSize, mr->fBuffer);
         b->MapObject(mr->fObject);  //register obj in map to handle self reference
         mr->fObject->Streamer(*b);
         mr->fBufSize = b->BufferSize();
         mr->fBuffer  = b->Buffer();
         SumBuffer(b->Length());
         b->DetachBuffer();
         delete b;
      }
      mr = mr->fNext;
   }

   gMmallocDesc = 0;

   ReleaseSemaphore();
}

//______________________________________________________________________________
TObject *TMapFile::Remove(TObject *obj, Bool_t lock)
{
   // Remove object from shared memory. Returns pointer to removed
   // object if successful, 0 otherwise.

   if (!fWritable || !fMmallocDesc) return 0;

   if (lock)
      AcquireSemaphore();

   TObject *retObj = 0;
   TMapRec *prev = 0, *mr = fFirst;
   while (mr) {
      if (mr->fObject == obj) {
         if (mr == fFirst) {
            fFirst = mr->fNext;
            if (mr == fLast)
               fLast = 0;
         } else {
            prev->fNext = mr->fNext;
            if (mr == fLast)
               fLast = prev;
         }
         retObj = obj;
         delete mr;
         break;
      }
      prev = mr;
      mr   = mr->fNext;
   }

   if (lock)
      ReleaseSemaphore();

   return retObj;
}

//______________________________________________________________________________
TObject *TMapFile::Remove(const char *name, Bool_t lock)
{
   // Remove object by name from shared memory. Returns pointer to removed
   // object if successful, 0 otherwise.

   if (!fWritable || !fMmallocDesc) return 0;

   if (lock)
      AcquireSemaphore();

   TObject *retObj = 0;
   TMapRec *prev = 0, *mr = fFirst;
   while (mr) {
      if (!strcmp(mr->fName, name)) {
         if (mr == fFirst) {
            fFirst = mr->fNext;
            if (mr == fLast)
               fLast = 0;
         } else {
            prev->fNext = mr->fNext;
            if (mr == fLast)
               fLast = prev;
         }
         retObj = mr->fObject;
         delete mr;
         break;
      }
      prev = mr;
      mr   = mr->fNext;
   }

   if (lock)
      ReleaseSemaphore();

   return retObj;
}

//______________________________________________________________________________
void TMapFile::RemoveAll()
{
   // Remove all objects from shared memory.

   if (!fWritable || !fMmallocDesc) return;

   AcquireSemaphore();

   TMapRec *mr = fFirst;
   while (mr) {
      TMapRec *t = mr;
      mr = mr->fNext;
      delete t;
   }
   fFirst = fLast = 0;

   ReleaseSemaphore();
}

//______________________________________________________________________________
TObject *TMapFile::Get(const char *name, TObject *delObj)
{
   // Return pointer to object retrieved from shared memory. The object must
   // be deleted after use. If delObj is a pointer to a previously allocated
   // object it will be deleted. Returns 0 in case object with the given
   // name does not exist.

   if (!fMmallocDesc) return 0;

   AcquireSemaphore();

   delete delObj;

   TObject *obj = 0;
   TMapRec *mr = GetFirst();
   while (OrgAddress(mr)) {
      if (!strcmp(mr->GetName(fOffset), name)) {
         if (!mr->fBufSize) goto release;
         TClass *cl = gROOT->GetClass(mr->GetClassName(fOffset));
         if (!cl) {
            Error("Get", "unknown class %s", mr->GetClassName(fOffset));
            goto release;
         }

         obj = (TObject *)cl->New();
         if (!obj) {
            Error("Get", "cannot create new object of class %s", mr->GetClassName(fOffset));
            goto release;
         }

         fGetting = obj;
         TBuffer *b = new TBuffer(TBuffer::kRead, mr->fBufSize, mr->GetBuffer(fOffset));
         b->MapObject(obj);  //register obj in map to handle self reference
         obj->Streamer(*b);
         b->DetachBuffer();
         delete b;
         fGetting = 0;
         goto release;
      }
      mr = mr->GetNext(fOffset);
   }

release:
   ReleaseSemaphore();

   return obj;
}

//______________________________________________________________________________
#ifndef WIN32
void TMapFile::CreateSemaphore(int)
#else
void TMapFile::CreateSemaphore(int pid)
#endif
{
   // Create semaphore used for synchronizing access to shared memory.

#ifdef HAVE_SEMOP
#ifndef WIN32
   // create semaphore to synchronize access (should use read/write lock)
   fSemaphore = semget(IPC_PRIVATE, 1, SEM_R|SEM_A|(SEM_R>>3)|(SEM_A>>3)|
                                       (SEM_R>>6)|(SEM_A>>6));

   // set semaphore to 1
   if (fSemaphore != -1) {
      union semun set;
      set.val = 1;
      semctl(fSemaphore, 0, SETVAL, set);
   }
#else
   char buffer[] ="ROOT_Semaphore_xxxxxxxx";
   int lbuf = strlen(buffer);
   if (!pid) fSemaphore = getpid();
   fhSemaphore = (ULong_t)CreateMutex(NULL,FALSE,itoa(fSemaphore,&buffer[lbuf-8],16));
   if (fhSemaphore == 0) fSemaphore = (Int_t)INVALID_HANDLE_VALUE;
#endif
#endif
}

//______________________________________________________________________________
void TMapFile::DeleteSemaphore()
{
   // Delete the semaphore.

#ifdef HAVE_SEMOP
   // remove semaphore
#ifndef WIN32
   if (fSemaphore != -1) {
      int semid  = fSemaphore;
      fSemaphore = -1;
      union semun set;
      set.val = 0;
      semctl(semid, 0, IPC_RMID, set);
   }
#else
   if (fSemaphore != (Int_t)INVALID_HANDLE_VALUE) {
      CloseHandle((HANDLE)fhSemaphore);
      fhSemaphore = 0;
      fSemaphore  = (Int_t)INVALID_HANDLE_VALUE;
   }
#endif
#endif
}

//______________________________________________________________________________
Int_t TMapFile::AcquireSemaphore()
{
   // Acquire semaphore. Returns 0 if OK, -1 on error.

#ifdef HAVE_SEMOP
#ifndef WIN32
   if (fSemaphore != -1) {
      struct sembuf buf = { 0, -1, SEM_UNDO };
      int intr = 0;
again:
      if (semop(fSemaphore, &buf, 1) == -1) {
#if defined(R__FBSD)
         if (TSystem::GetErrno() == EINVAL)
#else
         if (TSystem::GetErrno() == EIDRM)
#endif
            fSemaphore = -1;
#if !defined(R__FBSD)
         if (TSystem::GetErrno() == EINTR) {
            if (intr > 2)
               return -1;
            TSystem::ResetErrno();
            intr++;
            goto again;
         }
#endif
      }
   }
#else
   // Enter Critical section to "write" lock
   if (fSemaphore != (Int_t)INVALID_HANDLE_VALUE)
      WaitForSingleObject((HANDLE)fhSemaphore,INFINITE);
#endif
#endif

   // file might have grown, update mapping on reader to new size
   if (!fWritable && fMmallocDesc) {
      if (mmalloc_update_mapping(fMmallocDesc) == -1)
         Error("AcquireSemaphore", "cannot update mapping");
   }

   return 0;
}

//______________________________________________________________________________
Int_t TMapFile::ReleaseSemaphore()
{
   // Release semaphore. Returns 0 if OK, -1 on error.

#ifdef HAVE_SEMOP
#ifndef WIN32
   if (fSemaphore != -1) {
      struct sembuf buf = { 0, 1, SEM_UNDO };
      if (semop(fSemaphore, &buf, 1) == -1) {
#if defined(R__FBSD)
         if (TSystem::GetErrno() == EINVAL)
#else
         if (TSystem::GetErrno() == EIDRM)
#endif
            fSemaphore = -1;
      }
   }
#else
   if (fSemaphore != (Int_t)INVALID_HANDLE_VALUE)
      ReleaseMutex((HANDLE)fhSemaphore);
#endif
#endif
   return 0;
}

//______________________________________________________________________________
void TMapFile::Close(Option_t *option)
{
   // Close a mapped file. First detach mapped memory then close file.
   // No member functions of a TMapFile that was opened in write mode
   // may be called after Close() (this includes, of course, "delete" which
   // would call the dtors). The option="dtor" is only used when called
   // via the ~TMapFile.

   if (!fMmallocDesc) return;

   TMapFile *shadow = FindShadowMapFile();
   if (!shadow) {
      Error("Close", "shadow map == 0, should never happen!");
      return;
   }

   gROOT->GetListOfMappedFiles()->Remove(shadow);
   gROOT->GetListOfMappedFiles()->Remove(this);

   if (shadow->fWritable) {
      fWritable = kFALSE;
      DeleteSemaphore();
   }

   if (fMmallocDesc) {
      if (strcmp(option, "dtor"))
         mmalloc_detach(fMmallocDesc);

      // If writable cannot access fMmallocDesc anymore since
      // it points to the just unmapped memory region. Any further
      // access to this TMapFile will cause a crash.
      if (!shadow->fWritable)
         fMmallocDesc = 0;
   }

   if (shadow->fFd != -1)
#ifndef WIN32
      close(shadow->fFd);
#else
      CloseHandle((HANDLE)shadow->fFd);
#endif

   delete shadow;
}

//______________________________________________________________________________
TMapFile *TMapFile::FindShadowMapFile()
{
   // Returns shadow map file.

   TObjLink *lnk = ((TList *)gROOT->GetListOfMappedFiles())->LastLink();
   while (lnk) {
      TMapFile *mf = (TMapFile*)lnk->GetObject();
      if (mf->fVersion == -1 && fBaseAddr == mf->fBaseAddr && fSize == mf->fSize)
         return mf;
      lnk = lnk->Prev();
   }
   return 0;
}

//______________________________________________________________________________
void TMapFile::Print(Option_t *) const
{
   // Print some info about the mapped file.

   Printf("Memory mapped file:   %s", fName);
   Printf("Title:                %s", fTitle);
   if (fMmallocDesc) {
      Printf("Option:               %s", fOption);
      ULong_t size = (ULong_t)((struct mdesc *)fMmallocDesc)->top - fBaseAddr;
      Printf("Mapped Memory region: 0x%lx - 0x%lx (%.2f MB)", fBaseAddr, fBaseAddr + size,
             (float)size/1048576);
      Printf("Current breakval:     0x%lx", (ULong_t)GetBreakval());
   } else
      Printf("Option:               file closed");
}

//______________________________________________________________________________
Bool_t TMapFile::IsFolder() const
{
   // Returns kTRUE in case object is a folder (i.e. contains browsable lists).

   if (fMmallocDesc && fVersion > 0) return kTRUE;
   return kFALSE;
}

//______________________________________________________________________________
void TMapFile::Browse(TBrowser *b)
{
   // Browse contents of TMapFile.

   if (b && fMmallocDesc) {

      AcquireSemaphore();

      TMapRec *mr = GetFirst();
      TKeyMapFile *keymap;
      if (!fBrowseList) fBrowseList = new TList();
      while (OrgAddress(mr)) {
         keymap = (TKeyMapFile*)fBrowseList->FindObject(mr->GetName(fOffset));
         if (!keymap) {
            keymap = new TKeyMapFile(mr->GetName(fOffset),mr->GetClassName(fOffset),this);
            fBrowseList->Add(keymap);
         }
         b->Add(keymap, keymap->GetName());
         mr = mr->GetNext(fOffset);
      }

      ReleaseSemaphore();

   }
}

//______________________________________________________________________________
Bool_t TMapFile::cd(const char *path)
{
   // Cd to associated directory,

   if (fDirectory)
      return fDirectory->cd(path);
   return kFALSE;
}

//______________________________________________________________________________
void TMapFile::ls(Option_t *) const
{
   // List contents of TMapFile.

   if (fMmallocDesc) {

      ((TMapFile*)this)->AcquireSemaphore();

      Printf("%-20s %-20s %-10s", "Object", "Class", "Size");
      if (!fFirst)
         Printf("*** no objects stored in memory mapped file ***");

      TMapRec *mr = GetFirst();
      while (OrgAddress(mr)) {
         Printf("%-20s %-20s %-10d", mr->GetName(fOffset),
                mr->GetClassName(fOffset), mr->fBufSize);
         mr = mr->GetNext(fOffset);
      }

      ((TMapFile*)this)->ReleaseSemaphore();

   }
}

//_______________________________________________________________________
void TMapFile::SumBuffer(Int_t bufsize)
{
   // Increment statistics for buffer sizes of objects in this file.

   fWritten++;
   fSumBuffer  += bufsize;
   fSum2Buffer += bufsize*bufsize;
}

//______________________________________________________________________________
Int_t TMapFile::GetBestBuffer()
{
   // Return the best buffer size for objects in this file.
   //
   // The best buffer size is estimated based on the current mean value
   // and standard deviation of all objects written so far to this file.
   // Returns mean value + one standard deviation.

   if (!fWritten) return TBuffer::kMinimalSize;
   Double_t mean = fSumBuffer/fWritten;
   Double_t rms2 = TMath::Abs(fSum2Buffer/fSumBuffer - mean*mean);
   return (Int_t)(mean + TMath::Sqrt(rms2));
}


//______________________________________________________________________________
TMapFile *TMapFile::Create(const char *name, Option_t *option, Int_t size,
                           const char *title)
{
   // Create a memory mapped file. This opens a file (to which the
   // memory will be mapped) and attaches a memory region to it.
   // Option can be either: "NEW", "CREATE", "RECREATE", "UPDATE"
   // or "READ" (see TFile). The default open mode is "READ". The size
   // argument specifies the maximum size of shared memory file in bytes.
   // TMapFile's can only be created via this method. Create() enforces that
   // a TMapFile is always on the memory mapped heap (when "NEW", "CREATE"
   // or "RECREATE" are used).

   TMapFile *newMapFile;
   new TMapFile(name, title, option, size, newMapFile);

   return newMapFile;
}

//______________________________________________________________________________
void TMapFile::SetMapAddress(Long_t addr)
{
   // Set preferred map address. Find out preferred map address as follows:
   // 1) Run consumer program to find the preferred map address:
   //       $ root
   //       root [0] m = TMapFile::Create("dummy.map", "recreate", 10000000);
   //       root [1] m.Print()
   //       Memory mapped file:   dummy.map
   //       Title:
   //       Option:               CREATE
   //       Mapped Memory region: 0x40b4c000 - 0x40d95f00 (2.29 MB)
   //       Current breakval:     0x40b53000
   //       root [2] .q
   //       $ rm dummy.map
   //    Remember begin of mapped region, i.e. 0x40b4c000
   //
   // 2) Add to producer program, just before creating the TMapFile:
   //       TMapFile::SetMapAddress(0x40b4c000);
   //
   // Repeat this if more than one map file is being used.
   //
   // The above procedure allow programs using, e.g., different number of
   // shared libraries (that cause the default mapping address to be
   // different) to create shared memory regions in the same location
   // without overwriting a shared library. The above assumes the consumer
   // program is larger (i.e. has more shared memory occupied) than the
   // producer. If this is not true inverse the procedure.

   fgMapAddress = addr;
}

//______________________________________________________________________________
void *TMapFile::MapToAddress()
{
   // Return the base address at which we would like the next TMapFile's
   // mapped data to start.
   //
   // For now, we let the system decide (start address 0). There are
   // a lot of issues to deal with here to make this work reasonably,
   // including:
   //
   // - Avoid memory collisions with existing mapped address spaces
   //
   // - Reclaim address spaces when their mmalloc heaps are unmapped
   //
   // - When mmalloc heaps are shared between processes they have to be
   //   mapped at the same addresses in each
   //
   // Once created, a mmalloc heap that is to be mapped back in must be
   // mapped at the original address.  I.E. each TMapFile will expect
   // to be remapped at it's original address. This becomes a problem if
   // the desired address is already in use.

#ifdef HAVE_MMAP
   if (TStorage::HasCustomNewDelete())
      return (void *)fgMapAddress;
   else
      return (void *)-1;
#else
   return (void *)-1;
#endif
}

//______________________________________________________________________________
void TMapFile::operator delete(void *ptr)
{
   // Need special "operator delete" in which we close the shared memory.
   // This has to be done after the dtor chain has been finished.

   mmalloc_detach(fgMmallocDesc);
   fgMmallocDesc = 0;

   TObject::operator delete(ptr);
}
