#include "jemalloc/internal/jemalloc_preamble.h"
#include "jemalloc/internal/jemalloc_internal_includes.h"

#include "jemalloc/internal/malloc_io.h"
#include "jemalloc/internal/prof_sys.h"

#if defined(__linux__) && defined(JEMALLOC_HAVE_GETTID)

#	include <errno.h>
#	include <fcntl.h>
#	include <stdio.h>
#	include <stdlib.h> // strtoul
#	include <string.h>
#	include <unistd.h>

/*
 * Converts a string representing a hexadecimal number to an unsigned long long
 * integer. Functionally equivalent to strtoull() (for base 16) but faster for
 * that case.
 *
 * @param nptr Pointer to the string to be converted.
 * @param endptr Pointer to a pointer to character, which will be set to the
 * character in `nptr` where parsing stopped. Can be NULL.
 * @return The converted unsigned long long integer value.
 */
static inline unsigned long long int
strtoull_hex(const char *nptr, char **endptr) {
	unsigned long long int val = 0;
	int                    ii = 0;
	for (; ii < 16; ++ii) {
		char c = nptr[ii];
		if (c >= '0' && c <= '9') {
			val = (val << 4) + (c - '0');
		} else if (c >= 'a' && c <= 'f') {
			val = (val << 4) + (c - 'a' + 10);
		} else {
			break;
		}
	}
	if (endptr) {
		*endptr = (char *)(nptr + ii);
	}
	return val;
}

static int
prof_mapping_containing_addr(uintptr_t addr, const char *maps_path,
    uintptr_t *mm_start, uintptr_t *mm_end) {
	int ret = ENOENT; /* not found */
	*mm_start = *mm_end = 0;

	/*
     * Each line of /proc/<pid>/maps is:
     * <start>-<end> <perms> <offset> <dev> <inode> <pathname>
     *
     * The fields we care about are always within the first 34 characters so
     * as long as `buf` contains the start of a mapping line it can always be
     * parsed.
     */
	static const int kMappingFieldsWidth = 34;

	int     fd = -1;
	char    buf[4096];
	ssize_t remaining = 0; /* actual number of bytes read to buf */
	char   *line = NULL;

	while (1) {
		if (fd < 0) {
			/* case 0: initial open of maps file */
			fd = malloc_open(maps_path, O_RDONLY);
			if (fd < 0) {
				return errno;
			}

			remaining = malloc_read_fd(fd, buf, sizeof(buf));
			if (remaining <= 0) {
				ret = errno;
				break;
			}
			line = buf;
		} else if (line == NULL) {
			/* case 1: no newline found in buf */
			remaining = malloc_read_fd(fd, buf, sizeof(buf));
			if (remaining <= 0) {
				ret = errno;
				break;
			}
			line = memchr(buf, '\n', remaining);
			if (line != NULL) {
				line++; /* advance to character after newline */
				remaining -= (line - buf);
			}
		} else if (line != NULL && remaining < kMappingFieldsWidth) {
			/*
             * case 2: found newline but insufficient characters remaining in
             * buf
             */
			memcpy(buf, line,
			    remaining); /* copy remaining characters to start of buf */
			line = buf;

			size_t count = malloc_read_fd(
			    fd, buf + remaining, sizeof(buf) - remaining);
			if (count <= 0) {
				ret = errno;
				break;
			}

			remaining +=
			    count; /* actual number of bytes read to buf */
		} else {
			/* case 3: found newline and sufficient characters to parse */

			/* parse <start>-<end> */
			char     *tmp = line;
			uintptr_t start_addr = (uintptr_t)strtoull_hex(
			    tmp, &tmp);
			if (addr >= start_addr) {
				tmp++; /* advance to character after '-' */
				uintptr_t end_addr = (uintptr_t)strtoull_hex(
				    tmp, NULL);
				if (addr < end_addr) {
					*mm_start = start_addr;
					*mm_end = end_addr;
					ret = 0;
					break;
				}
			}

			/* Advance to character after next newline in the current buf. */
			char *prev_line = line;
			line = memchr(line, '\n', remaining);
			if (line != NULL) {
				line++; /* advance to character after newline */
				remaining -= (line - prev_line);
			}
		}
	}

	malloc_close(fd);
	return ret;
}

int
prof_thread_stack_range(uintptr_t fp, uintptr_t *low, uintptr_t *high) {
	/*
     * NOTE: Prior to kernel 4.5 an entry for every thread stack was included in
     * /proc/<pid>/maps as [STACK:<tid>]. Starting with kernel 4.5 only the main
     * thread stack remains as the [stack] mapping. For other thread stacks the
     * mapping is still visible in /proc/<pid>/task/<tid>/maps (though not
     * labeled as [STACK:tid]).
     * https://lists.ubuntu.com/archives/kernel-team/2016-March/074681.html
    */
	char maps_path[64]; // "/proc/<pid>/task/<tid>/maps"
	malloc_snprintf(maps_path, sizeof(maps_path), "/proc/%d/task/%d/maps",
	    getpid(), gettid());
	return prof_mapping_containing_addr(fp, maps_path, low, high);
}

#else

int
prof_thread_stack_range(
    UNUSED uintptr_t addr, uintptr_t *stack_start, uintptr_t *stack_end) {
	*stack_start = *stack_end = 0;
	return ENOENT;
}

#endif // __linux__
