/******************************************************************************/
/*                                                                            */
/*                      X r d X r o o t d F i l e . c c                       */
/*                                                                            */
/* (c) 2004 by the Board of Trustees of the Leland Stanford, Jr., University  */
/*   Produced by Andrew Hanushevsky for Stanford University under contract    */
/*              DE-AC02-76-SFO0515 with the Department of Energy              */
/*                                                                            */
/* This file is part of the XRootD software suite.                            */
/*                                                                            */
/* XRootD is free software: you can redistribute it and/or modify it under    */
/* the terms of the GNU Lesser General Public License as published by the     */
/* Free Software Foundation, either version 3 of the License, or (at your     */
/* option) any later version.                                                 */
/*                                                                            */
/* XRootD is distributed in the hope that it will be useful, but WITHOUT      */
/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or      */
/* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public       */
/* License for more details.                                                  */
/*                                                                            */
/* You should have received a copy of the GNU Lesser General Public License   */
/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file  */
/* COPYING (GPL license).  If not, see <http://www.gnu.org/licenses/>.        */
/*                                                                            */
/* The copyright holder's institutional names and contributor's names may not */
/* be used to endorse or promote products derived from this software without  */
/* specific prior written permission of the institution or contributor.       */
/******************************************************************************/

#include <cinttypes>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
  
#include "XrdSys/XrdSysError.hh"
#include "XrdSys/XrdSysPthread.hh"
#include "XrdSfs/XrdSfsInterface.hh"
#include "XrdXrootd/XrdXrootdAioFob.hh"
#include "XrdXrootd/XrdXrootdFile.hh"
#include "XrdXrootd/XrdXrootdFileLock.hh"
#include "XrdXrootd/XrdXrootdMonFile.hh"
#include "XrdXrootd/XrdXrootdMonitor.hh"
#include "XrdXrootd/XrdXrootdPgwFob.hh"
#define  TRACELINK this
#include "XrdXrootd/XrdXrootdTrace.hh"
 
/******************************************************************************/
/*                               G l o b a l s                                */
/******************************************************************************/

#ifndef NODEBUG  
extern XrdSysTrace       XrdXrootdTrace;
#endif

       XrdXrootdFileLock *XrdXrootdFile::Locker;

       int              XrdXrootdFile::sfOK         = 1;
       const char      *XrdXrootdFile::TraceID      = "File";
       const char      *XrdXrootdFileTable::TraceID = "FileTable";
       const char      *XrdXrootdFileTable::ID      = "";

       XrdXrootdFile   *XrdXrootdFileTable::heldSpotP = (XrdXrootdFile *)1;

namespace
{
             XrdSysError   *eDest;

static const unsigned long  heldSpotV = 1UL;;

static const unsigned long  heldMask = ~1UL;
}

/******************************************************************************/
/*                        x r d _ F i l e   C l a s s                         */
/******************************************************************************/
/******************************************************************************/
/*                           C o n s t r u c t o r                            */
/******************************************************************************/
  
XrdXrootdFile::XrdXrootdFile(const char *id, const char *path, XrdSfsFile *fp,
                             char mode, bool async, struct stat *sP)
                            : XrdSfsp(fp), mmAddr(0), FileKey(strdup(path)),
                              FileMode(mode), AsyncMode(async),
                              aioFob(0), pgwFob(0), fhProc(0),
                              ID(id), refCount(0), syncWait(0)
{
    static XrdSysMutex seqMutex;
    struct stat buf;
    off_t mmSize;

// Initialize statistical counters
//
    Stats.Init();

// Get the file descriptor number for sendfile() processing
//
   if (!sfOK || fp->fctl(SFS_FCTL_GETFD, 0, fp->error) != SFS_OK)
      {fdNum = -1;
       sfEnabled = false;
      } else {
       fdNum = fp->error.getErrInfo();
       sfEnabled = (fdNum >= 0 || fdNum == (int)SFS_SFIO_FDVAL);
      }

// Determine if file is memory mapped
//
   if (fp->getMmap((void **)&mmAddr, mmSize) != SFS_OK) isMMapped = false;
      else {isMMapped = (mmSize ? true : false);
            Stats.fSize = static_cast<long long>(mmSize);
           }

// Get file status information (we need it) and optionally return it to caller
//
   if (sP || !isMMapped)
      {if (!sP) sP = &buf;
       fp->stat(sP);
       if (!isMMapped) Stats.fSize = static_cast<long long>(sP->st_size);
      }
}
  
/******************************************************************************/
/*                                  I n i t                                   */
/******************************************************************************/
  
void XrdXrootdFile::Init(XrdXrootdFileLock *lp, XrdSysError *erP, bool sfok)
{
   Locker = lp;
   eDest  = erP;
   sfOK   = sfok;
}

/******************************************************************************/
/*                            D e s t r u c t o r                             */
/******************************************************************************/
  
XrdXrootdFile::~XrdXrootdFile()
{
   if (aioFob) aioFob->Reset();

   Serialize(); // Make sure there are no outstanding references

   if (XrdSfsp)
      {TRACEI(FS, "closing " <<FileMode <<' ' <<FileKey);
       delete XrdSfsp;
       XrdSfsp = 0;
       Locker->Unlock(FileKey, FileMode);
      }

   if (fhProc) fhProc->Avail(fHandle);

   if (aioFob) delete aioFob;

   if (pgwFob) delete pgwFob;

   if (FileKey) free(FileKey); // Must be the last thing deleted!
}

/******************************************************************************/
/*                                   R e f                                    */
/******************************************************************************/
  
void XrdXrootdFile::Ref(int num)
{

// Change the reference counter and check if anyone is waiting
//
   fileMutex.Lock();
   refCount += num;
   TRACEI(FSAIO,"File::Ref="<<refCount<<" after +"<<num<<' '<<FileKey);
   if (num < 0 && syncWait && refCount <= 0)
      {syncWait->Post();
       syncWait = nullptr;
      }
   fileMutex.UnLock();
}

/******************************************************************************/
/*                             S e r i a l i z e                              */
/******************************************************************************/
  
void XrdXrootdFile::Serialize()
{

// Wait until the reference count reaches zero
//
   fileMutex.Lock();
   TRACEI(FSAIO, "serializing access "<<FileMode<<" Ref="<<refCount<<' '<<FileKey);
   if (refCount > 0)
      {XrdSysSemaphore mySem(0);
       syncWait = &mySem;
       fileMutex.UnLock();
       mySem.Wait();
      } else fileMutex.UnLock();
}
  
/******************************************************************************/
/*                   x r d _ F i l e T a b l e   C l a s s                    */
/******************************************************************************/
/******************************************************************************/
/*                                   A d d                                    */
/******************************************************************************/
  
int XrdXrootdFileTable::Add(XrdXrootdFile *fp)
{
   const int allocsz = XRD_FTABSIZE*sizeof(fp);
   XrdXrootdFile **newXTab, **oldXTab;
   int i;

// If we have a file handle processor, see if it can give us a file handle
// that's already in our table.
//
   if (fhProc && (i = fhProc->Get()) >= 0) 
      {XrdXrootdFile **fP;
       if (i < XRD_FTABSIZE)   fP = &FTab[i];
          else {i -= XRD_FTABSIZE;
                if (XTab && i < XTnum) fP = &XTab[i];
                   else fP = 0;
                i += XRD_FTABSIZE;
               }
       if (fP && *fP == heldSpotP)
          {*fP = fp;
           TRACEI(FS, "reusing fh " <<i <<" for " <<fp->FileKey);
           return i;
          }
       char fhn[32];
       snprintf(fhn, sizeof(fhn), "%d", i);
       eDest->Emsg("FTab_Add", "Invalid recycled fHandle",fhn,"ignored.");
      }

// Find a free spot in the internal table
//
   for (i = FTfree; i < XRD_FTABSIZE; i++) if (!FTab[i]) break;

   if (i < XRD_FTABSIZE)
      {FTab[i] = fp; FTfree = i+1; return i;}

// Allocate an external table if we do not have one
//
   if (!XTab)
      {if (!(XTab = (XrdXrootdFile **)malloc(allocsz))) return -1;
       memset((void *)XTab, 0, allocsz);
       XTnum   = XRD_FTABSIZE;
       XTfree  = 1;
       XTab[0] = fp;
       return XRD_FTABSIZE;
      }

// Find a free spot in the external table
//
   for (i = XTfree; i < XTnum; i++) if (!XTab[i]) break;
   if (i < XTnum)
      {XTab[i] = fp; XTfree = i+1; return i+XRD_FTABSIZE;}

// Extend the table
//
   if (!(newXTab = (XrdXrootdFile **)malloc(XTnum*sizeof(XrdXrootdFile *)+allocsz)))
      return -1;
   memcpy((void *)newXTab, (const void *)XTab, XTnum*sizeof(XrdXrootdFile *));
   memset((void *)(newXTab+XTnum), 0, allocsz);
   oldXTab = XTab;
   XTab = newXTab;
   XTab[XTnum] = fp;
   i = XTnum;
   XTfree = XTnum+1;
   XTnum += XRD_FTABSIZE;
   free(oldXTab);
   return i+XRD_FTABSIZE;
}
 
/******************************************************************************/
/*                                   D e l                                    */
/******************************************************************************/
  
XrdXrootdFile *XrdXrootdFileTable::Del(XrdXrootdMonitor *monP, int fnum,
                                       bool dodel)
{
   union {XrdXrootdFile *fp; unsigned long fv;};
   XrdXrootdFile *repVal = (dodel ? 0 : heldSpotP);
   int  fh = fnum;

   if (fnum < XRD_FTABSIZE) 
      {fp = FTab[fnum];
       FTab[fnum] = repVal;
       if (fnum < FTfree) FTfree = fnum;
      } else {
       fnum -= XRD_FTABSIZE;
       if (XTab && fnum < XTnum)
          {fp = XTab[fnum];
           XTab[fnum] = repVal;
           if (fnum < XTfree) XTfree = fnum;
          }
           else fp = 0;
      }

   fv &= heldMask;

   if (fp)
      {XrdXrootdFileStats &Stats = fp->Stats;
   //!!! For now we add pgreads to normal reads and pgwrite to normal writes
   //!!! Once we figure out how to report them separately, we need to do this.

       Stats.xfr.read  += Stats.prw.rBytes;
       Stats.xfr.write += Stats.prw.wBytes;
       Stats.ops.read  += Stats.prw.rCount;
       Stats.ops.write += Stats.prw.wCount; // Doesn't include retries!!!

       if (monP) monP->Close(Stats.FileID,
                             Stats.xfr.read + Stats.xfr.readv,
                             Stats.xfr.write);
       if (Stats.MonEnt != -1) XrdXrootdMonFile::Close(&Stats, false);
       if (dodel) {delete fp; fp = 0;}  // Will do the close
          else {if (!fhProc) fhProc = new XrdXrootdFileHP;
                   else fhProc->Ref();
                fp->fHandle = fh;
                fp->fhProc  = fhProc;
                TRACEI(FS, "defer fh " <<fh <<" del for " <<fp->FileKey);
               }
      }
   return fp;
}

/******************************************************************************/
/*                               R e c y c l e                                */
/******************************************************************************/
  
// WARNING! The object subject to this method must be serialized. There can
// be no active requests on link associated with this object at the time the
// destructor is called. The same restrictions apply to Add() and Del().
//
void XrdXrootdFileTable::Recycle(XrdXrootdMonitor *monP)
{
   int i;

// Delete all objects from the internal table (see warning)
//
   FTfree = 0;
   for (i = 0; i < XRD_FTABSIZE; i++)
       if (FTab[i] && FTab[i] != heldSpotP)
          {XrdXrootdFileStats &Stats = FTab[i]->Stats;
           if (monP) monP->Close(Stats.FileID,
                                 Stats.xfr.read+Stats.xfr.readv,
                                 Stats.xfr.write);
           if (Stats.MonEnt != -1) XrdXrootdMonFile::Close(&Stats, true);
           delete FTab[i]; FTab[i] = 0;
          }

// Delete all objects from the external table (see warning)
//
if (XTab)
  {for (i = 0; i < XTnum; i++)
      {if (XTab[i] && XTab[i] != heldSpotP)
          {XrdXrootdFileStats &Stats = XTab[i]->Stats;
           if (monP) monP->Close(Stats.FileID,
                                 Stats.xfr.read+Stats.xfr.readv,
                                 Stats.xfr.write);
           if (Stats.MonEnt != -1) XrdXrootdMonFile::Close(&Stats, true);
           delete XTab[i];
          }
       }
   free(XTab); XTab = 0; XTnum = 0; XTfree = 0;
  }

// If we have a filehandle processor, delete it. Note that it will stay alive
// until all requests for file handles against it are resolved.
//
   if (fhProc) fhProc->Delete();

// Delete this object
//
   delete this;
}
  
/******************************************************************************/
/*                       P r i v a t e   M e t h o d s                        */
/******************************************************************************/
/******************************************************************************/
/*                               b i n 2 h e x                                */
/******************************************************************************/
  
int XrdXrootdFile::bin2hex(char *outbuff, char *inbuff, int inlen)
{
    static const char hv[] = "0123456789abcdef";
    int i, j = 0;

// Skip leading zeroes
//
    for (i = 0; i < inlen; i++) if (inbuff[i]) break;
    if (i >= inlen)
       {outbuff[0] = '0'; outbuff[1] = '\0'; return 1;}

// Format the data
//
    for (     ; i < inlen; i++)
       {outbuff[j++] = hv[(inbuff[i] >> 4) & 0x0f];
        outbuff[j++] = hv[ inbuff[i]       & 0x0f];
       }
     outbuff[j] = '\0';
     return j;
}
