/* TODO:
   1. check the ARM EABI version--this works for versions 1 and 2.
   2. use a more-intelligent approach to finding the symbol table, symbol-string
      table, and the .dynamic section.
   3. fix the determination of the host and ELF-file endianness
   4. write the help screen
*/

#include <stdio.h>
#include <common.h>
#include <debug.h>
#include <hash.h>
#include <libelf.h>
#include <elf.h>
#include <gelf.h>
#include <cmdline.h>
#include <string.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <soslim.h>
#include <symfilter.h>
#ifdef SUPPORT_ANDROID_PRELINK_TAGS
#include <prelink_info.h>
#endif

/* Flag set by --verbose.  This variable is global as it is accessed by the
   macro INFO() in multiple compilation unites. */
int verbose_flag = 0;
/* Flag set by --quiet.  This variable is global as it is accessed by the
   macro PRINT() in multiple compilation unites. */
int quiet_flag = 0;
static void print_dynamic_symbols(Elf *elf, const char *symtab_name);

int main(int argc, char **argv)
{
    int elf_fd = -1, newelf_fd = -1;
    Elf *elf = NULL, *newelf = NULL;
    char *infile = NULL;
    char *outfile = NULL;
    char *symsfile_name = NULL;
    int print_symtab = 0;
    int shady = 0;
    int dry_run = 0;
    int strip_debug = 0;

    /* Do not issue INFO() statements before you call get_options() to set
       the verbose flag as necessary.
    */

    int first = get_options(argc, argv,
                            &outfile,
                            &symsfile_name,
                            &print_symtab,
                            &verbose_flag,
                            &quiet_flag,
                            &shady,
                            &dry_run,
                            &strip_debug);

    if ((print_symtab && (first == argc)) ||
        (!print_symtab && first + 1 != argc)) {
        print_help();
        FAILIF(1,  "You must specify an input ELF file!\n");
    }
    FAILIF(print_symtab && (outfile || symsfile_name || shady),
           "You cannot provide --print and --outfile, --filter options, or "
           "--shady simultaneously!\n");
    FAILIF(dry_run && outfile,
           "You cannot have a dry run and output a file at the same time.");

    /* Check to see whether the ELF library is current. */
    FAILIF (elf_version(EV_CURRENT) == EV_NONE, "libelf is out of date!\n");

    if (print_symtab) {

        while (first < argc) {
            infile = argv[first++];

            INFO("Opening %s...\n", infile);
            elf_fd = open(infile, O_RDONLY);
            FAILIF(elf_fd < 0, "open(%s): %s (%d)\n",
                   infile,
                   strerror(errno),
                   errno);
            INFO("Calling elf_begin(%s)...\n", infile);
            elf = elf_begin(elf_fd, ELF_C_READ, NULL);
            FAILIF_LIBELF(elf == NULL, elf_begin);

            /* libelf can recognize COFF and A.OUT formats, but we handle only
               ELF. */
            FAILIF(elf_kind(elf) != ELF_K_ELF,
                   "Input file %s is not in ELF format!\n",
                   infile);

            /* Make sure this is a shared library or an executable. */
            {
                GElf_Ehdr elf_hdr;
                INFO("Making sure %s is a shared library or an executable.\n",
                     infile);
                FAILIF_LIBELF(0 == gelf_getehdr(elf, &elf_hdr), gelf_getehdr);
                FAILIF(elf_hdr.e_type != ET_DYN &&
                       elf_hdr.e_type != ET_EXEC,
                       "%s must be a shared library or an executable "
                       "(elf type is %d).\n",
                       infile,
                       elf_hdr.e_type);
            }

            print_dynamic_symbols(elf, infile);

            FAILIF_LIBELF(elf_end(elf), elf_end);
            FAILIF(close(elf_fd) < 0, "Could not close file %s: %s (%d)!\n",
                   infile, strerror(errno), errno);
        }
    }
    else {
        int elf_fd = -1;
        Elf *elf = NULL;
        infile = argv[first];

        INFO("Opening %s...\n", infile);
        elf_fd = open(infile, ((outfile == NULL && dry_run == 0) ? O_RDWR : O_RDONLY));
        FAILIF(elf_fd < 0, "open(%s): %s (%d)\n",
               infile,
               strerror(errno),
               errno);
        INFO("Calling elf_begin(%s)...\n", infile);
        elf = elf_begin(elf_fd,
                        ((outfile == NULL && dry_run == 0) ? ELF_C_RDWR : ELF_C_READ),
                        NULL);
        FAILIF_LIBELF(elf == NULL, elf_begin);

        /* libelf can recognize COFF and A.OUT formats, but we handle only ELF. */
        FAILIF(elf_kind(elf) != ELF_K_ELF,
               "Input file %s is not in ELF format!\n",
               infile);

        /* We run a better check in adjust_elf() itself.  It is permissible to call adjust_elf()
           on an executable if we are only stripping sections from the executable, not rearranging
           or moving sections.
        */
        if (0) {
            /* Make sure this is a shared library. */
            GElf_Ehdr elf_hdr;
            INFO("Making sure %s is a shared library...\n", infile);
            FAILIF_LIBELF(0 == gelf_getehdr(elf, &elf_hdr), gelf_getehdr);
            FAILIF(elf_hdr.e_type != ET_DYN,
                   "%s must be a shared library (elf type is %d, expecting %d).\n",
                   infile,
                   elf_hdr.e_type,
                   ET_DYN);
        }

        if (outfile != NULL) {
            ASSERT(!dry_run);
            struct stat st;
            FAILIF(fstat (elf_fd, &st) != 0,
                   "Cannot stat input file %s: %s (%d)!\n",
                   infile, strerror(errno), errno);
            newelf_fd = open (outfile, O_RDWR | O_CREAT | O_TRUNC,
                    st.st_mode & ACCESSPERMS);
            FAILIF(newelf_fd < 0, "Cannot create file %s: %s (%d)!\n",
                   outfile, strerror(errno), errno);
            INFO("Output file is [%s].\n", outfile);
            newelf = elf_begin(newelf_fd, ELF_C_WRITE_MMAP, NULL);
        } else {
            INFO("Modifying [%s] in-place.\n", infile);
            newelf = elf_clone(elf, ELF_C_EMPTY);
        }

        symfilter_t symfilter;

        symfilter.symbols_to_keep = NULL;
        symfilter.num_symbols_to_keep = 0;
        if (symsfile_name) {
            /* Make sure that the file is not empty. */
            struct stat s;
            FAILIF(stat(symsfile_name, &s) < 0,
                   "Cannot stat file %s.\n", symsfile_name);
            if (s.st_size) {
                INFO("Building symbol filter.\n");
                build_symfilter(symsfile_name, elf, &symfilter, s.st_size);
            }
            else INFO("Not building symbol filter, filter file is empty.\n");
        }
#ifdef SUPPORT_ANDROID_PRELINK_TAGS
        int prelinked = 0;
        int elf_little; /* valid if prelinked != 0 */
        long prelink_addr; /* valid if prelinked != 0 */
#endif
        clone_elf(elf, newelf,
                  infile, outfile,
                  symfilter.symbols_to_keep,
                  symfilter.num_symbols_to_keep,
                  shady
#ifdef SUPPORT_ANDROID_PRELINK_TAGS
                  , &prelinked,
                  &elf_little,
                  &prelink_addr
#endif
                  ,
                  true, /* rebuild the section-header-strings table */
                  strip_debug,
                  dry_run);

        if (symsfile_name && symfilter.symbols_to_keep != NULL) {
            destroy_symfilter(&symfilter);
        }

        if (outfile != NULL) INFO("Closing %s...\n", outfile);
        FAILIF_LIBELF(elf_end (newelf) != 0, elf_end);
        FAILIF(newelf_fd >= 0 && close(newelf_fd) < 0,
               "Could not close file %s: %s (%d)!\n",
               outfile, strerror(errno), errno);

        INFO("Closing %s...\n", infile);
        FAILIF_LIBELF(elf_end(elf), elf_end);
        FAILIF(close(elf_fd) < 0, "Could not close file %s: %s (%d)!\n",
               infile, strerror(errno), errno);

#ifdef SUPPORT_ANDROID_PRELINK_TAGS
        if (prelinked) {
            INFO("File is prelinked, putting prelink TAG back in place.\n");
            setup_prelink_info(outfile != NULL ? outfile : infile,
                               elf_little,
                               prelink_addr);
        }
#endif
    }

    FREEIF(outfile);
    return 0;
}

static void print_dynamic_symbols(Elf *elf, const char *file)
{
    Elf_Scn *scn = NULL;
    GElf_Shdr shdr;

    GElf_Ehdr ehdr;
    FAILIF_LIBELF(0 == gelf_getehdr(elf, &ehdr), gelf_getehdr);
    while ((scn = elf_nextscn (elf, scn)) != NULL) {
        FAILIF_LIBELF(NULL == gelf_getshdr(scn, &shdr), gelf_getshdr);
        if (SHT_DYNSYM == shdr.sh_type) {
            /* This failure is too restrictive.  There is no reason why
               the symbol table couldn't be called something else, but
               there is a standard name, and chances are that if we don't
               see it, there's something wrong.
            */
            size_t shstrndx;
            FAILIF_LIBELF(elf_getshstrndx(elf, &shstrndx) < 0,
                          elf_getshstrndx);
            /* Now print the symbols. */
            {
                Elf_Data *symdata;
                size_t elsize;
                symdata = elf_getdata (scn, NULL); /* get the symbol data */
                FAILIF_LIBELF(NULL == symdata, elf_getdata);
                /* Get the number of section.  We need to compare agains this
                   value for symbols that have special info in their section
                   references */
                size_t shnum;
                FAILIF_LIBELF(elf_getshnum (elf, &shnum) < 0, elf_getshnum);
                /* Retrieve the size of a symbol entry */
                elsize = gelf_fsize(elf, ELF_T_SYM, 1, ehdr.e_version);

                size_t index;
                for (index = 0; index < symdata->d_size / elsize; index++) {
                    GElf_Sym sym_mem;
                    GElf_Sym *sym;
                    /* Get the symbol. */
                    sym = gelf_getsymshndx (symdata, NULL,
                                            index, &sym_mem, NULL);
                    FAILIF_LIBELF(sym == NULL, gelf_getsymshndx);
                    /* Print the symbol. */
                    char bind = '?';
                    switch(ELF32_ST_BIND(sym->st_info))
                    {
                    case STB_LOCAL: bind = 'l'; break;
                    case STB_GLOBAL: bind = 'g'; break;
                    case STB_WEAK: bind = 'w'; break;
                    default: break;
                    }
                    char type = '?';
                    switch(ELF32_ST_TYPE(sym->st_info))
                    {
                    case STT_NOTYPE: /* Symbol type is unspecified */
                        type = '?';
                        break;
                    case STT_OBJECT: /* Symbol is a data object */
                        type = 'o';
                        break;
                    case STT_FUNC: /* Symbol is a code object */
                        type = 'f';
                        break;
                    case STT_SECTION:/* Symbol associated with a section */
                        type = 's';
                        break;
                    case STT_FILE: /* Symbol's name is file name */
                        type = 'f';
                        break;
                    case STT_COMMON: /* Symbol is a common data object */
                        type = 'c';
                        break;
                    case STT_TLS: /* Symbol is thread-local data object*/
                        type = 't';
                        break;
                    }
                    {
                        int till_lineno;
                        int lineno;
                        const char *section_name = "(unknown)";
                        FAILIF(sym->st_shndx == SHN_XINDEX,
                               "Can't handle symbol's st_shndx == SHN_XINDEX!\n");
                        if (sym->st_shndx != SHN_UNDEF &&
                            sym->st_shndx < shnum) {
                            Elf_Scn *symscn = elf_getscn(elf, sym->st_shndx);
                            FAILIF_LIBELF(NULL == symscn, elf_getscn);
                            GElf_Shdr symscn_shdr;
                            FAILIF_LIBELF(NULL == gelf_getshdr(symscn,
                                                               &symscn_shdr),
                                          gelf_getshdr);
                            section_name = elf_strptr(elf, shstrndx,
                                                      symscn_shdr.sh_name);
                        }
                        else if (sym->st_shndx == SHN_ABS) {
                            section_name = "SHN_ABS";
                        }
                        else if (sym->st_shndx == SHN_COMMON) {
                            section_name = "SHN_COMMON";
                        }
                        else if (sym->st_shndx == SHN_UNDEF) {
                            section_name = "(undefined)";
                        }
                        /* value size binding type section symname */
                        PRINT("%-15s %8zd: %08llx %08llx %c%c %5d %n%s%n",
                              file,
                              index,
                              sym->st_value, sym->st_size, bind, type,
                              sym->st_shndx,
                              &till_lineno,
                              section_name,
                              &lineno);
                        lineno -= till_lineno;
                        /* Create padding for section names of 15 chars.
                           This limit is somewhat arbitratry. */
                        while (lineno++ < 15) PRINT(" ");
                        PRINT("(%d) %s\n",
                              sym->st_name,
                              elf_strptr(elf, shdr.sh_link, sym->st_name));
                    }
                }
            }
        } /* if (shdr.sh_type = SHT_DYNSYM) */
    } /* while ((scn = elf_nextscn (elf, scn)) != NULL) */
}