#define _GNU_SOURCE
#include <assert.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <dlfcn.h>
#include <limits.h>
#include <link.h>
#include "relf.h"
#include "liballocs_private.h"
#include "pageindex.h"
#include "raw-syscalls-defs.h"

/* static */ _Bool __static_segment_allocator_trying_to_initialize __attribute__((visibility("hidden")));
#define trying_to_initialize __static_segment_allocator_trying_to_initialize
static _Bool initialized;

void ( __attribute__((constructor(102))) __static_segment_allocator_init)(void)
{
	if (!initialized && !trying_to_initialize)
	{
		/* Initialize what we depend on. This might do nothing if we
		 * are already in the middle of doing this init. How do we
		 * ensure that we always come back here to do *our* init?
		 * Firstly, the static file allocator calls *us* when it's done.
		 * Secondly, we don't set our "trying" flag until *it's* inited,
		 * so that call will not give up saying "doing it". */
		__static_file_allocator_init();
		trying_to_initialize = 1;
		/* That's all. */
		initialized = 1;
		trying_to_initialize = 0;
	}
}
/* In the most natural/direct model, children of the segment
 * may be sections or symbols, i.e. some symbols are not in
 * any section, and some ELF files do not have any section
 * headers at all. How common is this? How can we regularise
 * this? Rather than create dummy sections, we have only one
 * bitmap per segment. */
void __static_segment_setup_metavector(
		struct allocs_file_metadata *afile,
		unsigned phndx,
		unsigned loadndx
	)
{
	ElfW(Phdr) *phdr = &afile->m.phdrs[phndx];
	union sym_or_reloc_rec *metavector = NULL;
	size_t metavector_size = 0;
	if (afile->meta_obj_handle)
	{
#define METAVEC_SYM_PREFIX "metavec_0x"
		char buf[sizeof METAVEC_SYM_PREFIX+8]; // 8 bytes + NUL
		snprintf(buf, sizeof buf, METAVEC_SYM_PREFIX "%x", (unsigned) phdr->p_vaddr);
#undef METAVEC_SYM_PREFIX
		void *found = fake_dlsym(afile->meta_obj_handle, buf);
		if (found && found != (void*) -1)
		{
			metavector = found;
			// what about the size?
			ElfW(Sym) *found_sym = gnu_hash_lookup(
				get_gnu_hash(afile->meta_obj_handle),
				get_dynsym(afile->meta_obj_handle),
				get_dynstr(afile->meta_obj_handle),
				buf);
			assert(found_sym);
			metavector_size = found_sym->st_size;
		}
	}
	assert(afile->m.segments[loadndx].phdr_idx == phndx); // librunt has already done it
	afile->m.segments[loadndx].metavector = metavector;
	afile->m.segments[loadndx].metavector_size = metavector_size;
}

void __real___runt_segments_notify_define_segment(struct file_metadata *file, unsigned phndx, unsigned loadndx);
void __static_segment_allocator_notify_define_segment(
	struct file_metadata *file_,
	unsigned phndx,
	unsigned loadndx
)
{
	/* To avoid confusion, don't use "file" in this function
	 * (that's why I called it "file_"). */
	struct allocs_file_metadata *afile = CONTAINER_OF(file_, struct allocs_file_metadata, m);
	/* DON'T check for liballocs's global initializedness here.
	 * Because the only thing we need to initialize is the data segment
	 * bigalloc end, we can only become fully initialized once our
	 * depended-on allocators (static file, mmap) are fully initialized.
	 * But the file allocator calls *us* during *its* initialization.
	 * So this function has to work even if we're not fully initialized yet. */
	ElfW(Phdr) *phdr = &afile->m.phdrs[phndx];
	const void *segment_start_addr = (char*) afile->m.l->l_addr + phdr->p_vaddr;
	size_t segment_size = phdr->p_memsz;

	/* librunt does the outcalls to set up segments, so for us to run,
	 * we  need to be a wrapper of the librunt call. We call its __real_
	 * one to do the basics. */
	__real___runt_segments_notify_define_segment(&afile->m, phndx, loadndx);
	/* Now librunt has set up a dummy segment_metadata for us; we mostly
	 * just have to do the bigalloc stuff and fill it in the metavector. */

	struct big_allocation *containing_file = __lookup_bigalloc_from_root(
		segment_start_addr, &__static_file_allocator, NULL);
	if (!containing_file) abort();

	struct big_allocation *b = __liballocs_new_bigalloc(
		(void*) segment_start_addr,
		segment_size,
		(struct meta_info) {
			.what = DATA_PTR,
			.un = {
				opaque_data: { 
					/* HMM. This will often "overflow" the 1-element array. So we
					 * are really trusting the compiler not to know that. */
					.data_ptr = &afile->m.segments[loadndx],
					.free_func = NULL
				}
			}
		},
		containing_file,
		&__static_segment_allocator /* allocated_by */
	);
	/* What's the suballocator? For the executable's data segment,
	 * a malloc will be the suballocator. But sections will be
	 * child bigallocs so that is OK -- we still only have one true
	 * suballocator. FIXME: what if we have syms directly underneath?
	 * Syms may or may not be part of a section... the not-part case
	 * may be tricky with this arrangement. */
	if ((uintptr_t) segment_start_addr == executable_data_segment_start_addr)
	{
		/* Here we rely on both sections and segments always being bigallocs,
		 * so the only suballocator of the data segment is the generic malloc.
		 * Of course the sections may themselves have suballocators (the symbol
		 * allocator). This may be a problem when we start to hang the bitmaps
		 * on places, because we want the bitmaps to be per-segment not
		 * per-section. It might be better to invert this: create a brk bigalloc,
		 * and the malloc becomes the suballocator under there while the
		 * segment is suballocated by the symbols (?). */
		executable_data_segment_bigalloc = b;
		// the data segment always extends as far as the file+mapping do (should be the same)
		assert(b->parent); // the segment's parent is the file
		assert(b->parent->parent); // the parent's parent is the mapping, which includes brk area
		// with the brk area included, we may extend further than the segment
		assert((uintptr_t) b->parent->parent->end >= (uintptr_t) b->parent->end);
		// the end of the segment is the end of the file
		__adjust_bigalloc_end(b, b->parent->end);
		b->suballocator = &__static_symbol_allocator;
	}
	/* Fill in the per-segment info that is stored in the file metadata. */
	__static_segment_setup_metavector(afile, phndx, loadndx);
}
void __wrap___runt_segments_notify_define_segment(struct file_metadata *file, unsigned phndx, unsigned loadndx) __attribute__((alias("__static_segment_allocator_notify_define_segment")));

void __real___runt_segments_notify_destroy_segment(ElfW(Phdr) *phdr);
void __static_segment_allocator_notify_destroy_segment(ElfW(Phdr) *phdr)
{
	/* I think we don't have to do anything -- the usual bigalloc
	 * teardown also tears down children and frees their metadata.
	 * But call the runt version for good measure. */
	__real___runt_segments_notify_destroy_segment(phdr);
}
void __wrap___runt_segments_notify_destroy_segment(ElfW(Phdr) *phdr) __attribute__((alias("__static_segment_allocator_notify_destroy_segment")));

static liballocs_err_t get_info(void *obj, struct big_allocation *b,
	struct uniqtype **out_type, void **out_base,
	unsigned long *out_size, const void **out_site)
{
	/* The only allocation we have is the bigalloc, so it's easy. */
	assert(b);
	if (out_type) *out_type = pointer_to___uniqtype____uninterpreted_byte;
	if (out_base) *out_base = b->begin;
	if (out_site) *out_site =
			((struct file_metadata *) (b->parent->meta.un.opaque_data.data_ptr))
				->load_site;
	if (out_size) *out_size = (char*) b->end - (char*) b->begin;
	return NULL;
}

DEFAULT_GET_TYPE

struct allocator __static_segment_allocator = {
	.name = "static-segment",
	.is_cacheable = 1,
	.get_info = get_info,
	.get_type = get_type
};
