/*=========================================================================
 * Copyright (C) VMware, Inc. 1986-2008.  All Rights Reserved.
 *
 * Name - offlinegc.c
 *
 * Description - Implementation of offline garbage collection user
 *               action routines for UNIX systems on GemStone 6.x.
 *
 * This code is supplied "as-is" and is not supported by GemStone.
 * However feedback and bug fixes are welcome.
 *
 * Written by: Norm Green, GemStone Engineering
 *
 * Updated on September 11, 2007.  Output file format not compatible with
 * previous versions.  See offlinegc.txt for more details.
 *
 * $Id: offlinegc6x.cc,v 1.2 2008-01-09 22:50:07 stever Exp $
 *========================================================================
 */

/* enable support for files larger than 2 GB */
#define _LARGEFILE64_SOURCE 1

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "gciua.hf"

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE 1
#endif

#ifndef LONG_MAX
#define LONG_MAX        2147483647L
#endif

#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

/* number of bytes in oop file header */
#define HDR_SIZE 16

/* max array size of a small object */
#define MAX_OOPS_PER_ARRAY 2024

/* hidden set number of GcCandidates hidden set */
#define GC_CANDIDATES_HIDDEN_SET 34

/* Make this an even multiple of MAX_OOPS_PER_ARRAY
 * 259072 oops = 1036288 bytes = 0.988 MB
 */
#define OOP_BUFFER_SIZE (MAX_OOPS_PER_ARRAY * 128)

/* junk for swizzling byte order of data read from files to native byte order */
#define NATIVE_MASK_LONG  0X3020100L
#define MY_CHAR_BIT 8

#define OOP_TO_BIT(anOop) ((long)((unsigned long)(anOop) >> 2))

#define OOP_TAG_POM_OOP  1  /* 2r01  disk oop */
#define BIT_TO_OOP(aBitNum) \
 ((unsigned long)( ((aBitNum) << 2) | OOP_TAG_POM_OOP) )

/* swizzle a 4 byte long to native byte order */
#define GET_NATIVE_LONG(bufPtr, tl)\
   ((bufPtr) += 4, \
   ((long)(*((bufPtr)-4)) << (MY_CHAR_BIT * (tl)[0])) | \
   ((long)(*((bufPtr)-3)) << (MY_CHAR_BIT * (tl)[1])) | \
   ((long)(*((bufPtr)-2)) << (MY_CHAR_BIT * (tl)[2])) | \
   ((long)(*((bufPtr)-1)) << (MY_CHAR_BIT * (tl)[3])) )


/* alignment macros for buffers used with disk IO to enable DMA */
#define ALIGN_SIZE 512L
#define ALIGN_BUFFER(val) \
 (((unsigned long)val + (ALIGN_SIZE - 1)) & (unsigned long)~(ALIGN_SIZE - 1))

/* oop of SystemRepository.  Really should be in gcioop.ht header file */
#define OOP_SYSTEM_REPOSITORY 3265

/* oop of kernel symbol #manualBegin */
#define OOP_SYM_MANUAL_BEGIN 3689

/* oop of kernel symbol #autoBegin */
#define OOP_SYM_AUTO_BEGIN 3693

/* read/write page buffer sizes defined in number of 8K pages */
#define FDC_PAGE_BUFFER_DEFAULT 1000
#define FDC_PAGE_BUFFER_MIN 100
#define FDC_PAGE_BUFFER_MAX 6000

 /* abort every 60 s during oop file write or load */
#define ABORT_INTERVAL 60

#define PRINT_TIME (printTime(time(NULL)))

typedef struct {
  ByteType *baseBuf;
  ByteType *alignedBuf;
  long requestedBufSize;
  long realBufSize;
} AlignedBufferSType;

#define INIT_BUF_STRUCT(buf) \
  buf.baseBuf = NULL;\
  buf.alignedBuf = NULL;\
  buf.realBufSize = 0;\
  buf.requestedBufSize = 0;

/* Global Variables go HERE */
static char error[2000];
/* Global Variables end HERE */



static OopType handleError(const char *errorString)
{
  return GciNewString(errorString);
}

static char *printTime(time_t t)
{
  static char result[100];
  struct tm *theTime;

  theTime = localtime(&t);
  strftime(result, sizeof(result) - 1, "%a %d %b %Y %X %Z", theTime);
  return &result[0];
}

static OopType getAlignedBuffer(AlignedBufferSType *bufStruct)
{

  if (bufStruct == NULL)
    return OOP_NIL;

  bufStruct->baseBuf = NULL;
  bufStruct->alignedBuf = NULL;
 
  bufStruct->realBufSize = bufStruct->requestedBufSize + ALIGN_SIZE;

  bufStruct->baseBuf = (ByteType *) malloc( (size_t) bufStruct->realBufSize);
  if (bufStruct->baseBuf == NULL) {
    bufStruct->realBufSize = 0;
    sprintf(error, "error: malloc for %ld bytes failed", bufStruct->realBufSize);
    return handleError(error);
  }

  bufStruct->alignedBuf = (ByteType *) ALIGN_BUFFER(bufStruct->baseBuf);

  return OOP_TRUE;
}

static void freeAlignedBuffer(AlignedBufferSType *bufStruct)
{

  if (bufStruct == NULL)
    return;

  if (bufStruct->baseBuf != NULL)
    free(bufStruct->baseBuf);

  bufStruct->baseBuf = NULL;
  bufStruct->alignedBuf = NULL;
  bufStruct->realBufSize = 0;
  bufStruct->requestedBufSize = 0;
}

static OopType seedWeakReferenceSet(void)
{
  OopType result = OOP_TRUE;
  /* seed the weak reference set */
  if (OOP_NIL == GciPerform(OOP_SYSTEM_REPOSITORY, "_buildWeakRefSet", NULL, 0)) {
    result = handleError("error: SystemRepository _buildWeakRefSet failed");
  }
  return result;
}

static OopType handleWriteError(void)
{
  sprintf(error, "error: write() failed with errno %d", errno);
  return handleError(error);
}

static OopType handleReadError(void)
{
  sprintf(error, "error: read() failed with errno %d", errno);
  return handleError(error);
}

/* do a write and rety if interrupted by EINTR nonsense */
static BoolType doWrite(int fd, void *src, long numBytes, OopType *errorString)
{
  ssize_t writeResult;

  if (src == NULL)
    return FALSE;
  if (errorString != NULL)
    *errorString = OOP_NIL;

  for (;;) {
    errno = 0;
    writeResult = write(fd, src, (size_t)numBytes);
    if ( (long)writeResult == numBytes)
      break;

    if (writeResult == -1) {
      if (errno == EINTR) /* interrupted, just retry */
	continue;
      if (errorString != NULL)
	*errorString = handleWriteError();
      return FALSE;
    }
    else {
      if (errorString != NULL) {
	sprintf(error, "error: write() wrote %ld bytes but expected %ld bytes", 
		writeResult, numBytes);
	*errorString = handleError(error);
      }
      return FALSE;
    }
  }

  return TRUE;
}

/* write a long to the file which will help us determine what byte ordering
 * scheme (UNIX or Intel) was used to write the file.
 */
static OopType writeTransformToFile(int fd)
{
 ByteType  swizzleLongTransform[sizeof(long)];
 long *longPtr;
 OopType errorOop;

 longPtr = (long *)swizzleLongTransform;
 *longPtr = NATIVE_MASK_LONG;
 
 if (!doWrite(fd, (void *)swizzleLongTransform, sizeof(swizzleLongTransform), &errorOop))
   return errorOop;

 return OOP_TRUE;

}

/* return the file size in bytes as an unsigned long */
static unsigned long getFileSize(int fd, OopType *errorString)
{
  struct stat64 buf;
  int result;
  
  result = fstat64(fd, &buf);

  if (result == -1) {
    sprintf(error, "fstat64 call failed with errno=%d", errno);
    if (errorString != NULL) 
      *errorString = handleError(error);
    return 0;
  }
  
  return (unsigned long) buf.st_size;
}

/*=========================================================================
 *
 * Name - writeOopsToFile
 *
 * Description -
 
 * Write the oops in resultArray to the file descriptor fd.
 * fd must reference a file open for writing in append mode.
 * resultArray must be an existing instance of Array.
 *
 * Oops are written as unsigned longs to the file.
 * The file size will be 8 + (numOops * 4) bytes in size.
 *
 * No byte order swizzling is done here.  We will swizzle the byte ordering
 * when we read the file, if required.
 *
 * Returns - resultArray on success, and String object with an error 
 *           message on error.
 *========================================================================
 */
static OopType writeOopsToFile(int fd, OopType resultArray)
{
  long arraySize, offset, numLeft, numThisTime;
  int64_t _arraySize;
  size_t bufferSize;
  OopType *oopBuffer, result;
  long *someLong;
  time_t lastAbortTime, timeNow, startTime, endTime;
  AlignedBufferSType bufStruct;

  INIT_BUF_STRUCT(bufStruct);

  arraySize = GciFetchSize(resultArray);
  _arraySize = (int64_t) arraySize;
  if (arraySize == 0) { /* no garbage found by FDC */
    result = handleError("No garbage objects were found, so no oop file was created.");
    goto TILT;
  }

  bufStruct.requestedBufSize = (long) (OOP_BUFFER_SIZE * sizeof(OopType));

  result = getAlignedBuffer(&bufStruct); 
  if (result != OOP_TRUE) goto TILT;

  startTime = time(NULL);

  oopBuffer = (OopType *) bufStruct.alignedBuf;

  /* first 4 bytes are the transform codes for big / little endian */
  result = writeTransformToFile(fd);
  if (result != OOP_TRUE) goto TILT;

  /* next 4 bytes are the size of oop number type (4) */
  {
    int oopNumSize = sizeof(OopNumberType);
    if (!doWrite(fd, (void *)&oopNumSize, sizeof(int), &result))
      goto TILT;
  }

  /* next 8 bytes are the number of oops in the file */
  if (!doWrite(fd, (void *)&_arraySize, sizeof(_arraySize), &result))
    goto TILT;

  GciAbort();
  lastAbortTime = time(NULL);

  offset = 0;
  for (;;) {
    int i;
    numLeft = arraySize - offset;
    if (numLeft <= 0)
      break;

    numThisTime = MIN(OOP_BUFFER_SIZE, numLeft);
    GciFetchOops(resultArray, offset + 1, oopBuffer, numThisTime);
    for (i = 0; i < numThisTime; i++) {
      OopType thisOne = oopBuffer[i];
      oopBuffer[i] = OOP_TO_BIT(thisOne); /* write to the file as bits, not oops */
    }
    if (!doWrite(fd, (void *)oopBuffer, (long)(numThisTime * sizeof(OopType)), &result))
      goto TILT;

    offset += numThisTime;
    timeNow = time(NULL);
    if (timeNow >= lastAbortTime + ABORT_INTERVAL) {
      GciAbort();
      lastAbortTime = timeNow;
    }
  }

  /* success if we get here */
  result = resultArray;

  endTime = time(NULL);
  printf("Finished writing %ld oops to file at %s\n", arraySize, printTime(endTime) );
  printf("  elapsed time for writing oops=%ld seconds.\n", endTime - startTime);
  printf("  file size=%lu bytes\n",  getFileSize(fd, NULL) );
  fflush(stdout);

TILT:;
  freeAlignedBuffer(&bufStruct);
  return result;
}

/* do a read and rety if interrupted by EINTR nonsense */
static BoolType doRead(int fd, void *dest, long numBytes, OopType *errorString)
{
  ssize_t readResult;

  if (dest == NULL)
    return FALSE;

  if (errorString != NULL)
    *errorString = OOP_NIL;

  for (;;) {
    errno = 0;
    readResult = read(fd, dest, numBytes);
    if ( (long)readResult == numBytes)
      break;

    if (readResult == -1) {
      if (errno == EINTR) /* interrupted, just retry */
	continue;
      if (errorString != NULL)
	*errorString = handleReadError();
      return FALSE;
    }
    else {
      if (errorString != NULL) {
	sprintf(error, "error: read returned %ld bytes but expected %ld bytes", 
		readResult, numBytes);
	*errorString = handleError(error);
      }
      return FALSE;
    }
  }
  return TRUE;
}

static long swizzleInt(ByteType *swizzleTransform, long rawInt)
{
  ByteType *bufPtr = (ByteType *) &rawInt;
  return GET_NATIVE_LONG(bufPtr, swizzleTransform);
}


static uint64_t swizzleInt64(uint64_t arg)
{
  ByteType *bufPtr = (ByteType *) &arg;
  uint64_t result = bufPtr[7];

  result = (result << 8) | bufPtr[6];
  result = (result << 8) | bufPtr[5];
  result = (result << 8) | bufPtr[4];
  result = (result << 8) | bufPtr[3];
  result = (result << 8) | bufPtr[2];
  result = (result << 8) | bufPtr[1];
  result = (result << 8) | bufPtr[0];
  return result;
}

static OopType checkFileIntegrity(int fd, BoolType *swizzleRequired, long *numOops, ByteType *swizzleTransform)
{
  long swizzleFromFile, swizzleCopy;
  int64_t rawNumOops;
  ByteType *bufPtr;
  unsigned long fileSize, expectedFileSize;
  OopType errorString;
  int oopNumSize;
  fileSize = getFileSize(fd, &errorString);
  if (fileSize == 0)
    return errorString;

  /* read first 4 bytes, which is swizzle transform signature */
  if (!doRead(fd, (void *)&swizzleFromFile, sizeof(long), &errorString))
    return errorString;

  *swizzleRequired = swizzleFromFile != NATIVE_MASK_LONG;

  if (*swizzleRequired) {
    printf("Notice: Oop file has different byte ordering from this platform.  Oops will be swizzled to the correct format.\n");
    fflush(stdout);
    *((long *)swizzleTransform) = swizzleFromFile;
  }

  /* next 4 bytes is oop number size in the file */
  if (!doRead(fd, (void *)&oopNumSize, sizeof(int), &errorString))
    return errorString;

  if (*swizzleRequired) {
    int raw = oopNumSize;
    oopNumSize = swizzleInt(swizzleTransform, raw);
  }

  if (oopNumSize != 4) { /* only 4 byte oops allowed in GS 6.x */
    sprintf(error, "file integrity error, oop number size found was  %d but was expected to be 4 bytes",
	    oopNumSize);
    return handleError(error);
  }

  /* next 8 bytes is number oops in the file */
  if (!doRead(fd, (void *)&rawNumOops, sizeof(rawNumOops), &errorString))
    return errorString;

  /* swizzle the number if needed */
  if (*swizzleRequired) {
    uint64_t arg = (uint64_t) rawNumOops;
    rawNumOops = swizzleInt64(arg);
  }

  if (rawNumOops > LONG_MAX) { /* can never be more than 1B in 6.x */
    sprintf(error, "file integrity error, file claims to contain %lld oops.  Maximum allowed is %ld", 
	    rawNumOops, (long)LONG_MAX);
    return handleError(error);
  }

  *numOops = (long) rawNumOops;

  /* compute the expected file size and ensure it matches actual size */
  expectedFileSize = HDR_SIZE + (*numOops * sizeof(OopType));

  /* file looks OK, file ptr positioned at first oop in the file */
  if (expectedFileSize == fileSize) {
    printf("Successful validation of oop file.  File contains %ld oops\n",
	   *numOops);
    return OOP_TRUE;
  }

  sprintf(error, "file integrity error, file size is %lu bytes but was expected to be %lu bytes",
	  fileSize, expectedFileSize);
  return handleError(error);
}

static OopType loadAndSwizzleOops(int fd, OopType *buffer, long numOops, BoolType swizzleRequired, ByteType *swizzleTransform)
{
  OopType errorString;
  OopType *oopPtr, *oopLimitPtr;

  if (!doRead(fd, (void *)buffer, numOops * sizeof(OopType), &errorString))
    return errorString;

  if (swizzleRequired) {
    oopPtr = buffer;
    oopLimitPtr = buffer + numOops;
    while (oopPtr < oopLimitPtr) {
      OopNumberType thisOne = (OopNumberType) swizzleInt(swizzleTransform, *oopPtr);
      *oopPtr = BIT_TO_OOP(thisOne); /* convert from bits to oops */
      ++oopPtr;
    }
  }
  else { /* just convert from bits to oops */
    oopPtr = buffer;
    oopLimitPtr = buffer + numOops;
    while (oopPtr < oopLimitPtr) {
      OopNumberType thisOne = *oopPtr;
      *oopPtr = BIT_TO_OOP(thisOne);
      ++oopPtr;
    }
  }
  return OOP_TRUE;
}

static OopType validateOopsInArray(OopType *oops,
				   long numOops,
				   OopType arrayToUse,
				   OopType serialNumOop,
				   long *numUnloadableOops,
				   long numUnloadableToPrint)
{
  OopType argArray[2], result;
  OopType tempArray[MAX_OOPS_PER_ARRAY];
  int i;

  GciSetVaryingSize(arrayToUse, 0); /* arrayToUse size: 0 */
  GciStoreOops(arrayToUse, 1, oops, numOops); /* arrayToUse addAll: oops */
  argArray[0] = arrayToUse;
  argArray[1] = serialNumOop;
  result = GciPerform(OOP_CLASS_SYSTEM, "_encodedIntToOopFiltered:serial:", argArray, 2);
  if (result == OOP_NIL)
    return handleError("Error calling System>>_encodedIntToOopFiltered: serial:");

  /* oops that we can't load have been replaced by nil in the array.  The primitive
   * to load the hidden set handles these so we don't need to filter them out here.
   * We'll just scan through them and complain about any that we can't load.
   */
  GciFetchOops(arrayToUse, 1, tempArray, numOops);
  for (i = 0; i < numOops; i++) {
    if (tempArray[i] == OOP_NIL) {
      if (*numUnloadableOops < numUnloadableToPrint) {
	if (*numUnloadableOops == 0)
	  printf("List of first %ld oops that could not be loaded:\n", numUnloadableToPrint);
	/* code to print out oops we couldn't load.  Could be a very long list! */
	printf("  %lu\n", GciEncodedLongToOop(GCI_OOP_TO_LONG(oops[i])) );
      }
      ++(*numUnloadableOops);
    }
  }
  return OOP_TRUE;
}

static OopType addOopsInArrayToHiddenSet(OopType arrayToUse)
{
  OopType result, argArray[2];

  argArray[0] = arrayToUse;
  argArray[1] = GCI_LONG_TO_OOP(GC_CANDIDATES_HIDDEN_SET);

  result = GciPerform(OOP_CLASS_SYSTEM, "_addAll:to:", argArray, 2);
  if (result == OOP_NIL)
    return handleError("Error adding oops to hidden set");

  return OOP_TRUE;
}

static OopType convertOopsToEncodedOops(OopType *input, long numOops)
{
  OopType *inputOop, *inputOopLimit;
  long encodedLong;

  inputOop = input;
  inputOopLimit = inputOop + numOops;

  while (inputOop < inputOopLimit) {
    if (GCI_OOP_IS_SPECIAL(*inputOop)) {
      sprintf(error, "Error: oop %lu is not a valid oop for GC", *inputOop);
      return handleError(error);
    }

    encodedLong = GciOopToEncodedLong(*inputOop);
    if (encodedLong == LONG_MAX) {
      sprintf(error, "Error: GciOopToEncodedLong() failed for oop %lu ", *inputOop);
      return handleError(error);
    }
    *inputOop++ = GCI_LONG_TO_OOP(encodedLong);
  }
  return OOP_TRUE;
}

/*=========================================================================
 *
 * Name - getGcLock
 *
 * Description -
 * 
 * Request the GC lock from the stone.  This can fail for various reasons
 * including:
 *
 * -lock is held by another process running a GC (epoch, MFC, etc)
 * -possible dead objects exist.  GC lock will not be granted until all
 *  possible dead objects are finalized.
 *
 * Returns - OOP_TRUE on success, a string describing the error on failure.
 *========================================================================
 */
static OopType getGcLock(OopType *sessionSerial)
{
  OopType result, mySerialOop;
  OopType arg = OOP_TRUE;

  *sessionSerial = OOP_NIL;
  result = GciPerform(OOP_CLASS_SYSTEM, "_lockGc:", &arg, 1);
  if (result != OOP_TRUE)
    return handleError("Unable to acquire GC lock");

  mySerialOop = GciExecuteStr("GsSession serialOfSession: System session", OOP_NIL);
  if (mySerialOop == OOP_NIL || GCI_OOP_TO_LONG(mySerialOop) < 1 )
    return handleError("Error determining session serial number");  

  *sessionSerial = mySerialOop;
  return OOP_TRUE;
}

static void releaseGcLock(void)
{
  OopType someOoop = OOP_FALSE;
  GciPerform(OOP_CLASS_SYSTEM, "_lockGc:", &someOoop, 1);
}

static OopType runMgc(void)
{
  OopType result;
  time_t startTime, endTime;

  result = seedWeakReferenceSet();
  if (result != OOP_TRUE) return result;

  /* remove any stray references from local object memory */
  if (OOP_NIL == GciPerform(OOP_CLASS_SYSTEM, "_generationScavenge", NULL, 0))
    return handleError("error: System _generationScavenge failed");

  /* remove any stray references from the not connected set */
  if (OOP_NIL == GciPerform(OOP_CLASS_SYSTEM, "_markNotConnectedForCollection", NULL, 0))
    return handleError("error: System _markNotConnectedForCollection failed");

  startTime = time(NULL);
  printf("Starting MGC at %s\n", printTime(startTime) );
  fflush(stdout);

  /* GcCandidates hidden set already loaded and weak refs are loaded, call prim directly */
  result = GciPerform(OOP_SYSTEM_REPOSITORY, "_markGcCandidates", NULL, 0);
  if (result == OOP_NIL)
    return handleError("error: SystemRepository _markGcCandidates failed");
  
  endTime = time(NULL);
  printf("Finished MGC at %s\n", printTime(endTime) );
  printf("  Elapsed MGC time=%ld seconds.\n", endTime - startTime);

  return result;
}

static BoolType getTransactionMode(OopType *modeSymbolOop)
{
  *modeSymbolOop = GciPerform(OOP_CLASS_SYSTEM, "transactionMode", NULL, 0);
  if (*modeSymbolOop == OOP_NIL) {
    *modeSymbolOop = handleError("error: SystemRepository transactionMode failed");
    return FALSE;
  }
  return TRUE;
}

static OopType setTransactionMode(OopType modeSymbolOop)
{
  OopType result, tmp;

  tmp = modeSymbolOop;
  result = GciPerform(OOP_CLASS_SYSTEM, "transactionMode:", &tmp, 1);
  if (result == OOP_NIL) {
    sprintf(error, "error: 'System transactionMode: @%lu' failed", tmp);
    return handleError(error);
  }
  return OOP_TRUE;
}

/*=========================================================================
 *
 * Name - uaRunFdcAndWriteOopsToFile
 *
 * Description -
 *
 * User action code to run the findDisconnectedObjects code and write the
 * resulting array of dead objects to a new binary file on disk with the given
 * file name. 
 * 
 * The file must not exist when this function is called.
 *
 * Unlike a regular FDC, this FDC will seed the weak references hidden set,
 * which causes objects with weak references to be detected as dead.  The
 * normal FDC call in smalltalk does not do this.
 *
 * The second argument is the size of the mark/sweep page buffer to use for
 * the FDC.  The default value used is 320 pages.  Larger sizes may improve
 * FDC times up to a point, but very large values may worsen performance.
 * Values over 4000 are usually not recommended.
 *
 * The output file size will be 8 + (numOops * 4) bytes in size.  The output
 * file is deleted if any error occurs.
 *
 * If more than about 536,870,910 dead objects are found, the oop file will
 * grow largerthan 2GB.  This code uses the correct functions to read and write
 * files this large, however some file systems must be explicitly enabled to
 * allow files larger than 2 GB.  Check with your UNIX system documentation
 * to see if this restriction exists on your system.  Also be sure there is 
 * enough free space on the target file system to accomodate the output file.
 *
 * Returns - on success, the array that would be returned by the
 *           findDisconnectedObjects method is returned.  The array is not
 *           committed to the the repository but the caller may do so if
 *           desired.
 * 
 *           If an error is encountered, an String describing the error
 *           is returned.
 *========================================================================
 */
OopType uaRunFdcAndWriteOopsToFile(OopType oopFileName, OopType oopPageBufSize)
{
  char fileName[2000];
  int openFlags, fd = -1;
  char someString[500];
  OopType resultArray, result, saveTransModeObj;
  long pageBufSize;
  BoolType success = FALSE;
  time_t startTime, endTime;

  for (;;) {
    if (GciFetchClass(oopFileName) != OOP_CLASS_STRING) {
      result = handleError("file name must be an instance of String");
      break;
    }

    if (GciFetchClass(oopPageBufSize) != OOP_CLASS_SMALL_INTEGER) {
      result = handleError("page buffer size must be an instance of SmallInteger");
      break;
    }

    if ( !GciFetchChars(oopFileName, 1, fileName, (ArraySizeType) sizeof(fileName) )) {
      result = handleError("file name was empty");
      break;
    }

    fd = open64(fileName,
		O_WRONLY | /* write only */
		O_APPEND | /* always write at the end */
		O_CREAT |  /* create the file */
		O_EXCL,    /* fail if the file already exists */
		S_IRUSR |  /* file permisions: -rw------- */
		S_IWUSR);

    if (fd == -1) {
      sprintf(error, "open64 on file %s failed with errno %d", fileName, errno);
      result = handleError(error);
      break;
    }

    /* set the page buffer size and commit */
    pageBufSize = GCI_OOP_TO_LONG(oopPageBufSize);
    if (pageBufSize < FDC_PAGE_BUFFER_MIN || pageBufSize > FDC_PAGE_BUFFER_MAX)
      pageBufSize = FDC_PAGE_BUFFER_DEFAULT;

    /* save current transaction mode for later */
    if (!getTransactionMode(&saveTransModeObj)) {
      result = saveTransModeObj;
      break;
    }

    /* go into manual mode for the FDC */
    result = setTransactionMode(OOP_SYM_MANUAL_BEGIN);
    if (result != OOP_TRUE) break;

    sprintf(someString, "UserGlobals at: #mfcGcPageBufSize put: %ld", pageBufSize);

    do {
      GciBegin();
      GciExecuteStr(someString, OOP_NIL);
    } while (GciCommit() == FALSE);

    /* seed the weak reference set */
    result = seedWeakReferenceSet();
    if (result != OOP_TRUE) break;

    /* now run the FDC */
    startTime = time(NULL);
    printf("Starting FDC at %s\n", printTime(startTime) );
    fflush(stdout);
    resultArray = GciPerform(OOP_SYSTEM_REPOSITORY, "findDisconnectedObjects", NULL, 0); 

    if (resultArray == OOP_NIL) {
      result = handleError("error: SystemRepository findDisconectedObjects returned nil");
      break;
    }
    else {
      endTime = time(NULL);
      printf("Finished FDC at %s\n", printTime(endTime) );
      printf("  Elapsed time=%ld seconds.\n", endTime - startTime);
      printf("  Number of dead objects found=%ld\n", GciFetchSize(resultArray) );
      fflush(stdout);
      /* go to auto mode. we don't want to miss any SigAborts while writing the file out */
      result = setTransactionMode(OOP_SYM_AUTO_BEGIN);
      if (result != OOP_TRUE) break;

      result = writeOopsToFile(fd, resultArray);
      success = result == resultArray;
      break;
    }
  }

  close(fd);

  /* remove the file on error */
  if (!success)
    remove(fileName);

  setTransactionMode(saveTransModeObj);
  return result;
}


/*=========================================================================
 *
 * Name - uaLoadOopsAndRunMgc
 *
 * Description -
 *
 * Give a full path name to a file, load the oops in the file into the  
 * appropriate hidden set and run a markGcCandidates operation.
 *
 * The user action takes 2 args.  The first is a string object containing
 * the full path of the file containing the oops to load.  The second is
 * a boolean object which indicates whether or not to force the MGC to
 * run if 1 or more oops in the file could not be validated.  Oops can
 * can fail validation for several reasons, including:
 *
 * -the object does not exist (oop not present in the object table)
 * -the object is a dead object which has not been reclaimed.
 * -the object is a small integer or special (should never happen).
 * -the objects are not in sorted order in the file (should never happen).
 *
 * If the second argument is not true, then the MGC is not run if 1 or 
 * more objects fail validation.  In this case an error string is returned
 * after attempting to load all objects.
 *
 * There is no danger in forcing the MGC if not all oops could be loaded.
 * However doing so increases the chances that not all objects in the
 * candidates will be found to be dead, which can cause the MGC to take
 * significantly more time to complete.
 *
 * If no objects have been garbage collected or reclaimed since the FDC
 * was run on the copy of this repository, all OOPs should load correctly.
 *
 * This code also prints messages to stdout, which goes to the log file
 * of this gem.  For topaz session, it goes to the "screen" of the topaz
 * session, which can be redirected to a log file.  For RPC gem sessions,
 * the output goes to the gem log file.  For linked Smalltalk image sessions,
 * the outupt disappears into the great void.
 *
 * Returns - on success, the array that would be returned by the
 *           markGcCandidates method is returned.  The array is not
 *           committed to the the repository but the caller may do so if
 *           desired.  See the comments in the markGcCandidates method for
 *           a description of the result array.
 * 
 *           If an error is encountered, an String describing the error
 *           is returned.
 *========================================================================
 */
OopType uaLoadOopsAndRunMgc(OopType oopFileName, OopType oopForceMgc)
{
  char fileName[2000];
  int openFlags, fd = -1;
  char someString[500];
  OopType serialNumOop, resultArray, result;
  long numOops, someLong, numOopsRead, numPd;
  BoolType swizzleRequired, success = FALSE;
  ByteType *swizzleTransform;
  OopType *oopBuffer, serial, oopArray;
  OopType hiddenSetOop, saveTransModeObj;
  OopType *oopPtr, *oopLimitPtr;
  long numThisTime, numUnloadableOops, numToValidate, numCandidates;
  time_t lastAbortTime, timeNow;
  AlignedBufferSType bufStruct;

  INIT_BUF_STRUCT(bufStruct);

  hiddenSetOop = GCI_LONG_TO_OOP(GC_CANDIDATES_HIDDEN_SET);

  if (GciFetchClass(oopFileName) != OOP_CLASS_STRING) {
    result = handleError("error: first argument is the file name and must be a String.");
    goto TILT;
  }

  if (oopForceMgc != OOP_TRUE && oopForceMgc != OOP_FALSE) {
    result = handleError("error: second argument must be true or false.");
    goto TILT;
  }

  if ( !GciFetchChars(oopFileName, 1, fileName, (ArraySizeType) sizeof(fileName) )) {
    result = handleError("file name was empty");
    goto TILT;
  }

  fd = open64(fileName, O_RDONLY /* read only */);

  if (fd == -1) {
    sprintf(error, "open64 on file %s failed with errno %d", fileName, errno);
    result = handleError(error);
    goto TILT;
  }

  swizzleTransform = (ByteType *)&someLong;
  result = checkFileIntegrity(fd, &swizzleRequired, &numOops, swizzleTransform);
  if (result != OOP_TRUE)
    goto TILT;

  bufStruct.requestedBufSize = (long) (OOP_BUFFER_SIZE * sizeof(OopType));

  result = getAlignedBuffer(&bufStruct); 
  if (result != OOP_TRUE) goto TILT;

  oopBuffer = (OopType *) bufStruct.alignedBuf;

  result = getGcLock(&serialNumOop);
  if (result != OOP_TRUE)
    goto TILT;

  oopArray = GciNewOop(OOP_CLASS_ARRAY);

  GciPerform(OOP_CLASS_SYSTEM, "_hiddenSetReinit:", &hiddenSetOop, 1);

  /* save current transaction mode for later */
  if (!getTransactionMode(&saveTransModeObj)) {
    result = saveTransModeObj;
    goto TILT;
  }

  /* set to auto begin so we don't miss SigAborts while loading the oops */
  setTransactionMode(OOP_SYM_AUTO_BEGIN);

  GciAbort();
  lastAbortTime = time(NULL);

  numOopsRead = 0;
  numUnloadableOops = 0;
  while (numOopsRead < numOops) {
    numThisTime = MIN(OOP_BUFFER_SIZE, numOops - numOopsRead);

    /* load buffer with oops, swizzle byte order if needed */
    result = loadAndSwizzleOops(fd, oopBuffer, numThisTime, swizzleRequired, swizzleTransform);
    if (result != OOP_TRUE) goto TILT;

    /* convert to encoded oops (SmallInts) */
    result = convertOopsToEncodedOops(oopBuffer, numThisTime);
    if (result != OOP_TRUE) goto TILT;

    oopPtr = oopBuffer;
    oopLimitPtr = oopPtr + numThisTime;
    while (oopPtr < oopLimitPtr) {
      numToValidate = MIN(oopLimitPtr - oopPtr, MAX_OOPS_PER_ARRAY);

      /* load oops into array instance and validate in chunks of 2024 to avoid creating large objects */
      result = validateOopsInArray(oopPtr,
				   numToValidate,
				   oopArray,
				   serialNumOop,
				   &numUnloadableOops,
				   100 /* num unloadable to print */
				   );
      if (result != OOP_TRUE) goto TILT;

      /* add to the hidden set.  nils in array are OK */
      result = addOopsInArrayToHiddenSet(oopArray);
      if (result != OOP_TRUE) goto TILT;

      oopPtr += numToValidate;
      timeNow = time(NULL);
      if (timeNow >= lastAbortTime + ABORT_INTERVAL) {
	GciAbort();
	lastAbortTime = timeNow;
      }
    } /* inner while */
    numOopsRead += numThisTime;
  } /* outer while */

  numCandidates = GciOopToLong(GciPerform(OOP_CLASS_SYSTEM, "_hiddenSetSize:", &hiddenSetOop, 1));
  printf("Num oops loaded in hidden set at start of MGC: %ld\n", numCandidates);

  if (numUnloadableOops)
    printf("Warning: %ld oops could not be loaded from the oop file.  Some objects may not be collected by MGC.\n", numUnloadableOops);
  else
    printf("Success: all oops loaded from file OK.\n");

  GciSetVaryingSize(oopArray, 0); /* oopArray size: 0 */
  GciReleaseOops(&oopArray, 1); /* remove from export set */
  success = TRUE;

TILT:;

  freeAlignedBuffer(&bufStruct);
  if (success) {
    if (oopForceMgc != OOP_TRUE && numUnloadableOops != 0) {
      sprintf(error, "%ld OOPs could not be loaded and forceMgc is false.  MGC will not be attempted.\n", numUnloadableOops);
      result = handleError(error);
    }
    else {
      /* go into manual mode for the MGC */
      result = setTransactionMode(OOP_SYM_MANUAL_BEGIN);
      /* keep the GC lock, don't leave a window for some other process to grab it before the MGC starts */
      result = runMgc();
      numPd = GciOopToLong(GciFetchOop(result, 1));
      if (numPd == numCandidates)
	printf("Congratulations, all candidate objects were confirmed dead by MGC\n");
      else {
	printf("Notice: one or more candidate objects could not be collected.\n");
	printf("  Number of possible dead candidates: %ld.\n  Number of Not Dead Candidates: %ld.\n",
	       numPd, numCandidates - numPd);
      }
      fflush(stdout);
    }
  }
  releaseGcLock(); /* release it if we have it */
  setTransactionMode(saveTransModeObj); /* restore to original mode */

  return result;
}


GCIUSER_ACTION_SHUTDOWN_DEF()
{ 
}

GCIUSER_ACTION_INIT_DEF()
{
  GCI_DECLARE_ACTION( "uaRunFdcAndWriteOopsToFile", uaRunFdcAndWriteOopsToFile, 2);
  GCI_DECLARE_ACTION( "uaLoadOopsAndRunMgc", uaLoadOopsAndRunMgc, 2);
}
