/** @file
This file contains functions required to generate a Firmware File System file.

Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "WinNtInclude.h"

#ifndef __GNUC__
#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif

#ifdef __GNUC__
#include <unistd.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <Common/UefiBaseTypes.h>
#include <Common/PiFirmwareFile.h>
#include <Guid/FfsSectionAlignmentPadding.h>

#include "CommonLib.h"
#include "ParseInf.h"
#include "EfiUtilityMsgs.h"
#include "FvLib.h"

#define UTILITY_NAME            "GenFfs"
#define UTILITY_MAJOR_VERSION   0
#define UTILITY_MINOR_VERSION   1

STATIC CHAR8 *mFfsFileType[] = {
  NULL,                                   // 0x00
  "EFI_FV_FILETYPE_RAW",                  // 0x01
  "EFI_FV_FILETYPE_FREEFORM",             // 0x02
  "EFI_FV_FILETYPE_SECURITY_CORE",        // 0x03
  "EFI_FV_FILETYPE_PEI_CORE",             // 0x04
  "EFI_FV_FILETYPE_DXE_CORE",             // 0x05
  "EFI_FV_FILETYPE_PEIM",                 // 0x06
  "EFI_FV_FILETYPE_DRIVER",               // 0x07
  "EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER", // 0x08
  "EFI_FV_FILETYPE_APPLICATION",          // 0x09
  "EFI_FV_FILETYPE_SMM",                  // 0x0A
  "EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE",// 0x0B
  "EFI_FV_FILETYPE_COMBINED_SMM_DXE",     // 0x0C
  "EFI_FV_FILETYPE_SMM_CORE",             // 0x0D
  "EFI_FV_FILETYPE_MM_STANDALONE",        // 0x0E
  "EFI_FV_FILETYPE_MM_CORE_STANDALONE"    // 0x0F
};

STATIC CHAR8 *mAlignName[] = {
  "1", "2", "4", "8", "16", "32", "64", "128", "256", "512",
  "1K", "2K", "4K", "8K", "16K", "32K", "64K", "128K", "256K",
  "512K", "1M", "2M", "4M", "8M", "16M"
 };

STATIC CHAR8 *mFfsValidAlignName[] = {
  "8", "16", "128", "512", "1K", "4K", "32K", "64K", "128K","256K",
  "512K", "1M", "2M", "4M", "8M", "16M"
 };

STATIC UINT32 mFfsValidAlign[] = {0, 8, 16, 128, 512, 1024, 4096, 32768, 65536, 131072, 262144,
                                  524288, 1048576, 2097152, 4194304, 8388608, 16777216};

STATIC EFI_GUID mZeroGuid = {0};

STATIC EFI_GUID mEfiFfsSectionAlignmentPaddingGuid = EFI_FFS_SECTION_ALIGNMENT_PADDING_GUID;

STATIC
VOID
Version (
  VOID
  )
/*++

Routine Description:

  Print out version information for this utility.

Arguments:

  None

Returns:

  None

--*/
{
  fprintf (stdout, "%s Version %d.%d %s \n", UTILITY_NAME, UTILITY_MAJOR_VERSION, UTILITY_MINOR_VERSION, __BUILD_VERSION);
}

STATIC
VOID
Usage (
  VOID
  )
/*++

Routine Description:

  Print Error / Help message.

Arguments:

  VOID

Returns:

  None

--*/
{
  //
  // Summary usage
  //
  fprintf (stdout, "\nUsage: %s [options]\n\n", UTILITY_NAME);

  //
  // Copyright declaration
  //
  fprintf (stdout, "Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.\n\n");

  //
  // Details Option
  //
  fprintf (stdout, "Options:\n");
  fprintf (stdout, "  -o FileName, --outputfile FileName\n\
                        File is FFS file to be created.\n");
  fprintf (stdout, "  -t Type, --filetype Type\n\
                        Type is one FV file type defined in PI spec, which is\n\
                        EFI_FV_FILETYPE_RAW, EFI_FV_FILETYPE_FREEFORM,\n\
                        EFI_FV_FILETYPE_SECURITY_CORE, EFI_FV_FILETYPE_PEIM,\n\
                        EFI_FV_FILETYPE_PEI_CORE, EFI_FV_FILETYPE_DXE_CORE,\n\
                        EFI_FV_FILETYPE_DRIVER, EFI_FV_FILETYPE_APPLICATION,\n\
                        EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER,\n\
                        EFI_FV_FILETYPE_SMM, EFI_FV_FILETYPE_SMM_CORE,\n\
                        EFI_FV_FILETYPE_MM_STANDALONE,\n\
                        EFI_FV_FILETYPE_MM_CORE_STANDALONE,\n\
                        EFI_FV_FILETYPE_COMBINED_SMM_DXE, \n\
                        EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE.\n");
  fprintf (stdout, "  -g FileGuid, --fileguid FileGuid\n\
                        FileGuid is one module guid.\n\
                        Its format is xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n");
  fprintf (stdout, "  -x, --fixed           Indicates that the file may not be moved\n\
                        from its present location.\n");
  fprintf (stdout, "  -s, --checksum        Indicates to calculate file checksum.\n");
  fprintf (stdout, "  -a FileAlign, --align FileAlign\n\
                        FileAlign points to file alignment, which only support\n\
                        the following align: 1,2,4,8,16,128,512,1K,4K,32K,64K\n\
                        128K,256K,512K,1M,2M,4M,8M,16M\n");
  fprintf (stdout, "  -i SectionFile, --sectionfile SectionFile\n\
                        Section file will be contained in this FFS file.\n");
  fprintf (stdout, "  -oi SectionFile, --optionalsectionfile SectionFile\n\
                        If the Section file exists, it will be contained in this FFS file, otherwise, it will be ignored.\n");
  fprintf (stdout, "  -n SectionAlign, --sectionalign SectionAlign\n\
                        SectionAlign points to section alignment, which support\n\
                        the alignment scope 0~16M. If SectionAlign is specified\n\
                        as 0, tool get alignment value from SectionFile. It is\n\
                        specified together with sectionfile to point its\n\
                        alignment in FFS file.\n");
  fprintf (stdout, "  -v, --verbose         Turn on verbose output with informational messages.\n");
  fprintf (stdout, "  -q, --quiet           Disable all messages except key message and fatal error\n");
  fprintf (stdout, "  -d, --debug level     Enable debug messages, at input debug level.\n");
  fprintf (stdout, "  --version             Show program's version number and exit.\n");
  fprintf (stdout, "  -h, --help            Show this help message and exit.\n");
}

STATIC
EFI_STATUS
StringtoAlignment (
  IN  CHAR8  *AlignBuffer,
  OUT UINT32 *AlignNumber
  )
/*++

Routine Description:

  Converts Align String to align value (1~16M).

Arguments:

  AlignBuffer    - Pointer to Align string.
  AlignNumber    - Pointer to Align value.

Returns:

  EFI_SUCCESS             Successfully convert align string to align value.
  EFI_INVALID_PARAMETER   Align string is invalid or align value is not in scope.

--*/
{
  UINT32 Index = 0;
  //
  // Check AlignBuffer
  //
  if (AlignBuffer == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  for (Index = 0; Index < sizeof (mAlignName) / sizeof (CHAR8 *); Index ++) {
    if (stricmp (AlignBuffer, mAlignName [Index]) == 0) {
      *AlignNumber = 1 << Index;
      return EFI_SUCCESS;
    }
  }
  return EFI_INVALID_PARAMETER;
}

STATIC
UINT8
StringToType (
  IN CHAR8 *String
  )
/*++

Routine Description:

  Converts File Type String to value.  EFI_FV_FILETYPE_ALL indicates that an
  unrecognized file type was specified.

Arguments:

  String    - File type string

Returns:

  File Type Value

--*/
{
  UINT8 Index = 0;

  if (String == NULL) {
    return EFI_FV_FILETYPE_ALL;
  }

  for (Index = 0; Index < sizeof (mFfsFileType) / sizeof (CHAR8 *); Index ++) {
    if (mFfsFileType [Index] != NULL && (stricmp (String, mFfsFileType [Index]) == 0)) {
      return Index;
    }
  }
  return EFI_FV_FILETYPE_ALL;
}

STATIC
EFI_STATUS
GetSectionContents (
  IN  CHAR8                     **InputFileName,
  IN  UINT32                    *InputFileAlign,
  IN  UINT32                    InputFileNum,
  IN  EFI_FFS_FILE_ATTRIBUTES   FfsAttrib,
  OUT UINT8                     *FileBuffer,
  OUT UINT32                    *BufferLength,
  OUT UINT32                    *MaxAlignment,
  OUT UINT8                     *PESectionNum
  )
/*++

Routine Description:

  Get the contents of all section files specified in InputFileName
  into FileBuffer.

Arguments:

  InputFileName  - Name of the input file.

  InputFileAlign - Alignment required by the input file data.

  InputFileNum   - Number of input files. Should be at least 1.

  FileBuffer     - Output buffer to contain data

  BufferLength   - On input, this is size of the FileBuffer.
                   On output, this is the actual length of the data.

  MaxAlignment   - The max alignment required by all the input file datas.

  PeSectionNum   - Calculate the number of Pe Section in this FFS file.

Returns:

  EFI_SUCCESS on successful return
  EFI_INVALID_PARAMETER if InputFileNum is less than 1 or BufferLength point is NULL.
  EFI_ABORTED if unable to open input file.
  EFI_BUFFER_TOO_SMALL FileBuffer is not enough to contain all file data.
--*/
{
  UINT32                              Size;
  UINT32                              Offset;
  UINT32                              FileSize;
  UINT32                              Index;
  FILE                                *InFile;
  EFI_FREEFORM_SUBTYPE_GUID_SECTION   *SectHeader;
  EFI_COMMON_SECTION_HEADER2          TempSectHeader;
  EFI_GUID_DEFINED_SECTION            GuidSectHeader;
  EFI_GUID_DEFINED_SECTION2           GuidSectHeader2;
  UINT32                              HeaderSize;
  UINT32                              MaxEncounteredAlignment;

  Size                    = 0;
  Offset                  = 0;
  MaxEncounteredAlignment = 1;

  //
  // Go through our array of file names and copy their contents
  // to the output buffer.
  //
  for (Index = 0; Index < InputFileNum; Index++) {
    //
    // make sure section ends on a DWORD boundary
    //
    while ((Size & 0x03) != 0) {
      Size++;
    }

    //
    // Open file and read contents
    //
    InFile = fopen (LongFilePath (InputFileName[Index]), "rb");
    if (InFile == NULL) {
      Error (NULL, 0, 0001, "Error opening file", InputFileName[Index]);
      return EFI_ABORTED;
    }

    fseek (InFile, 0, SEEK_END);
    FileSize = ftell (InFile);
    fseek (InFile, 0, SEEK_SET);
    DebugMsg (NULL, 0, 9, "Input section files",
              "the input section name is %s and the size is %u bytes", InputFileName[Index], (unsigned) FileSize);

    //
    // Check this section is Pe section, and Calculate the numbers of Pe section.
    //
    if (FileSize >= MAX_FFS_SIZE) {
      HeaderSize = sizeof (EFI_COMMON_SECTION_HEADER2);
    } else {
      HeaderSize = sizeof (EFI_COMMON_SECTION_HEADER);
    }
    fread (&TempSectHeader, 1, HeaderSize, InFile);
    if (TempSectHeader.Type == EFI_SECTION_PE32) {
      (*PESectionNum) ++;
    } else if (TempSectHeader.Type == EFI_SECTION_GUID_DEFINED) {
      fseek (InFile, 0, SEEK_SET);
      if (FileSize >= MAX_SECTION_SIZE) {
        fread (&GuidSectHeader2, 1, sizeof (GuidSectHeader2), InFile);
        if ((GuidSectHeader2.Attributes & EFI_GUIDED_SECTION_PROCESSING_REQUIRED) == 0) {
          HeaderSize = GuidSectHeader2.DataOffset;
        }
      } else {
        fread (&GuidSectHeader, 1, sizeof (GuidSectHeader), InFile);
        if ((GuidSectHeader.Attributes & EFI_GUIDED_SECTION_PROCESSING_REQUIRED) == 0) {
          HeaderSize = GuidSectHeader.DataOffset;
        }
      }
      (*PESectionNum) ++;
    } else if (TempSectHeader.Type == EFI_SECTION_COMPRESSION ||
               TempSectHeader.Type == EFI_SECTION_FIRMWARE_VOLUME_IMAGE) {
      //
      // for the encapsulated section, assume it contains Pe section
      //
      (*PESectionNum) ++;
    }

    fseek (InFile, 0, SEEK_SET);

    //
    // make sure section data meet its alignment requirement by adding one pad section.
    // But the different sections have the different section header. Necessary or not?
    // Based on section type to adjust offset? Todo
    //
    if ((InputFileAlign [Index] != 0) && (((Size + HeaderSize) % InputFileAlign [Index]) != 0)) {
      Offset = (Size + sizeof (EFI_COMMON_SECTION_HEADER) + HeaderSize + InputFileAlign [Index] - 1) & ~(InputFileAlign [Index] - 1);
      Offset = Offset - Size - HeaderSize;

      if (FileBuffer != NULL && ((Size + Offset) < *BufferLength)) {
        //
        // The maximal alignment is 64K, the raw section size must be less than 0xffffff
        //
        memset (FileBuffer + Size, 0, Offset);
        SectHeader                        = (EFI_FREEFORM_SUBTYPE_GUID_SECTION *) (FileBuffer + Size);
        SectHeader->CommonHeader.Size[0]  = (UINT8) (Offset & 0xff);
        SectHeader->CommonHeader.Size[1]  = (UINT8) ((Offset & 0xff00) >> 8);
        SectHeader->CommonHeader.Size[2]  = (UINT8) ((Offset & 0xff0000) >> 16);

        //
        // Only add a special reducible padding section if
        // - this FFS has the FFS_ATTRIB_FIXED attribute,
        // - none of the preceding sections have alignment requirements,
        // - the size of the padding is sufficient for the
        //   EFI_SECTION_FREEFORM_SUBTYPE_GUID header.
        //
        if ((FfsAttrib & FFS_ATTRIB_FIXED) != 0 &&
            MaxEncounteredAlignment <= 1 &&
            Offset >= sizeof (EFI_FREEFORM_SUBTYPE_GUID_SECTION)) {
          SectHeader->CommonHeader.Type   = EFI_SECTION_FREEFORM_SUBTYPE_GUID;
          SectHeader->SubTypeGuid         = mEfiFfsSectionAlignmentPaddingGuid;
        } else {
          SectHeader->CommonHeader.Type   = EFI_SECTION_RAW;
        }
      }
      DebugMsg (NULL, 0, 9, "Pad raw section for section data alignment",
                "Pad Raw section size is %u", (unsigned) Offset);

      Size = Size + Offset;
    }

    //
    // Get the Max alignment of all input file datas
    //
    if (MaxEncounteredAlignment < InputFileAlign [Index]) {
      MaxEncounteredAlignment = InputFileAlign [Index];
    }

    //
    // Now read the contents of the file into the buffer
    // Buffer must be enough to contain the file content.
    //
    if ((FileSize > 0) && (FileBuffer != NULL) && ((Size + FileSize) <= *BufferLength)) {
      if (fread (FileBuffer + Size, (size_t) FileSize, 1, InFile) != 1) {
        Error (NULL, 0, 0004, "Error reading file", InputFileName[Index]);
        fclose (InFile);
        return EFI_ABORTED;
      }
    }

    fclose (InFile);
    Size += FileSize;
  }

  *MaxAlignment = MaxEncounteredAlignment;

  //
  // Set the actual length of the data.
  //
  if (Size > *BufferLength) {
    *BufferLength = Size;
    return EFI_BUFFER_TOO_SMALL;
  } else {
    *BufferLength = Size;
    return EFI_SUCCESS;
  }
}

int
main (
  int   argc,
  CHAR8 *argv[]
  )
/*++

Routine Description:

  Main function.

Arguments:

  argc - Number of command line parameters.
  argv - Array of pointers to parameter strings.

Returns:
  STATUS_SUCCESS - Utility exits successfully.
  STATUS_ERROR   - Some error occurred during execution.

--*/
{
  EFI_STATUS              Status;
  EFI_FFS_FILE_ATTRIBUTES FfsAttrib;
  UINT32                  FfsAlign;
  EFI_FV_FILETYPE         FfsFiletype;
  CHAR8                   *OutputFileName;
  EFI_GUID                FileGuid = {0};
  UINT32                  InputFileNum;
  UINT32                  *InputFileAlign;
  CHAR8                   **InputFileName;
  UINT8                   *FileBuffer;
  UINT32                  FileSize;
  UINT32                  MaxAlignment;
  EFI_FFS_FILE_HEADER2    FfsFileHeader;
  FILE                    *FfsFile;
  UINT32                  Index;
  UINT64                  LogLevel;
  UINT8                   PeSectionNum;
  UINT32                  HeaderSize;
  UINT32                  Alignment;
  //
  // Workaround for static code checkers.
  // Ensures the size of 'AlignmentBuffer' can hold all the digits of an
  // unsigned 32-bit integer plus the size unit character.
  //
  CHAR8                   AlignmentBuffer[16];

  //
  // Init local variables
  //
  LogLevel       = 0;
  Index          = 0;
  FfsAttrib      = 0;
  FfsAlign       = 0;
  FfsFiletype    = EFI_FV_FILETYPE_ALL;
  OutputFileName = NULL;
  InputFileNum   = 0;
  InputFileName  = NULL;
  InputFileAlign = NULL;
  FileBuffer     = NULL;
  FileSize       = 0;
  MaxAlignment   = 1;
  FfsFile        = NULL;
  Status         = EFI_SUCCESS;
  PeSectionNum   = 0;

  SetUtilityName (UTILITY_NAME);

  if (argc == 1) {
    Error (NULL, 0, 1001, "Missing options", "no options input");
    Usage ();
    return STATUS_ERROR;
  }

  //
  // Parse command line
  //
  argc --;
  argv ++;

  if ((stricmp (argv[0], "-h") == 0) || (stricmp (argv[0], "--help") == 0)) {
    Version ();
    Usage ();
    return STATUS_SUCCESS;
  }

  if (stricmp (argv[0], "--version") == 0) {
    Version ();
    return STATUS_SUCCESS;
  }

  while (argc > 0) {
    if ((stricmp (argv[0], "-t") == 0) || (stricmp (argv[0], "--filetype") == 0)) {
      if (argv[1] == NULL || argv[1][0] == '-') {
        Error (NULL, 0, 1003, "Invalid option value", "file type is missing for -t option");
        goto Finish;
      }
      FfsFiletype = StringToType (argv[1]);
      if (FfsFiletype == EFI_FV_FILETYPE_ALL) {
        Error (NULL, 0, 1003, "Invalid option value", "%s is not a valid file type", argv[1]);
        goto Finish;
      }
      argc -= 2;
      argv += 2;
      continue;
    }

    if ((stricmp (argv[0], "-o") == 0) || (stricmp (argv[0], "--outputfile") == 0)) {
      if (argv[1] == NULL || argv[1][0] == '-') {
        Error (NULL, 0, 1003, "Invalid option value", "Output file is missing for -o option");
        goto Finish;
      }
      OutputFileName = argv[1];
      argc -= 2;
      argv += 2;
      continue;
    }

    if ((stricmp (argv[0], "-g") == 0) || (stricmp (argv[0], "--fileguid") == 0)) {
      Status = StringToGuid (argv[1], &FileGuid);
      if (EFI_ERROR (Status)) {
        Error (NULL, 0, 1003, "Invalid option value", "%s = %s", argv[0], argv[1]);
        goto Finish;
      }
      argc -= 2;
      argv += 2;
      continue;
    }

    if ((stricmp (argv[0], "-x") == 0) || (stricmp (argv[0], "--fixed") == 0)) {
      FfsAttrib |= FFS_ATTRIB_FIXED;
      argc -= 1;
      argv += 1;
      continue;
    }

    if ((stricmp (argv[0], "-s") == 0) || (stricmp (argv[0], "--checksum") == 0)) {
      FfsAttrib |= FFS_ATTRIB_CHECKSUM;
      argc -= 1;
      argv += 1;
      continue;
    }

    if ((stricmp (argv[0], "-a") == 0) || (stricmp (argv[0], "--align") == 0)) {
      if (argv[1] == NULL || argv[1][0] == '-') {
        Error (NULL, 0, 1003, "Invalid option value", "Align value is missing for -a option");
        goto Finish;
      }
      for (Index = 0; Index < sizeof (mFfsValidAlignName) / sizeof (CHAR8 *); Index ++) {
        if (stricmp (argv[1], mFfsValidAlignName[Index]) == 0) {
          break;
        }
      }
      if (Index == sizeof (mFfsValidAlignName) / sizeof (CHAR8 *)) {
        if ((stricmp (argv[1], "1") == 0) || (stricmp (argv[1], "2") == 0) || (stricmp (argv[1], "4") == 0)) {
          //
          // 1, 2, 4 byte alignment same to 8 byte alignment
          //
          Index = 0;
        } else {
          Error (NULL, 0, 1003, "Invalid option value", "%s = %s", argv[0], argv[1]);
          goto Finish;
        }
      }
      FfsAlign = Index;
      argc -= 2;
      argv += 2;
      continue;
    }

    if ((stricmp (argv[0], "-oi") == 0) || (stricmp (argv[0], "--optionalsectionfile") == 0) || (stricmp (argv[0], "-i") == 0) || (stricmp (argv[0], "--sectionfile") == 0)) {
      //
      // Get Input file name and its alignment
      //
      if (argv[1] == NULL || argv[1][0] == '-') {
        Error (NULL, 0, 1003, "Invalid option value", "input section file is missing for -i option");
        goto Finish;
      }
      if ((stricmp (argv[0], "-oi") == 0) || (stricmp (argv[0], "--optionalsectionfile") == 0) ){
        if (-1 == access(argv[1] , 0)){
          Warning(NULL, 0, 0001, "File is not found.", argv[1]);
          argc -= 2;
          argv += 2;
          continue;
        }
      }
      //
      // Allocate Input file name buffer and its alignment buffer.
      //
      if ((InputFileNum == 0) && (InputFileName == NULL)) {
        InputFileName = (CHAR8 **) malloc (MAXIMUM_INPUT_FILE_NUM * sizeof (CHAR8 *));
        if (InputFileName == NULL) {
          Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!");
          return STATUS_ERROR;
        }
        memset (InputFileName, 0, (MAXIMUM_INPUT_FILE_NUM * sizeof (CHAR8 *)));

        InputFileAlign = (UINT32 *) malloc (MAXIMUM_INPUT_FILE_NUM * sizeof (UINT32));
        if (InputFileAlign == NULL) {
          Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!");
          free (InputFileName);
          return STATUS_ERROR;
        }
        memset (InputFileAlign, 0, MAXIMUM_INPUT_FILE_NUM * sizeof (UINT32));
      } else if (InputFileNum % MAXIMUM_INPUT_FILE_NUM == 0) {
        //
        // InputFileName and alignment buffer too small, need to realloc
        //
        InputFileName = (CHAR8 **) realloc (
                                    InputFileName,
                                    (InputFileNum + MAXIMUM_INPUT_FILE_NUM) * sizeof (CHAR8 *)
                                    );

        if (InputFileName == NULL) {
          Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!");
          free (InputFileAlign);
          return STATUS_ERROR;
        }
        memset (&(InputFileName[InputFileNum]), 0, (MAXIMUM_INPUT_FILE_NUM * sizeof (CHAR8 *)));

        InputFileAlign = (UINT32 *) realloc (
                                    InputFileAlign,
                                    (InputFileNum + MAXIMUM_INPUT_FILE_NUM) * sizeof (UINT32)
                                    );

        if (InputFileAlign == NULL) {
          Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!");
          free (InputFileName);
          return STATUS_ERROR;
        }
        memset (&(InputFileAlign[InputFileNum]), 0, (MAXIMUM_INPUT_FILE_NUM * sizeof (UINT32)));
      }

      InputFileName[InputFileNum] = argv[1];
      argc -= 2;
      argv += 2;

      if (argc <= 0) {
        InputFileNum ++;
        break;
      }

      //
      // Section File alignment requirement
      //
      if ((stricmp (argv[0], "-n") == 0) || (stricmp (argv[0], "--sectionalign") == 0)) {
        if ((argv[1] != NULL) && (stricmp("0", argv[1]) == 0)) {
          Status = GetAlignmentFromFile(InputFileName[InputFileNum], &Alignment);
          if (EFI_ERROR(Status)) {
            Error (NULL, 0, 1003, "Fail to get Alignment from %s", InputFileName[InputFileNum]);
            goto Finish;
          }
          if (Alignment < 0x400){
            sprintf (AlignmentBuffer, "%d", Alignment);
          }
          else if (Alignment >= 0x100000) {
            sprintf (AlignmentBuffer, "%dM", Alignment/0x100000);
          }
          else {
            sprintf (AlignmentBuffer, "%dK", Alignment/0x400);
          }
          Status = StringtoAlignment (AlignmentBuffer, &(InputFileAlign[InputFileNum]));
        }
        else {
          Status = StringtoAlignment (argv[1], &(InputFileAlign[InputFileNum]));
        }
        if (EFI_ERROR (Status)) {
          Error (NULL, 0, 1003, "Invalid option value", "%s = %s", argv[0], argv[1]);
          goto Finish;
        }
        argc -= 2;
        argv += 2;
      }
      InputFileNum ++;
      continue;
    }

    if ((stricmp (argv[0], "-n") == 0) || (stricmp (argv[0], "--sectionalign") == 0)) {
      Error (NULL, 0, 1000, "Unknown option", "SectionAlign option must be specified with section file.");
      goto Finish;
    }

    if ((stricmp (argv[0], "-v") == 0) || (stricmp (argv[0], "--verbose") == 0)) {
      SetPrintLevel (VERBOSE_LOG_LEVEL);
      VerboseMsg ("Verbose output Mode Set!");
      argc --;
      argv ++;
      continue;
    }

    if ((stricmp (argv[0], "-q") == 0) || (stricmp (argv[0], "--quiet") == 0)) {
      SetPrintLevel (KEY_LOG_LEVEL);
      KeyMsg ("Quiet output Mode Set!");
      argc --;
      argv ++;
      continue;
    }

    if ((stricmp (argv[0], "-d") == 0) || (stricmp (argv[0], "--debug") == 0)) {
      Status = AsciiStringToUint64 (argv[1], FALSE, &LogLevel);
      if (EFI_ERROR (Status)) {
        Error (NULL, 0, 1003, "Invalid option value", "%s = %s", argv[0], argv[1]);
        goto Finish;
      }
      if (LogLevel > 9) {
        Error (NULL, 0, 1003, "Invalid option value", "Debug Level range is 0-9, current input level is %d", (int) LogLevel);
        goto Finish;
      }
      SetPrintLevel (LogLevel);
      DebugMsg (NULL, 0, 9, "Debug Mode Set", "Debug Output Mode Level %s is set!", argv[1]);
      argc -= 2;
      argv += 2;
      continue;
    }

    Error (NULL, 0, 1000, "Unknown option", "%s", argv[0]);
    goto Finish;
  }

  VerboseMsg ("%s tool start.", UTILITY_NAME);

  //
  // Check the complete input parameters.
  //
  if (FfsFiletype == EFI_FV_FILETYPE_ALL) {
    Error (NULL, 0, 1001, "Missing option", "filetype");
    goto Finish;
  }

  if (BtCompareGuid (&FileGuid, &mZeroGuid) == 0) {
    Error (NULL, 0, 1001, "Missing option", "fileguid");
    goto Finish;
  }

  if (InputFileNum == 0) {
    Error (NULL, 0, 1001, "Missing option", "Input files");
    goto Finish;
  }

  //
  // Output input parameter information
  //
  VerboseMsg ("Fv File type is %s", mFfsFileType [FfsFiletype]);
  VerboseMsg ("Output file name is %s", OutputFileName);
  VerboseMsg ("FFS File Guid is %08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
                (unsigned) FileGuid.Data1,
                FileGuid.Data2,
                FileGuid.Data3,
                FileGuid.Data4[0],
                FileGuid.Data4[1],
                FileGuid.Data4[2],
                FileGuid.Data4[3],
                FileGuid.Data4[4],
                FileGuid.Data4[5],
                FileGuid.Data4[6],
                FileGuid.Data4[7]);
  if ((FfsAttrib & FFS_ATTRIB_FIXED) != 0) {
    VerboseMsg ("FFS File has the fixed file attribute");
  }
  if ((FfsAttrib & FFS_ATTRIB_CHECKSUM) != 0) {
    VerboseMsg ("FFS File requires the checksum of the whole file");
  }
  VerboseMsg ("FFS file alignment is %s", mFfsValidAlignName[FfsAlign]);
  for (Index = 0; Index < InputFileNum; Index ++) {
    if (InputFileAlign[Index] == 0) {
      //
      // Minimum alignment is 1 byte.
      //
      InputFileAlign[Index] = 1;
    }
    VerboseMsg ("the %dth input section name is %s and section alignment is %u", Index, InputFileName[Index], (unsigned) InputFileAlign[Index]);
  }

  //
  // Calculate the size of all input section files.
  //
  Status = GetSectionContents (
             InputFileName,
             InputFileAlign,
             InputFileNum,
             FfsAttrib,
             FileBuffer,
             &FileSize,
             &MaxAlignment,
             &PeSectionNum
             );

  if ((FfsFiletype == EFI_FV_FILETYPE_SECURITY_CORE ||
      FfsFiletype == EFI_FV_FILETYPE_PEI_CORE ||
      FfsFiletype == EFI_FV_FILETYPE_DXE_CORE) && (PeSectionNum != 1)) {
    Error (NULL, 0, 2000, "Invalid parameter", "Fv File type %s must have one and only one Pe section, but %u Pe section are input", mFfsFileType [FfsFiletype], PeSectionNum);
    goto Finish;
  }

  if ((FfsFiletype == EFI_FV_FILETYPE_PEIM ||
      FfsFiletype == EFI_FV_FILETYPE_DRIVER ||
      FfsFiletype == EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER ||
      FfsFiletype == EFI_FV_FILETYPE_APPLICATION) && (PeSectionNum < 1)) {
    Error (NULL, 0, 2000, "Invalid parameter", "Fv File type %s must have at least one Pe section, but no Pe section is input", mFfsFileType [FfsFiletype]);
    goto Finish;
  }

  if (Status == EFI_BUFFER_TOO_SMALL) {
    FileBuffer = (UINT8 *) malloc (FileSize);
    if (FileBuffer == NULL) {
      Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!");
      goto Finish;
    }
    memset (FileBuffer, 0, FileSize);

    //
    // read all input file contents into a buffer
    //
    Status = GetSectionContents (
               InputFileName,
               InputFileAlign,
               InputFileNum,
               FfsAttrib,
               FileBuffer,
               &FileSize,
               &MaxAlignment,
               &PeSectionNum
               );
  }

  if (EFI_ERROR (Status)) {
    goto Finish;
  }

  if (FileBuffer == NULL && FileSize != 0) {
    Error (NULL, 0, 4001, "Resource", "memory cannot be allocated!");
    goto Finish;
  }

  //
  // Create Ffs file header.
  //
  memset (&FfsFileHeader, 0, sizeof (EFI_FFS_FILE_HEADER2));
  memcpy (&FfsFileHeader.Name, &FileGuid, sizeof (EFI_GUID));
  FfsFileHeader.Type       = FfsFiletype;
  //
  // Update FFS Alignment based on the max alignment required by input section files
  //
  VerboseMsg ("the max alignment of all input sections is %u", (unsigned) MaxAlignment);
  for (Index = 0; Index < sizeof (mFfsValidAlign) / sizeof (UINT32) - 1; Index ++) {
    if ((MaxAlignment > mFfsValidAlign [Index]) && (MaxAlignment <= mFfsValidAlign [Index + 1])) {
      break;
    }
  }
  if (FfsAlign < Index) {
    FfsAlign = Index;
  }
  VerboseMsg ("the alignment of the generated FFS file is %u", (unsigned) mFfsValidAlign [FfsAlign + 1]);

  //
  // Now FileSize includes the EFI_FFS_FILE_HEADER
  //
  if (FileSize + sizeof (EFI_FFS_FILE_HEADER) >= MAX_FFS_SIZE) {
    HeaderSize = sizeof (EFI_FFS_FILE_HEADER2);
    FileSize += sizeof (EFI_FFS_FILE_HEADER2);
    FfsFileHeader.ExtendedSize = FileSize;
    memset(FfsFileHeader.Size, 0, sizeof (UINT8) * 3);
    FfsAttrib |= FFS_ATTRIB_LARGE_FILE;
  } else {
    HeaderSize = sizeof (EFI_FFS_FILE_HEADER);
    FileSize += sizeof (EFI_FFS_FILE_HEADER);
    FfsFileHeader.Size[0]  = (UINT8) (FileSize & 0xFF);
    FfsFileHeader.Size[1]  = (UINT8) ((FileSize & 0xFF00) >> 8);
    FfsFileHeader.Size[2]  = (UINT8) ((FileSize & 0xFF0000) >> 16);
  }
  VerboseMsg ("the size of the generated FFS file is %u bytes", (unsigned) FileSize);

  //FfsAlign larger than 7, set FFS_ATTRIB_DATA_ALIGNMENT2
  if (FfsAlign < 8) {
    FfsFileHeader.Attributes = (EFI_FFS_FILE_ATTRIBUTES) (FfsAttrib | (FfsAlign << 3));
  } else {
    FfsFileHeader.Attributes = (EFI_FFS_FILE_ATTRIBUTES) (FfsAttrib | ((FfsAlign & 0x7) << 3) | FFS_ATTRIB_DATA_ALIGNMENT2);
  }

  //
  // Fill in checksums and state, these must be zero for checksumming
  //
  // FileHeader.IntegrityCheck.Checksum.Header = 0;
  // FileHeader.IntegrityCheck.Checksum.File = 0;
  // FileHeader.State = 0;
  //
  FfsFileHeader.IntegrityCheck.Checksum.Header = CalculateChecksum8 (
                                                   (UINT8 *) &FfsFileHeader,
                                                   HeaderSize
                                                   );

  if (FfsFileHeader.Attributes & FFS_ATTRIB_CHECKSUM) {
    //
    // Ffs header checksum = zero, so only need to calculate ffs body.
    //
    FfsFileHeader.IntegrityCheck.Checksum.File = CalculateChecksum8 (
                                                   FileBuffer,
                                                   FileSize - HeaderSize
                                                   );
  } else {
    FfsFileHeader.IntegrityCheck.Checksum.File = FFS_FIXED_CHECKSUM;
  }

  FfsFileHeader.State = EFI_FILE_HEADER_CONSTRUCTION | EFI_FILE_HEADER_VALID | EFI_FILE_DATA_VALID;

  //
  // Open output file to write ffs data.
  //
  if (OutputFileName != NULL) {
    remove(OutputFileName);
    FfsFile = fopen (LongFilePath (OutputFileName), "wb");
    if (FfsFile == NULL) {
      Error (NULL, 0, 0001, "Error opening file", OutputFileName);
      goto Finish;
    }
    //
    // write header
    //
    fwrite (&FfsFileHeader, 1, HeaderSize, FfsFile);
    //
    // write data
    //
    if (FileBuffer != NULL) {
      fwrite (FileBuffer, 1, FileSize - HeaderSize, FfsFile);
    }

    fclose (FfsFile);
  }

Finish:
  if (InputFileName != NULL) {
    free (InputFileName);
  }
  if (InputFileAlign != NULL) {
    free (InputFileAlign);
  }
  if (FileBuffer != NULL) {
    free (FileBuffer);
  }
  //
  // If any errors were reported via the standard error reporting
  // routines, then the status has been saved. Get the value and
  // return it to the caller.
  //
  VerboseMsg ("%s tool done with return code is 0x%x.", UTILITY_NAME, GetUtilityStatus ());

  return GetUtilityStatus ();
}
