/******************************************************************************
 *
 * Module Name: asfile - Main module for the acpi source processor utility
 *
 *****************************************************************************/

/*
 * Copyright (C) 2000 - 2019, Intel Corp.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    substantially similar to the "NO WARRANTY" disclaimer below
 *    ("Disclaimer") and any redistribution must be conditioned upon
 *    including a substantially similar Disclaimer requirement for further
 *    binary redistribution.
 * 3. Neither the names of the above-listed copyright holders nor the names
 *    of any contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL") version 2 as published by the Free
 * Software Foundation.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 */

#include "acpisrc.h"

/* Local prototypes */

void
AsDoWildcard (
    ACPI_CONVERSION_TABLE   *ConversionTable,
    char                    *SourcePath,
    char                    *TargetPath,
    int                     MaxPathLength,
    int                     FileType,
    char                    *WildcardSpec);

BOOLEAN
AsDetectLoneLineFeeds (
    char                    *Filename,
    char                    *Buffer);

static BOOLEAN
AsCheckForNonPrintableChars (
    char                    *FileBuffer,
    UINT32                  FileSize);

static ACPI_INLINE int
AsMaxInt (int a, int b)
{
    return (a > b ? a : b);
}


/******************************************************************************
 *
 * FUNCTION:    AsDoWildcard
 *
 * DESCRIPTION: Process files via wildcards
 *
 ******************************************************************************/

void
AsDoWildcard (
    ACPI_CONVERSION_TABLE   *ConversionTable,
    char                    *SourcePath,
    char                    *TargetPath,
    int                     MaxPathLength,
    int                     FileType,
    char                    *WildcardSpec)
{
    void                    *DirInfo;
    char                    *Filename;
    char                    *SourceDirPath;
    char                    *TargetDirPath;
    char                    RequestedFileType;


    if (FileType == FILE_TYPE_DIRECTORY)
    {
        RequestedFileType = REQUEST_DIR_ONLY;
    }
    else
    {
        RequestedFileType = REQUEST_FILE_ONLY;
    }

    VERBOSE_PRINT (("Checking for %s source files in directory \"%s\"\n",
            WildcardSpec, SourcePath));

    /* Open the directory for wildcard search */

    DirInfo = AcpiOsOpenDirectory (SourcePath, WildcardSpec, RequestedFileType);
    if (DirInfo)
    {
        /*
         * Get all of the files that match both the
         * wildcard and the requested file type
         */
        while ((Filename = AcpiOsGetNextFilename (DirInfo)))
        {
            /* Looking for directory files, must check file type */

            switch (RequestedFileType)
            {
            case REQUEST_DIR_ONLY:

                /* If we actually have a dir, process the subtree */

                if (!AsCheckForDirectory (SourcePath, TargetPath, Filename,
                    &SourceDirPath, &TargetDirPath))
                {
                    VERBOSE_PRINT (("Subdirectory: %s\n", Filename));

                    AsProcessTree (ConversionTable, SourceDirPath, TargetDirPath);
                    free (SourceDirPath);
                    free (TargetDirPath);
                }
                break;

            case REQUEST_FILE_ONLY:

                /* Otherwise, this is a file, not a directory */

                VERBOSE_PRINT (("File: %s\n", Filename));

                AsProcessOneFile (ConversionTable, SourcePath, TargetPath,
                    MaxPathLength, Filename, FileType);
                break;

            default:

                break;
            }
        }

        /* Cleanup */

        AcpiOsCloseDirectory (DirInfo);
    }
}


/******************************************************************************
 *
 * FUNCTION:    AsProcessTree
 *
 * DESCRIPTION: Process the directory tree. Files with the extension ".C" and
 *              ".H" are processed as the tree is traversed.
 *
 ******************************************************************************/

ACPI_NATIVE_INT
AsProcessTree (
    ACPI_CONVERSION_TABLE   *ConversionTable,
    char                    *SourcePath,
    char                    *TargetPath)
{
    int                     MaxPathLength;


    MaxPathLength = AsMaxInt (strlen (SourcePath), strlen (TargetPath));

    if (!(ConversionTable->Flags & FLG_NO_FILE_OUTPUT))
    {
        if (ConversionTable->Flags & FLG_LOWERCASE_DIRNAMES)
        {
            AcpiUtStrlwr (TargetPath);
        }

        VERBOSE_PRINT (("Creating Directory \"%s\"\n", TargetPath));
        if (mkdir (TargetPath))
        {
            if (errno != EEXIST)
            {
                printf ("Could not create target directory\n");
                return (-1);
            }
        }
    }

    /* Do the C source files */

    AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
        FILE_TYPE_SOURCE, "*.c");

    /* Do the C header files */

    AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
        FILE_TYPE_HEADER, "*.h");

    /* Do the Lex file(s) */

    AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
        FILE_TYPE_SOURCE, "*.l");

    /* Do the yacc file(s) */

    AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
        FILE_TYPE_SOURCE, "*.y");

    /* Do any ASL files */

    AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
        FILE_TYPE_HEADER, "*.asl");

    /* Do any subdirectories */

    AsDoWildcard (ConversionTable, SourcePath, TargetPath, MaxPathLength,
        FILE_TYPE_DIRECTORY, "*");

    return (0);
}


/******************************************************************************
 *
 * FUNCTION:    AsDetectLoneLineFeeds
 *
 * DESCRIPTION: Find LF without CR.
 *
 ******************************************************************************/

BOOLEAN
AsDetectLoneLineFeeds (
    char                    *Filename,
    char                    *Buffer)
{
    UINT32                  i = 1;
    UINT32                  LfCount = 0;
    UINT32                  LineCount = 0;


    if (!Buffer[0])
    {
        return (FALSE);
    }

    while (Buffer[i])
    {
        if (Buffer[i] == 0x0A)
        {
            if (Buffer[i-1] != 0x0D)
            {
                LfCount++;
            }

            LineCount++;
        }
        i++;
    }

    if (LfCount)
    {
        if (LineCount == LfCount)
        {
            if (!Gbl_IgnoreLoneLineFeeds)
            {
                printf ("%s: ****File has UNIX format**** (LF only, not CR/LF) %u lines\n",
                    Filename, LfCount);
            }
        }
        else
        {
            printf ("%s: %u lone linefeeds in file\n", Filename, LfCount);
        }

        return (TRUE);
    }

    return (FALSE);
}


/******************************************************************************
 *
 * FUNCTION:    AsConvertFile
 *
 * DESCRIPTION: Perform the requested transforms on the file buffer (as
 *              determined by the ConversionTable and the FileType).
 *
 ******************************************************************************/

void
AsConvertFile (
    ACPI_CONVERSION_TABLE   *ConversionTable,
    char                    *FileBuffer,
    char                    *Filename,
    ACPI_NATIVE_INT         FileType)
{
    UINT32                  i;
    UINT32                  Functions;
    ACPI_STRING_TABLE       *StringTable;
    ACPI_IDENTIFIER_TABLE   *ConditionalTable;
    ACPI_IDENTIFIER_TABLE   *LineTable;
    ACPI_TYPED_IDENTIFIER_TABLE *StructTable;
    ACPI_IDENTIFIER_TABLE   *SpecialMacroTable;
    char                    *SpdxHeader=NULL;


    switch (FileType)
    {
    case FILE_TYPE_SOURCE:

        Functions           = ConversionTable->SourceFunctions;
        StringTable         = ConversionTable->SourceStringTable;
        LineTable           = ConversionTable->SourceLineTable;
        ConditionalTable    = ConversionTable->SourceConditionalTable;
        StructTable         = ConversionTable->SourceStructTable;
        SpecialMacroTable   = ConversionTable->SourceSpecialMacroTable;
        SpdxHeader          = ConversionTable->SourceSpdxHeader;
        break;

    case FILE_TYPE_HEADER:

        Functions           = ConversionTable->HeaderFunctions;
        StringTable         = ConversionTable->HeaderStringTable;
        LineTable           = ConversionTable->HeaderLineTable;
        ConditionalTable    = ConversionTable->HeaderConditionalTable;
        StructTable         = ConversionTable->HeaderStructTable;
        SpecialMacroTable   = ConversionTable->HeaderSpecialMacroTable;
        SpdxHeader          = ConversionTable->HeaderSpdxHeader;
        break;

    case FILE_TYPE_PATCH:

        Functions           = ConversionTable->PatchFunctions;
        StringTable         = ConversionTable->PatchStringTable;
        LineTable           = ConversionTable->PatchLineTable;
        ConditionalTable    = ConversionTable->PatchConditionalTable;
        StructTable         = ConversionTable->PatchStructTable;
        SpecialMacroTable   = ConversionTable->PatchSpecialMacroTable;
        break;

    default:

        printf ("Unknown file type, cannot process\n");
        return;
    }


    Gbl_StructDefs = strstr (FileBuffer, "/* acpisrc:StructDefs");
    Gbl_Files++;
    VERBOSE_PRINT (("Processing %u bytes\n",
        (unsigned int) strlen (FileBuffer)));

    if (Gbl_Cleanup)
    {
        AsRemoveExtraLines (FileBuffer, Filename);
        AsRemoveSpacesAfterPeriod (FileBuffer, Filename);
    }

    if (ConversionTable->LowerCaseTable)
    {
        for (i = 0; ConversionTable->LowerCaseTable[i].Identifier; i++)
        {
            AsLowerCaseString (ConversionTable->LowerCaseTable[i].Identifier,
                FileBuffer);
        }
    }

    /* Process all the string replacements */

    if (StringTable)
    {
        for (i = 0; StringTable[i].Target; i++)
        {
            AsReplaceString (StringTable[i].Target, StringTable[i].Replacement,
                StringTable[i].Type, FileBuffer);
        }
    }

    if (LineTable)
    {
        for (i = 0; LineTable[i].Identifier; i++)
        {
            AsRemoveLine (FileBuffer, LineTable[i].Identifier);
        }
    }

    if (ConditionalTable)
    {
        for (i = 0; ConditionalTable[i].Identifier; i++)
        {
            AsRemoveConditionalCompile (FileBuffer, ConditionalTable[i].Identifier);
        }
    }

#ifdef _OBSOLETE_FUNCTIONS
    if (MacroTable)
    {
        for (i = 0; MacroTable[i].Identifier; i++)
        {
            AsRemoveMacro (FileBuffer, MacroTable[i].Identifier);
        }
    }
#endif

    if (StructTable)
    {
        for (i = 0; StructTable[i].Identifier; i++)
        {
            AsInsertPrefix (FileBuffer, StructTable[i].Identifier,
                StructTable[i].Type);
        }
    }

    if (SpecialMacroTable)
    {
        for (i = 0; SpecialMacroTable[i].Identifier; i++)
        {
            AsCleanupSpecialMacro (FileBuffer, SpecialMacroTable[i].Identifier);
        }
    }

    /* Process the function table */

    for (i = 0; i < 32; i++)
    {
        /* Decode the function bitmap */

        switch ((1 << i) & Functions)
        {
        case 0:

            /* This function not configured */
            break;

        case CVT_COUNT_TABS:

            AsCountTabs (FileBuffer, Filename);
            break;

        case CVT_COUNT_NON_ANSI_COMMENTS:

            AsCountNonAnsiComments (FileBuffer, Filename);
            break;

        case CVT_CHECK_BRACES:

            AsCheckForBraces (FileBuffer, Filename);
            break;

        case CVT_TRIM_LINES:

            AsTrimLines (FileBuffer, Filename);
            break;

        case CVT_COUNT_LINES:

            AsCountSourceLines (FileBuffer, Filename);
            break;

        case CVT_BRACES_ON_SAME_LINE:

            AsBracesOnSameLine (FileBuffer);
            break;

        case CVT_MIXED_CASE_TO_UNDERSCORES:

            AsMixedCaseToUnderscores (FileBuffer, Filename);
            break;

        case CVT_LOWER_CASE_IDENTIFIERS:

            AsLowerCaseIdentifiers (FileBuffer);
            break;

        case CVT_REMOVE_DEBUG_MACROS:

            AsRemoveDebugMacros (FileBuffer);
            break;

        case CVT_TRIM_WHITESPACE:

            AsTrimWhitespace (FileBuffer);
            break;

        case CVT_REMOVE_EMPTY_BLOCKS:

            AsRemoveEmptyBlocks (FileBuffer, Filename);
            break;

        case CVT_REDUCE_TYPEDEFS:

            AsReduceTypedefs (FileBuffer, "typedef union");
            AsReduceTypedefs (FileBuffer, "typedef struct");
            break;

        case CVT_SPACES_TO_TABS4:

            AsTabify4 (FileBuffer);
            break;

        case CVT_SPACES_TO_TABS8:

            AsTabify8 (FileBuffer);
            break;

        case CVT_COUNT_SHORTMULTILINE_COMMENTS:

#ifdef ACPI_FUTURE_IMPLEMENTATION
            AsTrimComments (FileBuffer, Filename);
#endif
            break;

        default:

            printf ("Unknown conversion subfunction opcode\n");
            break;
        }
    }

    if (ConversionTable->NewHeader)
    {
        AsReplaceHeader (FileBuffer, ConversionTable->NewHeader);
    }
    if (SpdxHeader)
    {
        AsDoSpdxHeader (FileBuffer, SpdxHeader);
    }
}

/*******************************************************************************
 *
 * FUNCTION:    AsCheckForNonPrintableChars
 *
 * PARAMETERS:  FileBuffer              - Buffer with contents of entire file
 *              FileSize                - Size of the file and buffer
 *
 * RETURN:      TRUE if there are no non-printable characters
 *
 * DESCRIPTION: Scan a file for any non-printable ASCII bytes.
 *
 ******************************************************************************/

static BOOLEAN
AsCheckForNonPrintableChars (
    char                    *FileBuffer,
    UINT32                  FileSize)
{
    BOOLEAN                 Found = TRUE;
    UINT8                   Byte;
    UINT32                  i;


    /* Scan entire file for any non-printable characters */

    for (i = 0; i < FileSize; i++)
    {
        Byte = FileBuffer[i];
        if (!isprint (Byte) && !isspace (Byte))
        {
            printf ( "Non-printable character (0x%2.2X) "
                "at file offset: %8u (0x%X)\n", Byte, i, i);
            Found = FALSE;
        }
    }

    return (Found);
}


/******************************************************************************
 *
 * FUNCTION:    AsProcessOneFile
 *
 * DESCRIPTION: Process one source file. The file is opened, read entirely
 *              into a buffer, converted, then written to a new file.
 *
 ******************************************************************************/

ACPI_NATIVE_INT
AsProcessOneFile (
    ACPI_CONVERSION_TABLE   *ConversionTable,
    char                    *SourcePath,
    char                    *TargetPath,
    int                     MaxPathLength,
    char                    *Filename,
    ACPI_NATIVE_INT         FileType)
{
    char                    *Pathname;
    char                    *OutPathname;
    int                     Status = 0;


    /* Allocate a file pathname buffer for both source and target */

    Pathname = calloc (MaxPathLength + strlen (Filename) + 2, 1);
    if (!Pathname)
    {
        printf ("Could not allocate buffer for file pathnames\n");
        return (-1);
    }

    Gbl_FileType = FileType;

    /* Generate the source pathname and read the file */

    if (SourcePath)
    {
        strcpy (Pathname, SourcePath);
        strcat (Pathname, "/");
    }

    strcat (Pathname, Filename);
    if (AsGetFile (Pathname, &Gbl_FileBuffer, &Gbl_FileSize))
    {
        Status = -1;
        goto Exit1;
    }

    /* Exit now if simply checking the file for printable ascii chars */

    if (Gbl_CheckAscii)
    {
        Status = 0;
        goto Exit2;
    }

    Gbl_HeaderSize = 0;
    if (strstr (Filename, ".asl"))
    {
        Gbl_HeaderSize = LINES_IN_ASL_HEADER; /* Lines in default ASL header */
    }
    else if (strstr (Gbl_FileBuffer, LEGAL_HEADER_SIGNATURE))
    {
        Gbl_HeaderSize = LINES_IN_LEGAL_HEADER; /* Normal C file and H header */
    }
    else if (strstr (Gbl_FileBuffer, LINUX_HEADER_SIGNATURE))
    {
        Gbl_HeaderSize = LINES_IN_LINUX_HEADER; /* Linuxized C file and H header */
    }

    /* Process the file in the buffer */

    Gbl_MadeChanges = FALSE;
    if (!Gbl_IgnoreLoneLineFeeds && Gbl_HasLoneLineFeeds)
    {
        /*
         * All lone LFs will be converted to CR/LF
         * (when file is written, Windows version only)
         */
        printf ("Converting lone linefeeds\n");
        Gbl_MadeChanges = TRUE;
    }

    AsConvertFile (ConversionTable, Gbl_FileBuffer, Pathname, FileType);

    if (!(ConversionTable->Flags & FLG_NO_FILE_OUTPUT))
    {
        if (!(Gbl_Overwrite && !Gbl_MadeChanges))
        {
            /* Generate the target pathname and write the file */

            OutPathname = calloc (MaxPathLength +
                strlen (Filename) + 2 + strlen (TargetPath), 1);
            if (!OutPathname)
            {
                printf ("Could not allocate buffer for file pathnames\n");
                Status = -1;
                goto Exit2;
            }

            strcpy (OutPathname, TargetPath);
            if (SourcePath)
            {
                strcat (OutPathname, "/");
                strcat (OutPathname, Filename);
            }

            AsPutFile (OutPathname, Gbl_FileBuffer, ConversionTable->Flags);
            free (OutPathname);
        }
    }

Exit2:
    free (Gbl_FileBuffer);

Exit1:
    free (Pathname);
    return (Status);
}


/******************************************************************************
 *
 * FUNCTION:    AsCheckForDirectory
 *
 * DESCRIPTION: Check if the current file is a valid directory. If not,
 *              construct the full pathname for the source and target paths.
 *              Checks for the dot and dot-dot files (they are ignored)
 *
 ******************************************************************************/

ACPI_NATIVE_INT
AsCheckForDirectory (
    char                    *SourceDirPath,
    char                    *TargetDirPath,
    char                    *Filename,
    char                    **SourcePath,
    char                    **TargetPath)
{
    char                    *SrcPath;
    char                    *TgtPath;


    if (!(strcmp (Filename, ".")) ||
        !(strcmp (Filename, "..")))
    {
        return (-1);
    }

    SrcPath = calloc (strlen (SourceDirPath) + strlen (Filename) + 2, 1);
    if (!SrcPath)
    {
        printf ("Could not allocate buffer for directory source pathname\n");
        return (-1);
    }

    TgtPath = calloc (strlen (TargetDirPath) + strlen (Filename) + 2, 1);
    if (!TgtPath)
    {
        printf ("Could not allocate buffer for directory target pathname\n");
        free (SrcPath);
        return (-1);
    }

    strcpy (SrcPath, SourceDirPath);
    strcat (SrcPath, "/");
    strcat (SrcPath, Filename);

    strcpy (TgtPath, TargetDirPath);
    strcat (TgtPath, "/");
    strcat (TgtPath, Filename);

    *SourcePath = SrcPath;
    *TargetPath = TgtPath;
    return (0);
}


/******************************************************************************
 *
 * FUNCTION:    AsGetFile
 *
 * DESCRIPTION: Open a file and read it entirely into a an allocated buffer
 *
 ******************************************************************************/

int
AsGetFile (
    char                    *Filename,
    char                    **FileBuffer,
    UINT32                  *FileSize)
{
    FILE                    *File;
    UINT32                  Size;
    char                    *Buffer;
    size_t                  Actual;


    /* Binary mode leaves CR/LF pairs */

    File = fopen (Filename, "rb");
    if (!File)
    {
        printf ("Could not open file %s\n", Filename);
        return (-1);
    }

    /* Need file size to allocate a buffer */

    Size = CmGetFileSize (File);
    if (Size == ACPI_UINT32_MAX)
    {
        printf ("Could not get file size for %s\n", Filename);
        goto ErrorExit;
    }

    /*
     * Create a buffer for the entire file
     * Add plenty extra buffer to accommodate string replacements
     */
    Gbl_TotalSize += Size;

    Buffer = calloc (Size * 2, 1);
    if (!Buffer)
    {
        printf ("Could not allocate buffer of size %u\n", Size * 2);
        goto ErrorExit;
    }

    /* Read the entire file */

    Actual = fread (Buffer, 1, Size, File);
    if (Actual != Size)
    {
        printf ("Could not read the input file %s (%u bytes)\n",
            Filename, Size);
        goto ErrorFree;
    }

    Buffer [Size] = 0;         /* Null terminate the buffer */
    fclose (File);

    /* This option checks the entire file for non-printable chars */

    if (Gbl_CheckAscii)
    {
        if (AsCheckForNonPrintableChars (Buffer, Size))
        {
            printf ("File contains only printable ASCII characters\n");
        }

        free (Buffer);
        return (0);
    }

    /* Check for unix contamination */

    Gbl_HasLoneLineFeeds = AsDetectLoneLineFeeds (Filename, Buffer);

    /*
     * Convert all CR/LF pairs to LF only. We do this locally so that
     * this code is portable across operating systems.
     */
    AsConvertToLineFeeds (Buffer);

    *FileBuffer = Buffer;
    *FileSize = Size;
    return (0);

ErrorFree:
    free (Buffer);

ErrorExit:
    fclose (File);
    return (-1);
}


/******************************************************************************
 *
 * FUNCTION:    AsPutFile
 *
 * DESCRIPTION: Create a new output file and write the entire contents of the
 *              buffer to the new file. Buffer must be a zero terminated string
 *
 ******************************************************************************/

int
AsPutFile (
    char                    *Pathname,
    char                    *FileBuffer,
    UINT32                  SystemFlags)
{
    FILE                    *File;
    UINT32                  FileSize;
    size_t                  Actual;
    int                     Status = 0;


    /* Create the target file */

    if (!(SystemFlags & FLG_NO_CARRIAGE_RETURNS))
    {
        /* Put back the CR before each LF */

        AsInsertCarriageReturns (FileBuffer);
    }

    File = fopen (Pathname, "w+b");
    if (!File)
    {
        perror ("Could not create destination file");
        printf ("Could not create destination file \"%s\"\n", Pathname);
        return (-1);
    }

    /* Write the buffer to the file */

    FileSize = strlen (FileBuffer);
    Actual = fwrite (FileBuffer, 1, FileSize, File);
    if (Actual != FileSize)
    {
        printf ("Error writing output file \"%s\"\n", Pathname);
        Status = -1;
    }

    fclose (File);
    return (Status);
}
