/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: adjustvisibility.cxx,v $
 *
 *  $Revision: 1.5 $
 *
 *  last change: $Author: obo $ $Date: 2006-09-17 11:32:54 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

// 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>

// 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;
}
