/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
 * 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 <string.h>
#include "sqlite3.h"
#include "mozilla/net/IOActivityMonitor.h"

namespace {

// The last VFS version for which this file has been updated.
constexpr int kLastKnowVfsVersion = 3;

// The last io_methods version for which this file has been updated.
constexpr int kLastKnownIOMethodsVersion = 3;

using namespace mozilla;
using namespace mozilla::net;

struct BaseFile {
  // Base class.  Must be first
  sqlite3_file base;
  // The filename
  char* location;
  // This points to the underlying sqlite3_file
  sqlite3_file pReal[1];
};

int BaseClose(sqlite3_file* pFile) {
  BaseFile* p = (BaseFile*)pFile;
  delete[] p->location;
  return p->pReal->pMethods->xClose(p->pReal);
}

int BaseRead(sqlite3_file* pFile, void* zBuf, int iAmt, sqlite_int64 iOfst) {
  BaseFile* p = (BaseFile*)pFile;
  int rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
  if (rc == SQLITE_OK && IOActivityMonitor::IsActive()) {
    IOActivityMonitor::Read(nsDependentCString(p->location), iAmt);
  }
  return rc;
}

int BaseWrite(sqlite3_file* pFile, const void* zBuf, int iAmt,
              sqlite_int64 iOfst) {
  BaseFile* p = (BaseFile*)pFile;
  int rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
  if (rc == SQLITE_OK && IOActivityMonitor::IsActive()) {
    IOActivityMonitor::Write(nsDependentCString(p->location), iAmt);
  }
  return rc;
}

int BaseTruncate(sqlite3_file* pFile, sqlite_int64 size) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xTruncate(p->pReal, size);
}

int BaseSync(sqlite3_file* pFile, int flags) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xSync(p->pReal, flags);
}

int BaseFileSize(sqlite3_file* pFile, sqlite_int64* pSize) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xFileSize(p->pReal, pSize);
}

int BaseLock(sqlite3_file* pFile, int eLock) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xLock(p->pReal, eLock);
}

int BaseUnlock(sqlite3_file* pFile, int eLock) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xUnlock(p->pReal, eLock);
}

int BaseCheckReservedLock(sqlite3_file* pFile, int* pResOut) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
}

int BaseFileControl(sqlite3_file* pFile, int op, void* pArg) {
#ifdef EARLY_BETA_OR_EARLIER
  // Persist auxiliary files (-shm and -wal) on disk, because creating and
  // deleting them may be expensive on slow storage.
  // Only do this when there is a journal size limit, so the journal is
  // truncated instead of deleted on shutdown, that feels safer if the user
  // moves a database file around without its auxiliary files.
  MOZ_ASSERT(
      ::sqlite3_compileoption_used("DEFAULT_JOURNAL_SIZE_LIMIT"),
      "A journal size limit ensures the journal is truncated on shutdown");
  if (op == SQLITE_FCNTL_PERSIST_WAL) {
    *static_cast<int*>(pArg) = 1;
    return SQLITE_OK;
  }
#endif
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
}

int BaseSectorSize(sqlite3_file* pFile) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xSectorSize(p->pReal);
}

int BaseDeviceCharacteristics(sqlite3_file* pFile) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
}

int BaseShmMap(sqlite3_file* pFile, int iPg, int pgsz, int bExtend,
               void volatile** pp) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xShmMap(p->pReal, iPg, pgsz, bExtend, pp);
}

int BaseShmLock(sqlite3_file* pFile, int offset, int n, int flags) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xShmLock(p->pReal, offset, n, flags);
}

void BaseShmBarrier(sqlite3_file* pFile) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xShmBarrier(p->pReal);
}

int BaseShmUnmap(sqlite3_file* pFile, int deleteFlag) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xShmUnmap(p->pReal, deleteFlag);
}

int BaseFetch(sqlite3_file* pFile, sqlite3_int64 iOfst, int iAmt, void** pp) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xFetch(p->pReal, iOfst, iAmt, pp);
}

int BaseUnfetch(sqlite3_file* pFile, sqlite3_int64 iOfst, void* pPage) {
  BaseFile* p = (BaseFile*)pFile;
  return p->pReal->pMethods->xUnfetch(p->pReal, iOfst, pPage);
}

int BaseOpen(sqlite3_vfs* vfs, const char* zName, sqlite3_file* pFile,
             int flags, int* pOutFlags) {
  BaseFile* p = (BaseFile*)pFile;
  if (zName) {
    p->location = new char[7 + strlen(zName) + 1];
    strcpy(p->location, "file://");
    strcpy(p->location + 7, zName);
  } else {
    p->location = new char[8];
    strcpy(p->location, "file://");
  }

  sqlite3_vfs* origVfs = (sqlite3_vfs*)(vfs->pAppData);
  int rc = origVfs->xOpen(origVfs, zName, p->pReal, flags, pOutFlags);
  if (rc) {
    return rc;
  }
  if (p->pReal->pMethods) {
    // If the io_methods version is higher than the last known one, you should
    // update this VFS adding appropriate IO methods for any methods added in
    // the version change.
    MOZ_ASSERT(p->pReal->pMethods->iVersion == kLastKnownIOMethodsVersion);
    static const sqlite3_io_methods IOmethods = {
        kLastKnownIOMethodsVersion, /* iVersion */
        BaseClose,                  /* xClose */
        BaseRead,                   /* xRead */
        BaseWrite,                  /* xWrite */
        BaseTruncate,               /* xTruncate */
        BaseSync,                   /* xSync */
        BaseFileSize,               /* xFileSize */
        BaseLock,                   /* xLock */
        BaseUnlock,                 /* xUnlock */
        BaseCheckReservedLock,      /* xCheckReservedLock */
        BaseFileControl,            /* xFileControl */
        BaseSectorSize,             /* xSectorSize */
        BaseDeviceCharacteristics,  /* xDeviceCharacteristics */
        BaseShmMap,                 /* xShmMap */
        BaseShmLock,                /* xShmLock */
        BaseShmBarrier,             /* xShmBarrier */
        BaseShmUnmap,               /* xShmUnmap */
        BaseFetch,                  /* xFetch */
        BaseUnfetch                 /* xUnfetch */
    };
    pFile->pMethods = &IOmethods;
  }

  return SQLITE_OK;
}

}  // namespace

namespace mozilla::storage {

const char* GetBaseVFSName(bool exclusive) {
  return exclusive ? "base-vfs-excl" : "base-vfs";
}

UniquePtr<sqlite3_vfs> ConstructBaseVFS(bool exclusive) {
#if defined(XP_WIN)
#  define EXPECTED_VFS "win32"
#  define EXPECTED_VFS_EXCL "win32"
#else
#  define EXPECTED_VFS "unix"
#  define EXPECTED_VFS_EXCL "unix-excl"
#endif

  if (sqlite3_vfs_find(GetBaseVFSName(exclusive))) {
    return nullptr;
  }

  bool found;
  sqlite3_vfs* origVfs;
  if (!exclusive) {
    // Use the non-exclusive VFS.
    origVfs = sqlite3_vfs_find(nullptr);
    found = origVfs && origVfs->zName && !strcmp(origVfs->zName, EXPECTED_VFS);
  } else {
    origVfs = sqlite3_vfs_find(EXPECTED_VFS_EXCL);
    found = (origVfs != nullptr);
  }
  if (!found) {
    return nullptr;
  }

  // If the VFS version is higher than the last known one, you should update
  // this VFS adding appropriate methods for any methods added in the version
  // change.
  MOZ_ASSERT(origVfs->iVersion == kLastKnowVfsVersion);

  sqlite3_vfs vfs = {
      kLastKnowVfsVersion,                                    /* iVersion  */
      origVfs->szOsFile + static_cast<int>(sizeof(BaseFile)), /* szOsFile */
      origVfs->mxPathname,                                    /* mxPathname */
      nullptr,                                                /* pNext */
      GetBaseVFSName(exclusive),                              /* zName */
      origVfs,                                                /* pAppData */
      BaseOpen,                                               /* xOpen */
      origVfs->xDelete,                                       /* xDelete */
      origVfs->xAccess,                                       /* xAccess */
      origVfs->xFullPathname,     /* xFullPathname */
      origVfs->xDlOpen,           /* xDlOpen */
      origVfs->xDlError,          /* xDlError */
      origVfs->xDlSym,            /* xDlSym */
      origVfs->xDlClose,          /* xDlClose */
      origVfs->xRandomness,       /* xRandomness */
      origVfs->xSleep,            /* xSleep */
      origVfs->xCurrentTime,      /* xCurrentTime */
      origVfs->xGetLastError,     /* xGetLastError */
      origVfs->xCurrentTimeInt64, /* xCurrentTimeInt64 */
      origVfs->xSetSystemCall,    /* xSetSystemCall */
      origVfs->xGetSystemCall,    /* xGetSystemCall */
      origVfs->xNextSystemCall    /* xNextSystemCall */
  };

  return MakeUnique<sqlite3_vfs>(vfs);
}

}  // namespace mozilla::storage
