/*-
 ***********************************************************************
 *
 * $Id: pad-binary-guts.c,v 1.15 2012/01/07 07:56:14 mavrik Exp $
 *
 ***********************************************************************
 *
 * Copyright 2002-2012 The WebJob Project, All Rights Reserved.
 *
 ***********************************************************************
 */
#include "pad-common.h"
#include "pad-binary-guts.h"

/*-
 ***********************************************************************
 *
 * Global Variables.
 *
 ***********************************************************************
 */
void *gpvProperties;
PAD_CALL_TABLE gsCallTable[] =
{
  { PAD_VOID_CALL main, "PaDMain()" },
  { PAD_VOID_CALL PaDBootStrap, "PaDBootStrap()" },
  { PAD_VOID_CALL PaDBuildCmdLine, "PaDBuildCmdLine()" },
  { PAD_VOID_CALL PaDCalculateCmdLineLength, "PaDCalculateCmdLineLength()" },
  { PAD_VOID_CALL PaDCheckBasename, "PaDCheckBasename()" },
  { PAD_VOID_CALL PaDCheckSuffix, "PaDCheckSuffix()" },
  { PAD_VOID_CALL PaDDeliverPayload, "PaDDeliverPayload()" },
  { PAD_VOID_CALL PaDExtractPayload, "PaDExtractPayload()" },
  { PAD_VOID_CALL PaDGetBasename, "PaDGetBasename()" },
  { PAD_VOID_CALL PaDGetCallname, "PaDGetCallname()" },
  { PAD_VOID_CALL PaDGetEnvValue, "PaDGetEnvValue()" },
  { PAD_VOID_CALL PaDGetMyHandle, "PaDGetMyHandle()" },
  { PAD_VOID_CALL PaDGetPropertiesReference, "PaDGetPropertiesReference()" },
  { PAD_VOID_CALL PaDLocateDelimiter, "PaDLocateDelimiter()" },
  { PAD_VOID_CALL PaDNewDelimiter, "PaDNewDelimiter()" },
  { PAD_VOID_CALL PaDNewProperties, "PaDNewProperties()" },
  { PAD_VOID_CALL PaDProcessArguments, "PaDProcessArguments()" },
  { PAD_VOID_CALL PaDReadWrite, "PaDReadWrite()" },
  { PAD_VOID_CALL PaDSetPropertiesReference, "PaDSetPropertiesReference()" },
  { PAD_VOID_CALL PaDShutdown, "PaDShutdown()" }
};
int giNCalls = (sizeof(gsCallTable) / sizeof(gsCallTable[0]));

/*-
 ***********************************************************************
 *
 * PaDMain
 *
 ***********************************************************************
 */
int
main(int iArgumentCount, char *ppcArgumentVector[])
{
  void              (*pvCall)() = PAD_VOID_CALL main;
  char                acLocalError[MESSAGE_SIZE];
  int                 iError;
  int                 iIndex;
  int                 iNeedXBit;
  PAD_GUTS_PROPERTIES *psProperties;

  /*-
   *********************************************************************
   *
   * Punch in and go to work.
   *
   *********************************************************************
   */
  iError = PaDBootStrap(acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", PaDGetCallname(pvCall), acLocalError);
    PaDShutdown(XER_BootStrap);
  }
  psProperties = (PAD_GUTS_PROPERTIES *) PaDGetPropertiesReference();

  /*-
   *********************************************************************
   *
   * Process command line arguments.
   *
   *********************************************************************
   */
  iError = PaDProcessArguments(iArgumentCount, ppcArgumentVector, psProperties, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", PaDGetCallname(pvCall), acLocalError);
    PaDShutdown(XER_ProcessArguments);
  }

  /*-
   *********************************************************************
   *
   * Check suffix.
   *
   *********************************************************************
   */
  iIndex = PaDCheckSuffix(ppcArgumentVector[0], PAD_SUFFIX, acLocalError);
  if (iIndex == ER)
  {
    fprintf(stderr, "%s: %s\n", PaDGetCallname(pvCall), acLocalError);
    PaDShutdown(XER_Suffix);
  }
  psProperties->pcPayload[iIndex] = 0; /* Chop off suffix. */

  /*-
   *********************************************************************
   *
   * Check basename.
   *
   *********************************************************************
   */
  iError = PaDCheckBasename(psProperties->pcPayload, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", PaDGetCallname(pvCall), acLocalError);
    PaDShutdown(XER_Basename);
  }

  /*-
   *********************************************************************
   *
   * Locate delimiter, and seek past it.
   *
   *********************************************************************
   */
  iError = PaDLocateDelimiter(psProperties->pFilePaD, psProperties->pcDelimiter, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", PaDGetCallname(pvCall), acLocalError);
    PaDShutdown(XER_Delimiter);
  }

  /*-
   *********************************************************************
   *
   * Extract payload.
   *
   *********************************************************************
   */
  if (psProperties->iMute == PAD_MUTE_OFF)
  {
    fprintf(stderr, "Extracting payload...\n");
  }
  iError = PaDExtractPayload(psProperties, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", PaDGetCallname(pvCall), acLocalError);
    PaDShutdown(XER_Extract);
  }

  /*-
   *********************************************************************
   *
   * Conditionally, deliver payload.
   *
   *********************************************************************
   */
  if (iArgumentCount > 1)
  {
    psProperties->iUnlinkPayload = 1;
    iNeedXBit = PaDBuildCmdLine(iArgumentCount, ppcArgumentVector, psProperties->pcPayload, psProperties->pcCmdLine);
    if (psProperties->iMute == PAD_MUTE_OFF)
    {
      fprintf(stderr, "Delivering payload... %s\n", psProperties->pcCmdLine);
    }
    iError = PaDDeliverPayload(psProperties->pcPayload, iNeedXBit, psProperties->pcCmdLine, acLocalError);
    if (iError == ER)
    {
      fprintf(stderr, "%s: %s\n", PaDGetCallname(pvCall), acLocalError);
      PaDShutdown(XER_Deliver);
    }
    if (psProperties->iMute == PAD_MUTE_OFF)
    {
      fprintf(stderr, "DeliveryStatus='%d'\n", iError);
    }
  }

  /*-
   *********************************************************************
   *
   * Shutdown and go home.
   *
   *********************************************************************
   */
  PaDShutdown(XER_OK);

  return XER_OK;
}


/*-
 ***********************************************************************
 *
 * PaDBootStrap
 *
 ***********************************************************************
 */
int
PaDBootStrap(char *pcError)
{
  void              (*pvCall)() = PAD_VOID_CALL PaDBootStrap;
  char                acLocalError[MESSAGE_SIZE];
  char               *pcValue = NULL;
  PAD_GUTS_PROPERTIES *psProperties;
#ifdef UNIX
  int                 i = 0;
  int                 iLength = 0;
#endif

#ifdef WIN32
  /*-
   *********************************************************************
   *
   * Suppress critical-error-handler message boxes.
   *
   *********************************************************************
   */
  SetErrorMode(SEM_FAILCRITICALERRORS);
#endif

  /*-
   *********************************************************************
   *
   * Allocate and initialize the properties structure.
   *
   *********************************************************************
   */
  psProperties = (PAD_GUTS_PROPERTIES *) PaDNewProperties(sizeof(PAD_GUTS_PROPERTIES), acLocalError);
  if (psProperties == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", PaDGetCallname(pvCall), acLocalError);
    return ER;
  }
  PaDSetPropertiesReference((void *) psProperties);

  /*-
   *********************************************************************
   *
   * Allocate and initialize the delimiter.
   *
   *********************************************************************
   */
  psProperties->pcDelimiter = PaDNewDelimiter(PAD_GUTS_TEMPLATE, 1, acLocalError);
  if (psProperties->pcDelimiter == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", PaDGetCallname(pvCall), acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Pull in envrionment variables.
   *
   *********************************************************************
   */
  pcValue = PaDGetEnvValue("PAD_MUTE");
  psProperties->iMute = (pcValue == NULL) ? PAD_MUTE_OFF : (strcmp(pcValue, "0") == 0) ? PAD_MUTE_OFF : PAD_MUTE_ON;

  pcValue = PaDGetEnvValue("PAD_OVERWRITE");
  psProperties->iOverwritePayload = (pcValue == NULL) ? PAD_OVERWRITE_ON : (strcmp(pcValue, "0") == 0) ? PAD_OVERWRITE_OFF : PAD_OVERWRITE_ON;

#ifdef UNIX
  pcValue = PaDGetEnvValue("PAD_UMASK");
  if (pcValue != NULL && (iLength = strlen(pcValue)) <= 4)
  {
    for (i = 0; i < iLength; i++)
    {
      if (!isdigit((int)pcValue[i]) || pcValue[i] > '7') /* Accept only octal digits. */
      {
        break;
      }
    }
    if (i == iLength) /* We have 1-4 octal digits, so strtoul() should succeed. */
    {
      psProperties->ulUmask = strtoul(pcValue, NULL, 8);
    }
  }
  else
  {
    psProperties->ulUmask = PAD_UMASK;
  }
  umask((mode_t)psProperties->ulUmask);
#endif

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * PaDBuildCmdLine
 *
 ***********************************************************************
 */
int
PaDBuildCmdLine(int iArgumentCount, char *ppcArgumentVector[], char *pcPayload, char *pcCmdLine)
{
  char               *pc;
  int                 i;
  int                 j;
  int                 iNeedXBit;
  int                 iIndex1;
  int                 iIndex2;
  int                 iLength;

  pcCmdLine[0] = iNeedXBit = 0;

  /*-
   *********************************************************************
   *
   * Build a command line if there's enough arguments.
   *
   *********************************************************************
   */
  if (iArgumentCount > 1)
  {
    if (strncmp(ppcArgumentVector[1], PAD_PAYLOAD_TOKEN, strlen(PAD_PAYLOAD_TOKEN)) == 0)
    {
      iNeedXBit = 1;
    }
    for (i = iIndex1 = iIndex2 = 0; i < iArgumentCount - 1; i++, iIndex1 = 0)
    {
      if (i > 0)
      {
        pcCmdLine[iIndex2++] = ' ';
      }
      while ((pc = strstr(&ppcArgumentVector[i + 1][iIndex1], PAD_PAYLOAD_TOKEN)) != NULL)
      {
        while (iIndex1 < (int)(pc - &ppcArgumentVector[i + 1][0]))
        {
          pcCmdLine[iIndex2++] = ppcArgumentVector[i + 1][iIndex1++];
        }
        iIndex2 += sprintf(&pcCmdLine[iIndex2], "%s", pcPayload);
        iIndex1 += strlen(PAD_PAYLOAD_TOKEN);
      }
      iLength = strlen(&ppcArgumentVector[i + 1][iIndex1]);
      for (j = 0; j < iLength; j++)
      {
        pcCmdLine[iIndex2++] = ppcArgumentVector[i + 1][iIndex1++];
      }
      pcCmdLine[iIndex2] = 0;
    }
  }
  return iNeedXBit;
}


/*-
 ***********************************************************************
 *
 * PaDCalculateCmdLineLength
 *
 ***********************************************************************
 */
int
PaDCalculateCmdLineLength(int iArgumentCount, char *ppcArgumentVector[])
{
  char               *pc;
  int                 i;
  int                 iDelta;
  int                 iIndex;
  int                 iLength;
  int                 iMatches;

  iDelta = strlen(ppcArgumentVector[0]) - strlen(PAD_PAYLOAD_TOKEN); /* This can be negative. */
  for (i = iIndex = iLength = iMatches = 0; i < iArgumentCount - 1; i++, iIndex = iMatches = 0)
  {
    while ((pc = strstr(&ppcArgumentVector[i + 1][iIndex], PAD_PAYLOAD_TOKEN)) != NULL)
    {
      iMatches++;
      iIndex = (pc - &ppcArgumentVector[i + 1][0]) + strlen(PAD_PAYLOAD_TOKEN);
    }
    iLength += ((i > 0) ? 1 : 0) + strlen(ppcArgumentVector[i + 1]) + (iDelta * iMatches);
  }
  return iLength;
}


/*-
 ***********************************************************************
 *
 * PaDCheckBasename
 *
 ***********************************************************************
 */
int
PaDCheckBasename(char *pcPath, char *pcError)
{
  void              (*pvCall)() = PAD_VOID_CALL PaDCheckBasename;
  char               *pcBasename;

  /*-
   *********************************************************************
   *
   * Get basename, and make sure it's not NULL, DOT, or DOTDOT.
   *
   *********************************************************************
   */
  pcBasename = PaDGetBasename(pcPath);
  if (pcBasename[0] == 0 || strcmp(pcBasename, DOT) == 0 || strcmp(pcBasename, DOTDOT) == 0)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Error='Invalid or missing basename.'", PaDGetCallname(pvCall));
    return ER;
  }
  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * PaDCheckSuffix
 *
 ***********************************************************************
 */
int
PaDCheckSuffix(char *pcName, char *pcSuffix, char *pcError)
{
  void              (*pvCall)() = PAD_VOID_CALL PaDCheckSuffix;
  int                 iIndex;

  /*-
   *********************************************************************
   *
   * Name must be at least as long as and equal to the suffix.
   *
   *********************************************************************
   */
  iIndex = strlen(pcName) - strlen(pcSuffix);
  if (iIndex < 0 || strcmp(&pcName[iIndex], pcSuffix) != 0)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Error='Invalid or missing suffix.'", PaDGetCallname(pvCall));
    return ER;
  }
  return iIndex;
}


/*-
 ***********************************************************************
 *
 * PaDDeliverPayload
 *
 ***********************************************************************
 */
int
PaDDeliverPayload(char *pcPayload, int iNeedXBit, char *pcCmdLine, char *pcError)
{
  void              (*pvCall)() = PAD_VOID_CALL PaDDeliverPayload;
  int                 iError;
#ifdef UNIX
  struct stat         statEntry;

  if (iNeedXBit)
  {
    if (stat(pcPayload, &statEntry) == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: stat(): File='%s' Error='%s'", PaDGetCallname(pvCall), pcPayload, strerror(errno));
      return ER;
    }
    if ((statEntry.st_mode & S_IXUSR) != S_IXUSR)
    {
      statEntry.st_mode |= S_IXUSR;
      if (chmod(pcPayload, statEntry.st_mode) == -1)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: chmod(): File='%s' Error='%s'", PaDGetCallname(pvCall), pcPayload, strerror(errno));
        return ER;
      }
    }
  }
#endif
  iError = system(pcCmdLine);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: system(): Command='%s' Error='%s'", PaDGetCallname(pvCall), pcCmdLine, strerror(errno));
    return ER;
  }
  return iError;
}


/*-
 ***********************************************************************
 *
 * PaDExtractPayload
 *
 ***********************************************************************
 */
int
PaDExtractPayload(PAD_GUTS_PROPERTIES *psProperties, char *pcError)
{
  void              (*pvCall)() = PAD_VOID_CALL PaDExtractPayload;
  char                acLocalError[MESSAGE_SIZE];
  FILE               *pFileFrom;
  FILE               *pFileTo;
  int                 iError;
  struct stat         statEntry;

  if (stat(psProperties->pcPayload, &statEntry) == 0 && psProperties->iOverwritePayload == 0)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: stat(): File='%s' Error='File exists and PAD_OVERWRITE is disabled. Extraction aborted.'", PaDGetCallname(pvCall), psProperties->pcPayload);
    return ER;
  }
  pFileFrom = psProperties->pFilePaD;
  pFileTo = fopen(psProperties->pcPayload, "wb");
  if (pFileTo == NULL)
  {
    psProperties->iUnlinkPayload = 1;
    snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File='%s' Error='%s'", PaDGetCallname(pvCall), psProperties->pcPayload, strerror(errno));
    return ER;
  }
  iError = PaDReadWrite(pFileFrom, pFileTo, acLocalError);
  if (iError != ER_OK)
  {
    psProperties->iUnlinkPayload = 1;
    snprintf(pcError, MESSAGE_SIZE, "%s: %s:", PaDGetCallname(pvCall), acLocalError);
    fclose(pFileTo);
    return ER;
  }
  fclose(pFileTo);
  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * PaDProcessArguments
 *
 ***********************************************************************
 */
int
PaDProcessArguments(int iArgumentCount, char *ppcArgumentVector[], PAD_GUTS_PROPERTIES *psProperties, char *pcError)
{
  void              (*pvCall)() = PAD_VOID_CALL PaDProcessArguments;
  char                acLocalError[MESSAGE_SIZE];
  int                 iLength;

  /*-
   *********************************************************************
   *
   * Open a read handle for this program.
   *
   *********************************************************************
   */
  psProperties->pFilePaD = PaDGetMyHandle(ppcArgumentVector[0], acLocalError);
  if (psProperties->pFilePaD == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", PaDGetCallname(pvCall), acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Allocate and initialize the payload filename.
   *
   *********************************************************************
   */
  iLength = strlen(ppcArgumentVector[0]) + 1;
  psProperties->pcPayload = calloc(iLength, 1);
  if (psProperties->pcPayload == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): Error='%s'", PaDGetCallname(pvCall), strerror(errno));
    return ER;
  }
  strcpy(psProperties->pcPayload, ppcArgumentVector[0]);

  /*-
   *********************************************************************
   *
   * Allocate and initialize the delivery command.
   *
   *********************************************************************
   */
  if (iArgumentCount > 1)
  {
    iLength = PaDCalculateCmdLineLength(iArgumentCount, ppcArgumentVector) + 1;
  }
  else
  {
    iLength = 1;
  }
  psProperties->pcCmdLine = calloc(iLength, 1);
  if (psProperties->pcCmdLine == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): Error='%s'", PaDGetCallname(pvCall), strerror(errno));
    return ER;
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * PaDShutdown
 *
 ***********************************************************************
 */
void
PaDShutdown(int iError)
{
  PAD_GUTS_PROPERTIES *psProperties;

  psProperties = (PAD_GUTS_PROPERTIES *) PaDGetPropertiesReference();
  if (psProperties != NULL)
  {
    if (psProperties->pFilePaD != NULL)
    {
      fclose(psProperties->pFilePaD);
    }
    if (psProperties->pcDelimiter != NULL)
    {
      free(psProperties->pcDelimiter);
    }
    if (psProperties->pcPayload != NULL)
    {
      if (psProperties->iUnlinkPayload)
      {
        unlink(psProperties->pcPayload);
      }
      free(psProperties->pcPayload);
    }
    if (psProperties->pcCmdLine != NULL)
    {
      free(psProperties->pcCmdLine);
    }
    free(psProperties);
  }
  exit(iError);
}
