#ifndef __TOOLS_LINUX_SLAB_H
#define __TOOLS_LINUX_SLAB_H

#include <malloc.h>
#include <stdlib.h>
#include <string.h>

#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/overflow.h>
#include <linux/page.h>
#include <linux/shrinker.h>
#include <linux/types.h>

#include <stdlib.h>
#include <sys/mman.h>

#define alloc_hooks(_do, ...)		_do

#define ARCH_KMALLOC_MINALIGN		16
#define ARCH_SLAB_MINALIGN		16
#define KMALLOC_MAX_SIZE		SIZE_MAX

#define MAX_PAGE_ORDER			10

static inline size_t kmalloc_size_roundup(size_t s)
{
	return roundup_pow_of_two(s);
}

static inline void *kmalloc_noprof(size_t size, gfp_t flags)
{
	unsigned i;
	void *p;

	for (i = 0; i < 10; i++) {
		if (size) {
			size_t alignment = min_t(size_t, PAGE_SIZE,
						 rounddown_pow_of_two(size));
			alignment = max(sizeof(void *), alignment);
			if (posix_memalign(&p, alignment, size))
				p = NULL;
		} else {
			p = malloc(0);
		}

		if (p) {
			if (flags & __GFP_ZERO)
				memset(p, 0, size);
			break;
		}

		run_shrinkers(flags, true);
	}

	return p;
}
#define kmalloc		kmalloc_noprof

static inline void *krealloc(void *old, size_t size, gfp_t flags)
{
	void *new;

	new = kmalloc(size, flags);
	if (!new)
		return NULL;

	if (flags & __GFP_ZERO)
		memset(new, 0, size);

	if (old) {
		memcpy(new, old,
		       min(malloc_usable_size(old),
			   malloc_usable_size(new)));
		free(old);
	}

	return new;
}

static inline void *krealloc_array(void *p, size_t new_n, size_t new_size, gfp_t flags)
{
	size_t bytes;

	if (unlikely(check_mul_overflow(new_n, new_size, &bytes)))
		return NULL;

	return krealloc(p, bytes, flags);
}

#define kzalloc(size, flags)		kmalloc(size, flags|__GFP_ZERO)

static inline void *kmalloc_array(size_t n, size_t size, gfp_t flags)
{
	size_t bytes;

	if (unlikely(check_mul_overflow(n, size, &bytes)))
		return NULL;
	return kmalloc(bytes, flags);
}

#define kvmalloc_array(n, size, flags)					\
	((size) != 0 && (n) > SIZE_MAX / (size)				\
	 ? NULL : kmalloc((n) * (size), flags))

#define kvcalloc(n, size, flags)	kvmalloc_array(n, size, flags|__GFP_ZERO)

#define kvmalloc_array_noprof(...)	kvmalloc_array(__VA_ARGS__)

#define kcalloc(n, size, flags)		kmalloc_array(n, size, flags|__GFP_ZERO)

#define kfree(p)			free((void *) p)
#define kzfree(p)			free((void *) p)

DEFINE_FREE(kfree, void *, if (!IS_ERR_OR_NULL(_T)) kfree(_T))

#define kvmalloc(size, flags)		kmalloc(size, flags)
#define kvmalloc_node_align_noprof(size, align, flags, node)	kmalloc(size, flags)
#define kvmalloc_noprof(size, flags)	kmalloc(size, flags)
#define kvzalloc(size, flags)		kzalloc(size, flags)
#define kvfree(p)			kfree(p)

DEFINE_FREE(kvfree, void *, if (!IS_ERR_OR_NULL(_T)) kvfree(_T))

static inline struct page *alloc_pages_noprof(gfp_t flags, unsigned int order)
{
	size_t size = PAGE_SIZE << order;
	unsigned i;
	void *p;

	for (i = 0; i < 10; i++) {
		p = aligned_alloc(PAGE_SIZE, size);

		if (p) {
			if (flags & __GFP_ZERO)
				memset(p, 0, size);
			break;
		}

		run_shrinkers(flags, true);
	}

	return p;
}
#define alloc_pages			alloc_pages_noprof

#define alloc_page(gfp)			alloc_pages(gfp, 0)

#define _get_free_pages(gfp, order)	((unsigned long) alloc_pages(gfp, order))
#define __get_free_pages(gfp, order)	((unsigned long) alloc_pages(gfp, order))
#define get_free_pages_noprof(gfp, order)				\
					((unsigned long) alloc_pages(gfp, order))
#define __get_free_page(gfp)		__get_free_pages(gfp, 0)

#define __free_pages(page, order)			\
do {							\
	(void) order;					\
	free(page);					\
} while (0)

#define free_pages(addr, order)				\
do {							\
	(void) order;					\
	free((void *) (addr));				\
} while (0)

#define __free_page(page) __free_pages((page), 0)
#define free_page(addr) free_pages((addr), 0)

#define VM_IOREMAP		0x00000001	/* ioremap() and friends */
#define VM_ALLOC		0x00000002	/* vmalloc() */
#define VM_MAP			0x00000004	/* vmap()ed pages */
#define VM_USERMAP		0x00000008	/* suitable for remap_vmalloc_range */
#define VM_UNINITIALIZED	0x00000020	/* vm_struct is not fully initialized */
#define VM_NO_GUARD		0x00000040      /* don't add guard page */
#define VM_KASAN		0x00000080      /* has allocated kasan shadow memory */

static inline void vunmap(const void *addr) {}

static inline void *vmap(struct page **pages, unsigned int count,
			 unsigned long flags, unsigned prot)
{
	return NULL;
}

#define is_vmalloc_addr(page)		0

#define vmalloc_to_page(addr)		((struct page *) (addr))

static inline void *kmemdup(const void *src, size_t len, gfp_t gfp)
{
	void *p;

	p = kmalloc(len, gfp);
	if (p)
		memcpy(p, src, len);
	return p;
}

struct kmem_cache {
	size_t		    obj_size;
};

static inline void *kmem_cache_alloc(struct kmem_cache *c, gfp_t gfp)
{
	return kmalloc(c->obj_size, gfp);
}

static inline void *kmem_cache_zalloc(struct kmem_cache *c, gfp_t gfp)
{
	return kzalloc(c->obj_size, gfp);
}

static inline void kmem_cache_free(struct kmem_cache *c, void *p)
{
	kfree(p);
}

static inline void kmem_cache_destroy(struct kmem_cache *p)
{
	kfree(p);
}

static inline struct kmem_cache *kmem_cache_create(size_t obj_size)
{
	struct kmem_cache *p = kmalloc(sizeof(*p), GFP_KERNEL);
	if (!p)
		return NULL;

	p->obj_size = obj_size;
	return p;
}

#define KMEM_CACHE(_struct, _flags)	kmem_cache_create(sizeof(struct _struct))

#define PAGE_KERNEL		0
#define PAGE_KERNEL_EXEC	1

#define vfree(p)		free(p)

static inline void *__vmalloc_noprof(unsigned long size, gfp_t flags)
{
	unsigned i;
	void *p;

	size = round_up(size, PAGE_SIZE);

	for (i = 0; i < 10; i++) {
		p = aligned_alloc(PAGE_SIZE, size);

		if (p) {
			if (flags & __GFP_ZERO)
				memset(p, 0, size);
			break;
		}

		run_shrinkers(flags, true);
	}

	return p;
}
#define __vmalloc __vmalloc_noprof

static inline void *vmalloc_exec(unsigned long size, gfp_t gfp_mask)
{
	void *p;

	p = __vmalloc(size, gfp_mask);
	if (!p)
		return NULL;

	if (mprotect(p, size, PROT_READ|PROT_WRITE|PROT_EXEC)) {
		vfree(p);
		return NULL;
	}

	return p;
}

static inline void *vmalloc(unsigned long size)
{
	return __vmalloc(size, GFP_KERNEL);
}

#define vmalloc_noprof(...)	vmalloc(__VA_ARGS__)

static inline void *vzalloc(unsigned long size)
{
	return __vmalloc(size, GFP_KERNEL|__GFP_ZERO);
}

#endif /* __TOOLS_LINUX_SLAB_H */
