/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_soltools.hxx"

/*
 * adjustvisibilty -- a tool to adjust the visibility of the so called 
 *                    'fix and continue' globalized symbols generated by 
 *                    the Sun Studio 8 compiler from 'DEFAULT' to 'HIDDEN'
 *                    
 * References: "Linker and Libraries Guide", Solaris 9 documentation
 *             "Stabs Interface", SunStudio 8 documentation
 */

#include <string>
#include <iostream>
#include <exception>
#include <stdexcept>
#include <cerrno>
#include <fcntl.h>
#include <unistd.h>
#include <libelf.h>
#include <gelf.h>
#include <utime.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits>
#include <stdio.h>

// Note: There is no GELF_ST_VISIBILITY macro in gelf.h, we roll our own.
#define GELF_ST_VISIBILITY(o)   ((o)&0x3) // See "Linker and Libraries Guide".

// See "Linker and Libraries Guide", ELF object format description.
static const char* SymbolType[STT_NUM] = {
    "NOTYPE",
    "OBJECT",
    "FUNC  ",
    "SECT  ",
    "FILE  ",
    "COMM  ",
    "TLS   "
};

static const char* SymbolBinding[STB_NUM] = {
    "LOCAL ",
    "GLOBAL",
    "WEAK  "
};

static const char* SymbolVisibility[4] = {   // Note: There is no STV_NUM macro
    "DEFAULT  ",
    "INTERNAL ",
    "HIDDEN   ",
    "PROTECTED"
};

class ElfError : public std::exception
{
    public:
        ElfError(const std::string& rFile, const std::string& rMessage);
        ~ElfError() throw() {};
        virtual const char* what() const throw() { return m_sMessage.c_str(); }

    private:
        std::string m_sMessage;
};

ElfError::ElfError(const std::string& rFile, const std::string& rMessage) 
{
    if ( rFile != "" ) {
        m_sMessage = rFile;
        m_sMessage += ": ";
    }
    m_sMessage += rMessage;
    const char *pElfMsg = elf_errmsg(0);
    if ( pElfMsg ) {
        m_sMessage += ": ";
        m_sMessage += pElfMsg;
    }
}
    
void initElfLib()
{
    if ( elf_version(EV_CURRENT) == EV_NONE) {
        throw ElfError("", "elf_version() failed");
    }
    return;
}

bool isFixAndContinueSymbol(const std::string& rSymbol)
{
    // The globalized 'fix and continue' symbols have the following
    // form, see "Stabs interface", page 164:
    // {.$}X{ABC}uniquepattern[.function_name][EQUIVn][.variable_name]
    char c0 = rSymbol[0];
    char c1 = rSymbol[1];
    char c2 = rSymbol[2];
    if ( c0 == '.' || c0 == '$' ) {
        if ( c1 == 'X' ) {
            if ( c2 == 'A' || c2 == 'B' || c2 == 'C' || c2 == 'D' ) {
                return true;
            }
        }
    }
    return false;
}

void adjustVisibility( const std::string& rFile, int fd, bool bVerbose)
{
    if ( bVerbose ) {
        std::cout << "File: " << rFile << ": adjusting 'fix and continue' symbol visibility\n";
    }

    try {
        Elf* pElf;
		if ((pElf = elf_begin(fd, ELF_C_RDWR, 0)) == NULL) {
            throw ElfError(rFile, "elf_begin() failed");
        }
        // Check if file is ELF file.
		if ( elf_kind(pElf) != ELF_K_ELF ) {
            throw ElfError(rFile, "elf_kind() failed, file is not an ELF object file");
        }

        // Iterate over sections.
	    Elf_Scn* pScn = 0;
	    while ( (pScn = elf_nextscn(pElf, pScn)) != 0 ) {
            GElf_Shdr aShdr;
    		if ( gelf_getshdr(pScn, &aShdr) == 0 ) {
                throw ElfError(rFile, "gelf_getshdr() failed");
	    	}
		    if ( aShdr.sh_type != SHT_SYMTAB ) {
    			continue;
            }
            // Section is a symbol section. Get the assiociated data.
            Elf_Data* pSymbolData;
		    if ( (pSymbolData = elf_getdata(pScn, 0)) == NULL ) {
                throw ElfError(rFile, "elf_getdata() failed");
            }
            // Iterate over symbol table.
            GElf_Xword nSymbols = aShdr.sh_size / aShdr.sh_entsize;
            if ( nSymbols > std::numeric_limits< int >::max() )
            {
                throw ElfError(rFile, "too many symbols");
            }
            for ( int nIndex = 0; nIndex < nSymbols; ++nIndex) {
                // Get symbol.
                GElf_Sym aSymbol;
    			if ( gelf_getsym(pSymbolData, nIndex, &aSymbol) == NULL ) 
                {
                    throw ElfError(rFile, "gelf_getsym() failed");
                }
                std::string  sSymbolName(elf_strptr(pElf, aShdr.sh_link, aSymbol.st_name));
                if ( isFixAndContinueSymbol(sSymbolName) ) {
                    // Get the symbol visibility.
    	    		unsigned int nSymbolVisibility = GELF_ST_VISIBILITY(aSymbol.st_other);
                    if ( bVerbose ) {
        	    		// Get the symbol type and binding.
	        		    unsigned int nSymbolType       = GELF_ST_TYPE(aSymbol.st_info);
    	        		unsigned int nSymbolBind       = GELF_ST_BIND(aSymbol.st_info);
                        std::cout << "Symbol: " << sSymbolName << ", " 
                            << "Type: ";
                            if ( SymbolType[nSymbolType] ) {
                                std::cout << SymbolType[nSymbolType];
                            } else {
                                std::cout << nSymbolType;
                            }
                            std::cout << ", Binding: ";
                            if ( SymbolBinding[nSymbolBind] ) {
                                std::cout << SymbolBinding[nSymbolBind];
                            } else {
                                std::cout << nSymbolBind;
                            }
                            std::cout << ", Visibility: ";
                            if ( SymbolVisibility[nSymbolVisibility] ) {
                                std::cout << SymbolVisibility[nSymbolVisibility];
                            } else {
                                std::cout << nSymbolVisibility;
                            }
                            std::cout << "-> " << SymbolVisibility[STV_HIDDEN] << "\n";
                    }
                    // Toggle visibility to "hidden".
                    aSymbol.st_other = GELF_ST_VISIBILITY(STV_HIDDEN);
                    // Write back symbol data to underlying structure.
    			    if ( gelf_update_sym(pSymbolData, nIndex, &aSymbol) == NULL ) 
                    {
                        throw ElfError(rFile, "gelf_update_sym() failed");
                    }
                }
            }
        }
        // Write changed object file to disk.
        if ( elf_update(pElf, ELF_C_WRITE) == -1 ) {
            throw ElfError(rFile, "elf_update() failed");
        }
        elf_end(pElf);

    } catch (ElfError& e) {
        close(fd);
        throw;
    }
    return;
}

void processObject(const std::string& rFile, bool bPreserve, bool bVerbose)
{
    int fd;
    struct stat aStatBuf;
    
    if ((fd = open(rFile.c_str(), O_RDWR)) == -1) {
         std::string sMessage("adjustVisibilty() failed: can't open file ");
         sMessage += rFile;
         sMessage += ": ";
         sMessage += std::strerror(errno);
         throw std::runtime_error(sMessage);
    }

    if ( bPreserve ) {
        if ( fstat(fd, &aStatBuf) == -1) {
             std::string sMessage("adjustVisibilty() failed: can't stat file ");
             sMessage += rFile;
             sMessage += ": ";
             sMessage += std::strerror(errno);
             throw std::runtime_error(sMessage);
        }
    }
    
    adjustVisibility(rFile, fd, bVerbose);
    
    close(fd);

    if ( bPreserve ) {
        struct utimbuf aUtimBuf = {aStatBuf.st_atime, aStatBuf.st_mtime};
        if ( utime(rFile.c_str(), &aUtimBuf) == -1 ) {
             std::string sMessage("adjustVisibilty() failed: can't reset timestamp ");
             sMessage += rFile;
             sMessage += ": ";
             sMessage += std::strerror(errno);
             throw std::runtime_error(sMessage);
        }
    }
    return;
}

int main(int argc, char* argv[])
{
    int  c;
    bool bPreserve = false;
    bool bVerbose  = false;

    while ( (c = getopt(argc, argv, "pv")) != -1 ) {
        switch(c) {
            case 'p':
                bPreserve = true;
                break;
            case 'v':
                bVerbose = true;
                break;
            case '?':
                std::cerr << "Unrecognized option: -" << optopt << "\n";
                break;
            default:
                break;
        }
    }

    if ( optind == argc ) {
        std::cout << "usage: " << argv[0] << " [-pv] <elf-object> ...\n";
        std::cout << "       -p     preserve time stamps\n";
        std::cout << "       -v     verbose\n";
        return 1;
    }

    try {
        initElfLib();
    
        for ( ; optind < argc; optind++ )  {
            processObject(std::string(argv[optind]), bPreserve, bVerbose);
        }

    } catch (std::exception& e) {
        std::cerr << argv[0] << ": " << e.what() << "\n";
        return 1;
    }

    return 0;
}
