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

#include "jemalloc/internal/buf_writer.h"
#include "jemalloc/internal/ctl.h"
#include "jemalloc/internal/malloc_io.h"
#include "jemalloc/internal/prof_data.h"
#include "jemalloc/internal/prof_sys.h"

#ifdef JEMALLOC_PROF_LIBUNWIND
#	define UNW_LOCAL_ONLY
#	include <libunwind.h>
#endif

#ifdef JEMALLOC_PROF_LIBGCC
/*
 * We have a circular dependency -- jemalloc_internal.h tells us if we should
 * use libgcc's unwinding functionality, but after we've included that, we've
 * already hooked _Unwind_Backtrace.  We'll temporarily disable hooking.
 */
#	undef _Unwind_Backtrace
#	include <unwind.h>
#	define _Unwind_Backtrace                                              \
		JEMALLOC_TEST_HOOK(_Unwind_Backtrace, test_hooks_libc_hook)
#endif

#ifdef JEMALLOC_PROF_FRAME_POINTER
// execinfo backtrace() as fallback unwinder
#	include <execinfo.h>
#endif

/******************************************************************************/

malloc_mutex_t prof_dump_filename_mtx;

static uint64_t prof_dump_seq;
static uint64_t prof_dump_iseq;
static uint64_t prof_dump_mseq;
static uint64_t prof_dump_useq;

static char *prof_prefix = NULL;

/* The fallback allocator profiling functionality will use. */
base_t *prof_base;

void
bt_init(prof_bt_t *bt, void **vec) {
	cassert(config_prof);

	bt->vec = vec;
	bt->len = 0;
}

#ifdef JEMALLOC_PROF_LIBUNWIND
static void
prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
	int nframes;

	cassert(config_prof);
	assert(*len == 0);
	assert(vec != NULL);
	assert(max_len <= PROF_BT_MAX_LIMIT);

	nframes = unw_backtrace(vec, max_len);
	if (nframes <= 0) {
		return;
	}
	*len = nframes;
}
#elif (defined(JEMALLOC_PROF_LIBGCC))
static _Unwind_Reason_Code
prof_unwind_init_callback(struct _Unwind_Context *context, void *arg) {
	cassert(config_prof);

	return _URC_NO_REASON;
}

static _Unwind_Reason_Code
prof_unwind_callback(struct _Unwind_Context *context, void *arg) {
	prof_unwind_data_t *data = (prof_unwind_data_t *)arg;
	void               *ip;

	cassert(config_prof);

	ip = (void *)_Unwind_GetIP(context);
	if (ip == NULL) {
		return _URC_END_OF_STACK;
	}
	data->vec[*data->len] = ip;
	(*data->len)++;
	if (*data->len == data->max) {
		return _URC_END_OF_STACK;
	}

	return _URC_NO_REASON;
}

static void
prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
	prof_unwind_data_t data = {vec, len, max_len};

	cassert(config_prof);
	assert(vec != NULL);
	assert(max_len <= PROF_BT_MAX_LIMIT);

	_Unwind_Backtrace(prof_unwind_callback, &data);
}
#elif (defined(JEMALLOC_PROF_FRAME_POINTER))
JEMALLOC_DIAGNOSTIC_PUSH
JEMALLOC_DIAGNOSTIC_IGNORE_FRAME_ADDRESS

struct stack_range {
	uintptr_t start;
	uintptr_t end;
};

struct thread_unwind_info {
	struct stack_range stack_range;
	bool               fallback;
};
static __thread struct thread_unwind_info unwind_info = {
    .stack_range =
        {
            .start = 0,
            .end = 0,
        },
    .fallback = false,
}; /* thread local */

static void
prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
	/* fp: 		current stack frame pointer
	 *
	 * stack_range:	readable stack memory range for the current thread.
	 *		Used to validate frame addresses during stack unwinding.
	 *		For most threads there is a single valid stack range
	 *		that is fixed at thread creation time.  This may not be
	 *		the case when folly fibers or boost contexts are used.
	 *		In those cases fall back to using execinfo backtrace()
	 *		(DWARF unwind).
	 */

	/* always safe to get the current stack frame address */
	uintptr_t fp = (uintptr_t)__builtin_frame_address(0);

	/* new thread - get the stack range */
	if (!unwind_info.fallback
	    && unwind_info.stack_range.start == unwind_info.stack_range.end) {
		if (prof_thread_stack_range(fp, &unwind_info.stack_range.start,
		        &unwind_info.stack_range.end)
		    != 0) {
			unwind_info.fallback = true;
		} else {
			assert(fp >= unwind_info.stack_range.start
			    && fp < unwind_info.stack_range.end);
		}
	}

	if (unwind_info.fallback) {
		goto label_fallback;
	}

	unsigned ii = 0;
	while (ii < max_len && fp != 0) {
		if (fp < unwind_info.stack_range.start
		    || fp >= unwind_info.stack_range.end) {
			/*
			 * Determining the stack range from procfs can be
			 * relatively expensive especially for programs with
			 * many threads / shared libraries.  If the stack
			 * range has changed, it is likely to change again
			 * in the future (fibers or some other stack
			 * manipulation).  So fall back to backtrace for this
			 * thread.
			 */
			unwind_info.fallback = true;
			goto label_fallback;
		}
		void *ip = ((void **)fp)[1];
		if (ip == 0) {
			break;
		}
		vec[ii++] = ip;
		fp = ((uintptr_t *)fp)[0];
	}
	*len = ii;
	return;

label_fallback:
	/*
	 * Using the backtrace from execinfo.h here.  Note that it may get
	 * redirected to libunwind when a libunwind not built with build-time
	 * flag --disable-weak-backtrace is linked.
	 */
	assert(unwind_info.fallback);
	int nframes = backtrace(vec, max_len);
	if (nframes > 0) {
		*len = nframes;
	} else {
		*len = 0;
	}
}

JEMALLOC_DIAGNOSTIC_POP
#elif (defined(JEMALLOC_PROF_GCC))
JEMALLOC_DIAGNOSTIC_PUSH
JEMALLOC_DIAGNOSTIC_IGNORE_FRAME_ADDRESS
static void
prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
/* The input arg must be a constant for __builtin_return_address. */
#	define BT_FRAME(i)                                                    \
		if ((i) < max_len) {                                           \
			void *p;                                               \
			if (__builtin_frame_address(i) == 0) {                 \
				return;                                        \
			}                                                      \
			p = __builtin_return_address(i);                       \
			if (p == NULL) {                                       \
				return;                                        \
			}                                                      \
			vec[(i)] = p;                                          \
			*len = (i) + 1;                                        \
		} else {                                                       \
			return;                                                \
		}

	cassert(config_prof);
	assert(vec != NULL);
	assert(max_len <= PROF_BT_MAX_LIMIT);

	BT_FRAME(0)
	BT_FRAME(1)
	BT_FRAME(2)
	BT_FRAME(3)
	BT_FRAME(4)
	BT_FRAME(5)
	BT_FRAME(6)
	BT_FRAME(7)
	BT_FRAME(8)
	BT_FRAME(9)

	BT_FRAME(10)
	BT_FRAME(11)
	BT_FRAME(12)
	BT_FRAME(13)
	BT_FRAME(14)
	BT_FRAME(15)
	BT_FRAME(16)
	BT_FRAME(17)
	BT_FRAME(18)
	BT_FRAME(19)

	BT_FRAME(20)
	BT_FRAME(21)
	BT_FRAME(22)
	BT_FRAME(23)
	BT_FRAME(24)
	BT_FRAME(25)
	BT_FRAME(26)
	BT_FRAME(27)
	BT_FRAME(28)
	BT_FRAME(29)

	BT_FRAME(30)
	BT_FRAME(31)
	BT_FRAME(32)
	BT_FRAME(33)
	BT_FRAME(34)
	BT_FRAME(35)
	BT_FRAME(36)
	BT_FRAME(37)
	BT_FRAME(38)
	BT_FRAME(39)

	BT_FRAME(40)
	BT_FRAME(41)
	BT_FRAME(42)
	BT_FRAME(43)
	BT_FRAME(44)
	BT_FRAME(45)
	BT_FRAME(46)
	BT_FRAME(47)
	BT_FRAME(48)
	BT_FRAME(49)

	BT_FRAME(50)
	BT_FRAME(51)
	BT_FRAME(52)
	BT_FRAME(53)
	BT_FRAME(54)
	BT_FRAME(55)
	BT_FRAME(56)
	BT_FRAME(57)
	BT_FRAME(58)
	BT_FRAME(59)

	BT_FRAME(60)
	BT_FRAME(61)
	BT_FRAME(62)
	BT_FRAME(63)
	BT_FRAME(64)
	BT_FRAME(65)
	BT_FRAME(66)
	BT_FRAME(67)
	BT_FRAME(68)
	BT_FRAME(69)

	BT_FRAME(70)
	BT_FRAME(71)
	BT_FRAME(72)
	BT_FRAME(73)
	BT_FRAME(74)
	BT_FRAME(75)
	BT_FRAME(76)
	BT_FRAME(77)
	BT_FRAME(78)
	BT_FRAME(79)

	BT_FRAME(80)
	BT_FRAME(81)
	BT_FRAME(82)
	BT_FRAME(83)
	BT_FRAME(84)
	BT_FRAME(85)
	BT_FRAME(86)
	BT_FRAME(87)
	BT_FRAME(88)
	BT_FRAME(89)

	BT_FRAME(90)
	BT_FRAME(91)
	BT_FRAME(92)
	BT_FRAME(93)
	BT_FRAME(94)
	BT_FRAME(95)
	BT_FRAME(96)
	BT_FRAME(97)
	BT_FRAME(98)
	BT_FRAME(99)

	BT_FRAME(100)
	BT_FRAME(101)
	BT_FRAME(102)
	BT_FRAME(103)
	BT_FRAME(104)
	BT_FRAME(105)
	BT_FRAME(106)
	BT_FRAME(107)
	BT_FRAME(108)
	BT_FRAME(109)

	BT_FRAME(110)
	BT_FRAME(111)
	BT_FRAME(112)
	BT_FRAME(113)
	BT_FRAME(114)
	BT_FRAME(115)
	BT_FRAME(116)
	BT_FRAME(117)
	BT_FRAME(118)
	BT_FRAME(119)

	BT_FRAME(120)
	BT_FRAME(121)
	BT_FRAME(122)
	BT_FRAME(123)
	BT_FRAME(124)
	BT_FRAME(125)
	BT_FRAME(126)
	BT_FRAME(127)
	BT_FRAME(128)
	BT_FRAME(129)

	BT_FRAME(130)
	BT_FRAME(131)
	BT_FRAME(132)
	BT_FRAME(133)
	BT_FRAME(134)
	BT_FRAME(135)
	BT_FRAME(136)
	BT_FRAME(137)
	BT_FRAME(138)
	BT_FRAME(139)

	BT_FRAME(140)
	BT_FRAME(141)
	BT_FRAME(142)
	BT_FRAME(143)
	BT_FRAME(144)
	BT_FRAME(145)
	BT_FRAME(146)
	BT_FRAME(147)
	BT_FRAME(148)
	BT_FRAME(149)

	BT_FRAME(150)
	BT_FRAME(151)
	BT_FRAME(152)
	BT_FRAME(153)
	BT_FRAME(154)
	BT_FRAME(155)
	BT_FRAME(156)
	BT_FRAME(157)
	BT_FRAME(158)
	BT_FRAME(159)

	BT_FRAME(160)
	BT_FRAME(161)
	BT_FRAME(162)
	BT_FRAME(163)
	BT_FRAME(164)
	BT_FRAME(165)
	BT_FRAME(166)
	BT_FRAME(167)
	BT_FRAME(168)
	BT_FRAME(169)

	BT_FRAME(170)
	BT_FRAME(171)
	BT_FRAME(172)
	BT_FRAME(173)
	BT_FRAME(174)
	BT_FRAME(175)
	BT_FRAME(176)
	BT_FRAME(177)
	BT_FRAME(178)
	BT_FRAME(179)

	BT_FRAME(180)
	BT_FRAME(181)
	BT_FRAME(182)
	BT_FRAME(183)
	BT_FRAME(184)
	BT_FRAME(185)
	BT_FRAME(186)
	BT_FRAME(187)
	BT_FRAME(188)
	BT_FRAME(189)

	BT_FRAME(190)
	BT_FRAME(191)
	BT_FRAME(192)
	BT_FRAME(193)
	BT_FRAME(194)
	BT_FRAME(195)
	BT_FRAME(196)
	BT_FRAME(197)
	BT_FRAME(198)
	BT_FRAME(199)

	BT_FRAME(200)
	BT_FRAME(201)
	BT_FRAME(202)
	BT_FRAME(203)
	BT_FRAME(204)
	BT_FRAME(205)
	BT_FRAME(206)
	BT_FRAME(207)
	BT_FRAME(208)
	BT_FRAME(209)

	BT_FRAME(210)
	BT_FRAME(211)
	BT_FRAME(212)
	BT_FRAME(213)
	BT_FRAME(214)
	BT_FRAME(215)
	BT_FRAME(216)
	BT_FRAME(217)
	BT_FRAME(218)
	BT_FRAME(219)

	BT_FRAME(220)
	BT_FRAME(221)
	BT_FRAME(222)
	BT_FRAME(223)
	BT_FRAME(224)
	BT_FRAME(225)
	BT_FRAME(226)
	BT_FRAME(227)
	BT_FRAME(228)
	BT_FRAME(229)

	BT_FRAME(230)
	BT_FRAME(231)
	BT_FRAME(232)
	BT_FRAME(233)
	BT_FRAME(234)
	BT_FRAME(235)
	BT_FRAME(236)
	BT_FRAME(237)
	BT_FRAME(238)
	BT_FRAME(239)

	BT_FRAME(240)
	BT_FRAME(241)
	BT_FRAME(242)
	BT_FRAME(243)
	BT_FRAME(244)
	BT_FRAME(245)
	BT_FRAME(246)
	BT_FRAME(247)
	BT_FRAME(248)
	BT_FRAME(249)

	BT_FRAME(250)
	BT_FRAME(251)
	BT_FRAME(252)
	BT_FRAME(253)
	BT_FRAME(254)
	BT_FRAME(255)
#	undef BT_FRAME
	JEMALLOC_DIAGNOSTIC_POP
}
#else
static void
prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
	cassert(config_prof);
	not_reached();
}
#endif

void
prof_backtrace(tsd_t *tsd, prof_bt_t *bt) {
	cassert(config_prof);
	prof_backtrace_hook_t prof_backtrace_hook = prof_backtrace_hook_get();
	assert(prof_backtrace_hook != NULL);

	pre_reentrancy(tsd, NULL);
	prof_backtrace_hook(bt->vec, &bt->len, opt_prof_bt_max);
	post_reentrancy(tsd);
}

void
prof_hooks_init(void) {
	prof_backtrace_hook_set(&prof_backtrace_impl);
	prof_dump_hook_set(NULL);
	prof_sample_hook_set(NULL);
	prof_sample_free_hook_set(NULL);
}

void
prof_unwind_init(void) {
#ifdef JEMALLOC_PROF_LIBGCC
	/*
	 * Cause the backtracing machinery to allocate its internal
	 * state before enabling profiling.
	 */
	_Unwind_Backtrace(prof_unwind_init_callback, NULL);
#endif
}

static int
prof_sys_thread_name_read_impl(char *buf, size_t limit) {
#if defined(JEMALLOC_HAVE_PTHREAD_GETNAME_NP)
	return pthread_getname_np(pthread_self(), buf, limit);
#elif defined(JEMALLOC_HAVE_PTHREAD_GET_NAME_NP)
	pthread_get_name_np(pthread_self(), buf, limit);
	return 0;
#else
	return ENOSYS;
#endif
}
prof_sys_thread_name_read_t *JET_MUTABLE prof_sys_thread_name_read =
    prof_sys_thread_name_read_impl;

void
prof_sys_thread_name_fetch(tsd_t *tsd) {
	prof_tdata_t *tdata = prof_tdata_get(tsd, true);
	if (tdata == NULL) {
		return;
	}

	if (prof_sys_thread_name_read(
	        tdata->thread_name, PROF_THREAD_NAME_MAX_LEN)
	    != 0) {
		prof_thread_name_clear(tdata);
	}

	tdata->thread_name[PROF_THREAD_NAME_MAX_LEN - 1] = '\0';
}

int
prof_getpid(void) {
#ifdef _WIN32
	return GetCurrentProcessId();
#else
	return getpid();
#endif
}

static long
prof_get_pid_namespace(void) {
	long ret = 0;

#if defined(_WIN32) || defined(__APPLE__)
	// Not supported, do nothing.
#else
	char        buf[PATH_MAX];
	const char *linkname =
#	if defined(__FreeBSD__) || defined(__DragonFly__)
	    "/proc/curproc/ns/pid"
#	else
	    "/proc/self/ns/pid"
#	endif
	    ;
	ssize_t linklen =
#	ifndef JEMALLOC_READLINKAT
	    readlink(linkname, buf, PATH_MAX)
#	else
	    readlinkat(AT_FDCWD, linkname, buf, PATH_MAX)
#	endif
	    ;

	// namespace string is expected to be like pid:[4026531836]
	if (linklen > 0) {
		// Trim the trailing "]"
		buf[linklen - 1] = '\0';
		char *index = strtok(buf, "pid:[");
		ret = atol(index);
	}
#endif

	return ret;
}

/*
 * This buffer is rather large for stack allocation, so use a single buffer for
 * all profile dumps; protected by prof_dump_mtx.
 */
static char prof_dump_buf[PROF_DUMP_BUFSIZE];

typedef struct prof_dump_arg_s prof_dump_arg_t;
struct prof_dump_arg_s {
	/*
	 * Whether error should be handled locally: if true, then we print out
	 * error message as well as abort (if opt_abort is true) when an error
	 * occurred, and we also report the error back to the caller in the end;
	 * if false, then we only report the error back to the caller in the
	 * end.
	 */
	const bool handle_error_locally;
	/*
	 * Whether there has been an error in the dumping process, which could
	 * have happened either in file opening or in file writing.  When an
	 * error has already occurred, we will stop further writing to the file.
	 */
	bool error;
	/* File descriptor of the dump file. */
	int prof_dump_fd;
};

static void
prof_dump_check_possible_error(
    prof_dump_arg_t *arg, bool err_cond, const char *format, ...) {
	assert(!arg->error);
	if (!err_cond) {
		return;
	}

	arg->error = true;
	if (!arg->handle_error_locally) {
		return;
	}

	va_list ap;
	char    buf[PROF_PRINTF_BUFSIZE];
	va_start(ap, format);
	malloc_vsnprintf(buf, sizeof(buf), format, ap);
	va_end(ap);
	malloc_write(buf);

	if (opt_abort) {
		abort();
	}
}

static int
prof_dump_open_file_impl(const char *filename, int mode) {
	return creat(filename, mode);
}
prof_dump_open_file_t *JET_MUTABLE prof_dump_open_file =
    prof_dump_open_file_impl;

static void
prof_dump_open(prof_dump_arg_t *arg, const char *filename) {
	arg->prof_dump_fd = prof_dump_open_file(filename, 0644);
	prof_dump_check_possible_error(arg, arg->prof_dump_fd == -1,
	    "<jemalloc>: failed to open \"%s\"\n", filename);
}

prof_dump_write_file_t *JET_MUTABLE prof_dump_write_file = malloc_write_fd;

static void
prof_dump_flush(void *opaque, const char *s) {
	cassert(config_prof);
	prof_dump_arg_t *arg = (prof_dump_arg_t *)opaque;
	if (!arg->error) {
		ssize_t err = prof_dump_write_file(
		    arg->prof_dump_fd, s, strlen(s));
		prof_dump_check_possible_error(arg, err == -1,
		    "<jemalloc>: failed to write during heap profile flush\n");
	}
}

static void
prof_dump_close(prof_dump_arg_t *arg) {
	if (arg->prof_dump_fd != -1) {
		close(arg->prof_dump_fd);
	}
}

#ifdef __APPLE__
#	include <mach-o/dyld.h>

#	ifdef __LP64__
typedef struct mach_header_64     mach_header_t;
typedef struct segment_command_64 segment_command_t;
#		define MH_MAGIC_VALUE MH_MAGIC_64
#		define MH_CIGAM_VALUE MH_CIGAM_64
#		define LC_SEGMENT_VALUE LC_SEGMENT_64
#	else
typedef struct mach_header     mach_header_t;
typedef struct segment_command segment_command_t;
#		define MH_MAGIC_VALUE MH_MAGIC
#		define MH_CIGAM_VALUE MH_CIGAM
#		define LC_SEGMENT_VALUE LC_SEGMENT
#	endif

static void
prof_dump_dyld_image_vmaddr(buf_writer_t *buf_writer, uint32_t image_index) {
	const mach_header_t *header = (const mach_header_t *)
	    _dyld_get_image_header(image_index);
	if (header == NULL
	    || (header->magic != MH_MAGIC_VALUE
	        && header->magic != MH_CIGAM_VALUE)) {
		// Invalid header
		return;
	}

	intptr_t             slide = _dyld_get_image_vmaddr_slide(image_index);
	const char          *name = _dyld_get_image_name(image_index);
	struct load_command *load_cmd = (struct load_command *)((char *)header
	    + sizeof(mach_header_t));
	for (uint32_t i = 0; load_cmd && (i < header->ncmds); i++) {
		if (load_cmd->cmd == LC_SEGMENT_VALUE) {
			const segment_command_t *segment_cmd =
			    (const segment_command_t *)load_cmd;
			if (!strcmp(segment_cmd->segname, "__TEXT")) {
				char buffer[PATH_MAX + 1];
				malloc_snprintf(buffer, sizeof(buffer),
				    "%016llx-%016llx: %s\n",
				    segment_cmd->vmaddr + slide,
				    segment_cmd->vmaddr + slide
				        + segment_cmd->vmsize,
				    name);
				buf_writer_cb(buf_writer, buffer);
				return;
			}
		}
		load_cmd = (struct load_command *)((char *)load_cmd
		    + load_cmd->cmdsize);
	}
}

static void
prof_dump_dyld_maps(buf_writer_t *buf_writer) {
	uint32_t image_count = _dyld_image_count();
	for (uint32_t i = 0; i < image_count; i++) {
		prof_dump_dyld_image_vmaddr(buf_writer, i);
	}
}

prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps = NULL;

static void
prof_dump_maps(buf_writer_t *buf_writer) {
	buf_writer_cb(buf_writer, "\nMAPPED_LIBRARIES:\n");
	/* No proc map file to read on MacOS, dump dyld maps for backtrace. */
	prof_dump_dyld_maps(buf_writer);
}
#else /* !__APPLE__ */
#	ifndef _WIN32
JEMALLOC_FORMAT_PRINTF(1, 2)
static int
prof_open_maps_internal(const char *format, ...) {
	int     mfd;
	va_list ap;
	char    filename[PATH_MAX + 1];

	va_start(ap, format);
	malloc_vsnprintf(filename, sizeof(filename), format, ap);
	va_end(ap);

#		if defined(O_CLOEXEC)
	mfd = open(filename, O_RDONLY | O_CLOEXEC);
#		else
	mfd = open(filename, O_RDONLY);
	if (mfd != -1) {
		fcntl(mfd, F_SETFD, fcntl(mfd, F_GETFD) | FD_CLOEXEC);
	}
#		endif

	return mfd;
}
#	endif

static int
prof_dump_open_maps_impl(void) {
	int mfd;

	cassert(config_prof);
#	if defined(__FreeBSD__) || defined(__DragonFly__)
	mfd = prof_open_maps_internal("/proc/curproc/map");
#	elif defined(_WIN32)
	mfd = -1; // Not implemented
#	else
	int pid = prof_getpid();

	mfd = prof_open_maps_internal("/proc/%d/task/%d/maps", pid, pid);
	if (mfd == -1) {
		mfd = prof_open_maps_internal("/proc/%d/maps", pid);
	}
#	endif
	return mfd;
}
prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps =
    prof_dump_open_maps_impl;

static ssize_t
prof_dump_read_maps_cb(void *read_cbopaque, void *buf, size_t limit) {
	int mfd = *(int *)read_cbopaque;
	assert(mfd != -1);
	return malloc_read_fd(mfd, buf, limit);
}

static void
prof_dump_maps(buf_writer_t *buf_writer) {
	int mfd = prof_dump_open_maps();
	if (mfd == -1) {
		return;
	}

	buf_writer_cb(buf_writer, "\nMAPPED_LIBRARIES:\n");
	buf_writer_pipe(buf_writer, prof_dump_read_maps_cb, &mfd);
	close(mfd);
}
#endif /* __APPLE__ */

static bool
prof_dump(
    tsd_t *tsd, bool propagate_err, const char *filename, bool leakcheck) {
	cassert(config_prof);
	assert(tsd_reentrancy_level_get(tsd) == 0);

	prof_tdata_t *tdata = prof_tdata_get(tsd, true);
	if (tdata == NULL) {
		return true;
	}

	prof_dump_arg_t arg = {/* handle_error_locally */ !propagate_err,
	    /* error */ false, /* prof_dump_fd */ -1};

	pre_reentrancy(tsd, NULL);
	malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_mtx);

	prof_dump_open(&arg, filename);
	buf_writer_t buf_writer;
	bool err = buf_writer_init(tsd_tsdn(tsd), &buf_writer, prof_dump_flush,
	    &arg, prof_dump_buf, PROF_DUMP_BUFSIZE);
	assert(!err);
	prof_dump_impl(tsd, buf_writer_cb, &buf_writer, tdata, leakcheck);
	prof_dump_maps(&buf_writer);
	buf_writer_terminate(tsd_tsdn(tsd), &buf_writer);
	prof_dump_close(&arg);

	prof_dump_hook_t dump_hook = prof_dump_hook_get();
	if (dump_hook != NULL) {
		dump_hook(filename);
	}
	malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx);
	post_reentrancy(tsd);

	return arg.error;
}

/*
 * If profiling is off, then PROF_DUMP_FILENAME_LEN is 1, so we'll end up
 * calling strncpy with a size of 0, which triggers a -Wstringop-truncation
 * warning (strncpy can never actually be called in this case, since we bail out
 * much earlier when config_prof is false).  This function works around the
 * warning to let us leave the warning on.
 */
static inline void
prof_strncpy(char *UNUSED dest, const char *UNUSED src, size_t UNUSED size) {
	cassert(config_prof);
#ifdef JEMALLOC_PROF
	strncpy(dest, src, size);
#endif
}

static const char *
prof_prefix_get(tsdn_t *tsdn) {
	malloc_mutex_assert_owner(tsdn, &prof_dump_filename_mtx);

	return prof_prefix == NULL ? opt_prof_prefix : prof_prefix;
}

static bool
prof_prefix_is_empty(tsdn_t *tsdn) {
	malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
	bool ret = (prof_prefix_get(tsdn)[0] == '\0');
	malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
	return ret;
}

#define DUMP_FILENAME_BUFSIZE (PATH_MAX + 1)
#define VSEQ_INVALID UINT64_C(0xffffffffffffffff)
static void
prof_dump_filename(tsd_t *tsd, char *filename, char v, uint64_t vseq) {
	cassert(config_prof);

	assert(tsd_reentrancy_level_get(tsd) == 0);
	const char *prefix = prof_prefix_get(tsd_tsdn(tsd));

	if (vseq != VSEQ_INVALID) {
		if (opt_prof_pid_namespace) {
			/* "<prefix>.<pid_namespace>.<pid>.<seq>.v<vseq>.heap" */
			malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
			    "%s.%ld.%d.%" FMTu64 ".%c%" FMTu64 ".heap", prefix,
			    prof_get_pid_namespace(), prof_getpid(),
			    prof_dump_seq, v, vseq);
		} else {
			/* "<prefix>.<pid>.<seq>.v<vseq>.heap" */
			malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
			    "%s.%d.%" FMTu64 ".%c%" FMTu64 ".heap", prefix,
			    prof_getpid(), prof_dump_seq, v, vseq);
		}
	} else {
		if (opt_prof_pid_namespace) {
			/* "<prefix>.<pid_namespace>.<pid>.<seq>.<v>.heap" */
			malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
			    "%s.%ld.%d.%" FMTu64 ".%c.heap", prefix,
			    prof_get_pid_namespace(), prof_getpid(),
			    prof_dump_seq, v);
		} else {
			/* "<prefix>.<pid>.<seq>.<v>.heap" */
			malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
			    "%s.%d.%" FMTu64 ".%c.heap", prefix, prof_getpid(),
			    prof_dump_seq, v);
		}
	}
	prof_dump_seq++;
}

void
prof_get_default_filename(tsdn_t *tsdn, char *filename, uint64_t ind) {
	malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
	if (opt_prof_pid_namespace) {
		malloc_snprintf(filename, PROF_DUMP_FILENAME_LEN,
		    "%s.%ld.%d.%" FMTu64 ".json", prof_prefix_get(tsdn),
		    prof_get_pid_namespace(), prof_getpid(), ind);
	} else {
		malloc_snprintf(filename, PROF_DUMP_FILENAME_LEN,
		    "%s.%d.%" FMTu64 ".json", prof_prefix_get(tsdn),
		    prof_getpid(), ind);
	}
	malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
}

void
prof_fdump_impl(tsd_t *tsd) {
	char filename[DUMP_FILENAME_BUFSIZE];

	assert(!prof_prefix_is_empty(tsd_tsdn(tsd)));
	malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
	prof_dump_filename(tsd, filename, 'f', VSEQ_INVALID);
	malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
	prof_dump(tsd, false, filename, opt_prof_leak);
}

bool
prof_prefix_set(tsdn_t *tsdn, const char *prefix) {
	cassert(config_prof);
	ctl_mtx_assert_held(tsdn);
	if (prefix == NULL) {
		return true;
	}
	malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
	if (prof_prefix == NULL) {
		malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
		/* Everything is still guarded by ctl_mtx. */
		char *buffer = base_alloc(
		    tsdn, prof_base, PROF_DUMP_FILENAME_LEN, QUANTUM);
		if (buffer == NULL) {
			return true;
		}
		malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
		prof_prefix = buffer;
	}
	assert(prof_prefix != NULL);

	prof_strncpy(prof_prefix, prefix, PROF_DUMP_FILENAME_LEN - 1);
	prof_prefix[PROF_DUMP_FILENAME_LEN - 1] = '\0';
	malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);

	return false;
}

void
prof_idump_impl(tsd_t *tsd) {
	malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
	if (prof_prefix_get(tsd_tsdn(tsd))[0] == '\0') {
		malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
		return;
	}
	char filename[PATH_MAX + 1];
	prof_dump_filename(tsd, filename, 'i', prof_dump_iseq);
	prof_dump_iseq++;
	malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
	prof_dump(tsd, false, filename, false);
}

bool
prof_mdump_impl(tsd_t *tsd, const char *filename) {
	char filename_buf[DUMP_FILENAME_BUFSIZE];
	if (filename == NULL) {
		/* No filename specified, so automatically generate one. */
		malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
		if (prof_prefix_get(tsd_tsdn(tsd))[0] == '\0') {
			malloc_mutex_unlock(
			    tsd_tsdn(tsd), &prof_dump_filename_mtx);
			return true;
		}
		prof_dump_filename(tsd, filename_buf, 'm', prof_dump_mseq);
		prof_dump_mseq++;
		malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
		filename = filename_buf;
	}
	return prof_dump(tsd, true, filename, false);
}

void
prof_gdump_impl(tsd_t *tsd) {
	tsdn_t *tsdn = tsd_tsdn(tsd);
	malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
	if (prof_prefix_get(tsdn)[0] == '\0') {
		malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
		return;
	}
	char filename[DUMP_FILENAME_BUFSIZE];
	prof_dump_filename(tsd, filename, 'u', prof_dump_useq);
	prof_dump_useq++;
	malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
	prof_dump(tsd, false, filename, false);
}
