/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */


/*
 * 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 <cstring>
#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 (const 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) {
            close(fd);
            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 (const std::exception& e) {
        std::cerr << argv[0] << ": " << e.what() << "\n";
        return 1;
    }

    return 0;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
