#include <libelf.h>
#include <stdbool.h>
#include <string.h>
#include <sys/param.h>

#include "libelfP.h"
#include "elf-knowledge.h"

#ifndef LIBELFBITS
# define LIBELFBITS 32
#endif



static int
ELFW(default_ehdr,LIBELFBITS) (Elf *elf, ElfW2(LIBELFBITS,Ehdr) *ehdr,
			       size_t shnum, int *change_bop)
{
  /* Always write the magic bytes.  */
  if (memcmp (&ehdr->e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0)
    {
      memcpy (&ehdr->e_ident[EI_MAG0], ELFMAG, SELFMAG);
      elf->state.ELFW(elf,LIBELFBITS).ehdr_flags |= ELF_F_DIRTY;
    }

  /* Always set the file class.  */
  update_if_changed (ehdr->e_ident[EI_CLASS], ELFW(ELFCLASS,LIBELFBITS),
		     elf->state.ELFW(elf,LIBELFBITS).ehdr_flags);

  /* Set the data encoding if necessary.  */
  if (unlikely (ehdr->e_ident[EI_DATA] == ELFDATANONE))
int
internal_function
__elfw2(LIBELFBITS,updatemmap) (Elf *elf, int change_bo, size_t shnum)
{
    bool previous_scn_changed = false;

    /* We need the ELF header several times.  */
    ElfW2(LIBELFBITS,Ehdr) *ehdr = elf->state.ELFW(elf,LIBELFBITS).ehdr;

    /* Write out the ELF header.  */
    if ((elf->state.ELFW(elf,LIBELFBITS).ehdr_flags | elf->flags) & ELF_F_DIRTY)
    {
        /* If the type sizes should be different at some time we have to
        rewrite this code.  */
        assert (sizeof (ElfW2(LIBELFBITS,Ehdr))
                == elf_typesize (LIBELFBITS, ELF_T_EHDR, 1));

        if (unlikely (change_bo))
        {
            /* Today there is only one version of the ELF header.  */
#if EV_NUM != 2
            xfct_t fctp;
            fctp = __elf_xfctstom[__libelf_version - 1][EV_CURRENT - 1][ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_EHDR];
#else
# undef fctp
# define fctp __elf_xfctstom[0][EV_CURRENT - 1][ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_EHDR]
#endif

            /* Do the real work.  */
            (*fctp) ((char *) elf->map_address + elf->start_offset, ehdr,
                     sizeof (ElfW2(LIBELFBITS,Ehdr)), 1);
        }
        else if (elf->map_address + elf->start_offset != ehdr)
            memcpy (elf->map_address + elf->start_offset, ehdr,
                    sizeof (ElfW2(LIBELFBITS,Ehdr)));

        elf->state.ELFW(elf,LIBELFBITS).ehdr_flags &= ~ELF_F_DIRTY;

        /* We start writing sections after the ELF header only if there is
        no program header.  */
        previous_scn_changed = elf->state.ELFW(elf,LIBELFBITS).phdr == NULL;
    }

    size_t phnum;
    if (unlikely (__elf_getphdrnum_rdlock (elf, &phnum) != 0))
        return -1;

    /* Write out the program header table.  */
    if (elf->state.ELFW(elf,LIBELFBITS).phdr != NULL
            && ((elf->state.ELFW(elf,LIBELFBITS).phdr_flags | elf->flags)
                & ELF_F_DIRTY))
    {
        /* If the type sizes should be different at some time we have to
        rewrite this code.  */
        assert (sizeof (ElfW2(LIBELFBITS,Phdr))
                == elf_typesize (LIBELFBITS, ELF_T_PHDR, 1));

        /* Maybe the user wants a gap between the ELF header and the program
        header.  */
        if (ehdr->e_phoff > ehdr->e_ehsize)
            memset (elf->map_address + elf->start_offset + ehdr->e_ehsize,
                    __libelf_fill_byte, ehdr->e_phoff - ehdr->e_ehsize);

        if (unlikely (change_bo))
        {
            /* Today there is only one version of the ELF header.  */
#if EV_NUM != 2
            xfct_t fctp;
            fctp = __elf_xfctstom[__libelf_version - 1][EV_CURRENT - 1][ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_PHDR];
#else
# undef fctp
# define fctp __elf_xfctstom[0][EV_CURRENT - 1][ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_PHDR]
#endif

            /* Do the real work.  */
            (*fctp) (elf->map_address + elf->start_offset + ehdr->e_phoff,
                     elf->state.ELFW(elf,LIBELFBITS).phdr,
                     sizeof (ElfW2(LIBELFBITS,Phdr)) * phnum, 1);
        }
        else
            memcpy (elf->map_address + elf->start_offset + ehdr->e_phoff,
                    elf->state.ELFW(elf,LIBELFBITS).phdr,
                    sizeof (ElfW2(LIBELFBITS,Phdr)) * phnum);

        elf->state.ELFW(elf,LIBELFBITS).phdr_flags &= ~ELF_F_DIRTY;

        /* We modified the program header.  Maybe this created a gap so
        we have to write fill bytes, if necessary.  */
        previous_scn_changed = true;
    }

    /* From now on we have to keep track of the last position to eventually
       fill the gaps with the prescribed fill byte.  */
    char *last_position = ((char *) elf->map_address + elf->start_offset
                           + MAX (elf_typesize (LIBELFBITS, ELF_T_EHDR, 1),
                                  ehdr->e_phoff)
                           + elf_typesize (LIBELFBITS, ELF_T_PHDR, phnum));

    /* Write all the sections.  Well, only those which are modified.  */
    if (shnum > 0)
    {
        if (unlikely (shnum > SIZE_MAX / sizeof (Elf_Scn *)))
            return 1;

        Elf_ScnList *list = &elf->state.ELFW(elf,LIBELFBITS).scns;
        Elf_Scn **scns = (Elf_Scn **) malloc (shnum * sizeof (Elf_Scn *));
        if (unlikely (scns == NULL))
        {
            __libelf_seterrno (ELF_E_NOMEM);
            return -1;
        }
        char *const shdr_start = ((char *) elf->map_address + elf->start_offset
                                  + ehdr->e_shoff);
        char *const shdr_end = shdr_start + ehdr->e_shnum * ehdr->e_shentsize;

#if EV_NUM != 2
        xfct_t shdr_fctp = __elf_xfctstom[__libelf_version - 1][EV_CURRENT - 1][ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_SHDR];
#else
# undef shdr_fctp
# define shdr_fctp __elf_xfctstom[0][EV_CURRENT - 1][ELFW(ELFCLASS, LIBELFBITS) - 1][ELF_T_SHDR]
#endif
#define shdr_dest ((ElfW2(LIBELFBITS,Shdr) *) shdr_start)

        /* Get all sections into the array and sort them.  */
        sort_sections (scns, list);

        /* We possibly have to copy the section header data because moving
        the sections might overwrite the data.  */
        for (size_t cnt = 0; cnt < shnum; ++cnt)
        {
            Elf_Scn *scn = scns[cnt];

            if (!elf->state.ELFW(elf,LIBELFBITS).shdr_malloced
                    && (scn->shdr_flags & ELF_F_MALLOCED) == 0
                    && scn->shdr.ELFW(e,LIBELFBITS) != &shdr_dest[scn->index])
            {
                assert ((char *) elf->map_address + elf->start_offset
                        < (char *) scn->shdr.ELFW(e,LIBELFBITS));
                assert ((char *) scn->shdr.ELFW(e,LIBELFBITS)
                        < ((char *) elf->map_address + elf->start_offset
                           + elf->maximum_size));

                void *p = malloc (sizeof (ElfW2(LIBELFBITS,Shdr)));
                if (unlikely (p == NULL))
                {
                    __libelf_seterrno (ELF_E_NOMEM);
                    return -1;
                }
                scn->shdr.ELFW(e,LIBELFBITS)
                    = memcpy (p, scn->shdr.ELFW(e,LIBELFBITS),
                              sizeof (ElfW2(LIBELFBITS,Shdr)));
            }

            /* If the file is mmaped and the original position of the
               section in the file is lower than the new position we
               need to save the section content since otherwise it is
               overwritten before it can be copied.  If there are
               multiple data segments in the list only the first can be
               from the file.  */
            if (((char *) elf->map_address + elf->start_offset
                    <= (char  *) scn->data_list.data.d.d_buf)
                    && ((char *) scn->data_list.data.d.d_buf
                        < ((char *) elf->map_address + elf->start_offset
                           + elf->maximum_size))
                    && (((char *) elf->map_address + elf->start_offset
                         + scn->shdr.ELFW(e,LIBELFBITS)->sh_offset)
                        > (char *) scn->data_list.data.d.d_buf))
            {
                void *p = malloc (scn->data_list.data.d.d_size);
                if (unlikely (p == NULL))
                {
                    __libelf_seterrno (ELF_E_NOMEM);
                    return -1;
                }
                scn->data_list.data.d.d_buf = scn->data_base
                                              = memcpy (p, scn->data_list.data.d.d_buf,
                                                        scn->data_list.data.d.d_size);
            }
        }

        /* Iterate over all the section in the order in which they
        appear in the output file.  */
        for (size_t cnt = 0; cnt < shnum; ++cnt)
        {
            Elf_Scn *scn = scns[cnt];
            if (scn->index == 0)
            {
                /* The dummy section header entry.  It should not be
                possible to mark this "section" as dirty.  */
                assert ((scn->flags & ELF_F_DIRTY) == 0);
                continue;
            }

            ElfW2(LIBELFBITS,Shdr) *shdr = scn->shdr.ELFW(e,LIBELFBITS);
            if (shdr->sh_type == SHT_NOBITS)
                goto next;

            char *scn_start = ((char *) elf->map_address
                               + elf->start_offset + shdr->sh_offset);
            Elf_Data_List *dl = &scn->data_list;
            bool scn_changed = false;

            void fill_mmap (size_t offset)
            {
                size_t written = 0;

                if (last_position < shdr_start)
                {
                    written = MIN (scn_start + offset - last_position,
                                   shdr_start - last_position);

                    memset (last_position, __libelf_fill_byte, written);
                }

                if (last_position + written != scn_start + offset
                        && shdr_end < scn_start + offset)
                {
                    char *fill_start = MAX (shdr_end, scn_start);
                    memset (fill_start, __libelf_fill_byte,
                            scn_start + offset - fill_start);
                }
            }

            if (scn->data_list_rear != NULL)
                do
                {
                    assert (dl->data.d.d_off >= 0);
                    assert ((GElf_Off) dl->data.d.d_off <= shdr->sh_size);
                    assert (dl->data.d.d_size <= (shdr->sh_size
                                                  - (GElf_Off) dl->data.d.d_off));

                    /* If there is a gap, fill it.  */
                    if (scn_start + dl->data.d.d_off > last_position
                            && (dl->data.d.d_off == 0
                                || ((scn->flags | dl->flags | elf->flags)
                                    & ELF_F_DIRTY) != 0))
                    {
                        fill_mmap (dl->data.d.d_off);
                        last_position = scn_start + dl->data.d.d_off;
                    }

                    if ((scn->flags | dl->flags | elf->flags) & ELF_F_DIRTY)
                    {
                        /* Let it go backward if the sections use a bogus
                           layout with overlaps.  We'll overwrite the stupid
                           user's section data with the latest one, rather than
                           crashing.  */

                        last_position = scn_start + dl->data.d.d_off;

                        if (unlikely (change_bo))
                        {
#if EV_NUM != 2
                            xfct_t fctp;
                            fctp = __elf_xfctstom[__libelf_version - 1][dl->data.d.d_version - 1][ELFW(ELFCLASS, LIBELFBITS) - 1][dl->data.d.d_type];
#else
# undef fctp
# define fctp __elf_xfctstom[0][EV_CURRENT - 1][ELFW(ELFCLASS, LIBELFBITS) - 1][dl->data.d.d_type]
#endif

                            /* Do the real work.  */
                            (*fctp) (last_position, dl->data.d.d_buf,
                                     dl->data.d.d_size, 1);

                            last_position += dl->data.d.d_size;
                        }
                        else if (dl->data.d.d_size != 0)
                            last_position = mempcpy (last_position,
                                                     dl->data.d.d_buf,
                                                     dl->data.d.d_size);

                        scn_changed = true;
                    }
                    else
                        last_position += dl->data.d.d_size;

                    assert (scn_start + dl->data.d.d_off + dl->data.d.d_size
                            == last_position);

                    dl->flags &= ~ELF_F_DIRTY;

                    dl = dl->next;
                }
                while (dl != NULL);
            else
            {
                /* If the previous section (or the ELF/program
                header) changed we might have to fill the gap.  */
                if (scn_start > last_position && previous_scn_changed)
                    fill_mmap (0);

                /* We have to trust the existing section header information.  */
                last_position = scn_start + shdr->sh_size;
            }


            previous_scn_changed = scn_changed;
next:
            scn->flags &= ~ELF_F_DIRTY;
        }

        /* Fill the gap between last section and section header table if
        necessary.  */
        if ((elf->flags & ELF_F_DIRTY)
                && last_position < ((char *) elf->map_address + elf->start_offset
                                    + ehdr->e_shoff))
            memset (last_position, __libelf_fill_byte,
                    (char *) elf->map_address + elf->start_offset + ehdr->e_shoff
                    - last_position);

        /* Write the section header table entry if necessary.  */
        for (size_t cnt = 0; cnt < shnum; ++cnt)
        {
            Elf_Scn *scn = scns[cnt];

            if ((scn->shdr_flags | elf->flags) & ELF_F_DIRTY)
            {
                if (unlikely (change_bo))
                    (*shdr_fctp) (&shdr_dest[scn->index],
                                  scn->shdr.ELFW(e,LIBELFBITS),
                                  sizeof (ElfW2(LIBELFBITS,Shdr)), 1);
                else
                    memcpy (&shdr_dest[scn->index],
                            scn->shdr.ELFW(e,LIBELFBITS),
                            sizeof (ElfW2(LIBELFBITS,Shdr)));

                /* If we previously made a copy of the section header
                entry we now have to adjust the pointer again so
                 point to new place in the mapping.  */
                if (!elf->state.ELFW(elf,LIBELFBITS).shdr_malloced
                        && (scn->shdr_flags & ELF_F_MALLOCED) == 0
                        && scn->shdr.ELFW(e,LIBELFBITS) != &shdr_dest[scn->index])
                {
                    free (scn->shdr.ELFW(e,LIBELFBITS));
                    scn->shdr.ELFW(e,LIBELFBITS) = &shdr_dest[scn->index];
                }

                scn->shdr_flags &= ~ELF_F_DIRTY;
            }
        }
        free (scns);
    }