// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2011 Andrea Mazzoleni

#include "portable.h"

#include "support.h"

/****************************************************************************/
/* esc */

struct esc_thread_pool {
	char pool[ESC_POOL_MAX][ESC_MAX];
	unsigned idx;
};

#if HAVE_THREAD
static thread_key_t esc_key;

static void esc_pool_free(void* ptr)
{
	free(ptr);
}
#else
static struct esc_thread_pool esc_single_pool;
#endif

char* esc_buf(void)
{
	struct esc_thread_pool* tp;

#if HAVE_THREAD
	tp = thread_getspecific(esc_key);
	if (!tp) {
		tp = calloc_nofail(1, sizeof(*tp));
		thread_setspecific(esc_key, tp);
	}
#else
	tp = &esc_single_pool;
#endif

	char* buffer = tp->pool[tp->idx];
	tp->idx = (tp->idx + 1) % ESC_POOL_MAX;

	return buffer;
}

/****************************************************************************/
/* lock */

/**
 * Locks used externally.
 */
#if HAVE_THREAD
static thread_mutex_t msg_lock;
static thread_mutex_t memory_lock;
static thread_mutex_t random_lock;
#endif

void lock_msg(void)
{
#if HAVE_THREAD
	thread_mutex_lock(&msg_lock);
#endif
}

void unlock_msg(void)
{
#if HAVE_THREAD
	thread_mutex_unlock(&msg_lock);
#endif
}

void lock_memory(void)
{
#if HAVE_THREAD
	thread_mutex_lock(&memory_lock);
#endif
}

void unlock_memory(void)
{
#if HAVE_THREAD
	thread_mutex_unlock(&memory_lock);
#endif
}

void lock_random(void)
{
#if HAVE_THREAD
	thread_mutex_lock(&random_lock);
#endif
}

void unlock_random(void)
{
#if HAVE_THREAD
	thread_mutex_unlock(&random_lock);
#endif
}

void lock_init(void)
{
#if HAVE_THREAD
	/* initialize the locks as first operation as log_fatal depends on them */
	thread_mutex_init(&msg_lock);
	thread_mutex_init(&memory_lock);
	thread_mutex_init(&random_lock);
	thread_key_create(&esc_key, esc_pool_free);
#endif
}

void lock_done(void)
{
#if HAVE_THREAD
	void* tp = thread_getspecific(esc_key);
	if (tp) {
		esc_pool_free(tp);
		thread_setspecific(esc_key, NULL);
	}
	thread_key_delete(esc_key);
	thread_mutex_destroy(&msg_lock);
	thread_mutex_destroy(&memory_lock);
	thread_mutex_destroy(&random_lock);
#endif
}

/****************************************************************************/
/* random */

static uint64_t random_state = 1;

void random_seed(uint64_t seed)
{
	lock_random();
	random_state = seed;
	unlock_random();
}

void random_reseed(void)
{
	uint64_t seed = os_tick();

	lock_random();
	random_state = seed;
	unlock_random();
}

unsigned char random_u8(void)
{
	return random_u64() & 0xFF;
}

/*
 * SplitMix64
 *
 * - Zero-safe: Any 64-bit seed is valid (even 0).
 * - Full period: Guaranteed to visit every 2^64 value.
 * - Logic: Uses a Weyl sequence + MurmurHash3-style finalizer.
 *
 * See: https://rosettacode.org/wiki/Pseudo-random_numbers/Splitmix64
 */
uint64_t random_u64(void)
{
	uint64_t z;

	lock_random();

	z = random_state += 0x9E3779B97F4A7C15ULL;

	unlock_random();

	z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
	z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;

	return z ^ (z >> 31);
}

/****************************************************************************/
/* error */

int is_hw(int err)
{
	/* LCOV_EXCL_START */
	if (err == EIO)
		return 1;
#ifdef EFSCORRUPTED
	if (err == EFSCORRUPTED) /* used by XFS inside the kernel - never expected in user mode as converted to EUCLEAN */
		return 1;
#endif
#ifdef EUCLEAN
	if (err == EUCLEAN) /* used by XFS at user level instead of EFSCORRUPTED */
		return 1;
#endif
#ifdef EBADCRC
	if (err == EBADCRC) /* used by BTRFS - never expected in user mode as converted to EIO */
		return 1;
#endif
#ifdef EBADMSG
	if (err == EBADMSG) /* used by BTRFS - never expected in user mode as converted to EIO */
		return 1;
#endif
#ifdef ENOMEDIUM
	if (err == ENOMEDIUM) /* medium removed */
		return 1;
#endif
#ifdef EHWPOISON
	if (err == EHWPOISON) /* hardware memory error */
		return 1;
#endif
	/* LCOV_EXCL_STOP */
	return 0;
}

void log_fatal_errno(int err, const char* name)
{
	/* LCOV_EXCL_START */
	if (err == EIO) {
		log_fatal(err, "DANGER! Unexpected input/output error in disk %s. It isn't possible to continue.\n", name);
#ifdef EFSCORRUPTED
	} else if (err == EFSCORRUPTED) {
		log_fatal(err, "DANGER! Unexpected corrupted error in disk %s. It isn't possible to continue.\n", name);
#endif
#ifdef EUCLEAN
	} else if (err == EUCLEAN) {
		log_fatal(err, "DANGER! Unexpected corrupted error in disk %s. It isn't possible to continue.\n", name);
#endif
#ifdef EBADCRC
	} else if (err == EBADCRC) {
		log_fatal(err, "DANGER! Unexpected bad crc error in disk %s. It isn't possible to continue.\n", name);
#endif
#ifdef EBADMSG
	} else if (err == EBADMSG) {
		log_fatal(err, "DANGER! Unexpected bad crc error in disk %s. It isn't possible to continue.\n", name);
#endif
#ifdef ENOMEDIUM
	} else if (err == ENOMEDIUM) {
		log_fatal(err, "DANGER! Unexpected medium removed in disk %s. It isn't possible to continue.\n", name);
#endif
#ifdef EHWPOISON
	} else if (err == EHWPOISON) {
		log_fatal(err, "DANGER! Unexpected memory error in disk %s. It isn't possible to continue.\n", name);
#endif
	} else if (err == EACCES) {
		log_fatal(err, "WARNING! Grant permission in the disk %s. It isn't possible to continue.\n", name);
	} else if (err == ENOSPC) {
		log_fatal(err, "WARNING! Ensure there is free space on the disk %s. It isn't possible to continue.\n", name);
	} else {
		log_fatal(err, "WARNING! Without a working %s disk, it isn't possible to continue.\n", name);
	}
	/* LCOV_EXCL_STOP */
}

void log_error_errno(int err, const char* name)
{
	/* LCOV_EXCL_START */
	if (err == EIO) {
		log_fatal(err, "DANGER! Unexpected input/output error in disk %s.\n", name);
#ifdef EFSCORRUPTED
	} else if (err == EFSCORRUPTED) {
		log_fatal(err, "DANGER! Unexpected corrupted error in disk %s.\n", name);
#endif
#ifdef EUCLEAN
	} else if (err == EUCLEAN) {
		log_fatal(err, "DANGER! Unexpected corrupted error in disk %s.\n", name);
#endif
#ifdef EBADCRC
	} else if (err == EBADCRC) {
		log_fatal(err, "DANGER! Unexpected bad crc error in disk %s.\n", name);
#endif
#ifdef EBADMSG
	} else if (err == EBADMSG) {
		log_fatal(err, "DANGER! Unexpected bad crc error in disk %s.\n", name);
#endif
#ifdef ENOMEDIUM
	} else if (err == ENOMEDIUM) {
		log_fatal(err, "DANGER! Unexpected medium removed in disk %s.\n", name);
#endif
#ifdef EHWPOISON
	} else if (err == EHWPOISON) {
		log_fatal(err, "DANGER! Unexpected memory error in disk %s.\n", name);
#endif
		/* LCOV_EXCL_STOP */
	} else if (err == EACCES) {
		log_error(err, "WARNING! Grant permission in the disk %s.\n", name);
	} else if (err == ENOSPC) {
		log_error(err, "WARNING! Ensure there is free space on the disk %s.\n", name);
	} else if (err == ENOENT) {
		log_error(err, "WARNING! You cannot modify files while running.\n");
	}
}

/****************************************************************************/
/* print */

int msg_level = 0;
FILE* stdlog = 0;
int msg_line_prev = 0; /**< Previous line width on stdout */
int msg_line_curr = 0; /**< Current line width on stdout */

/*
 * Note that in the following functions we always flush both
 * stdout and stderr, because we want to ensure that they mixes
 * well when redirected to files
 *
 * The buffering is similar at the "line buffered" one, that
 * is not available on Windows, so we emulate it in this way.
 *
 * For stdlog flushing is limited. To ensure flushing the
 * caller should use log_flush().
 */

static void vmsg(FILE* out, const char* format, va_list ap)
{
	char* dup = strdup_nofail(format);
	int len = strlen(dup);
	int written = 0;
	char control = 0;

	if (len > 0) {
		if (dup[len - 1] == '\n') {
			dup[len - 1] = 0;
			control = '\n';
		} else if (dup[len - 1] == '\r') {
			dup[len - 1] = 0;
			control = '\r';
		}
	}

	if (dup[0]) {
		written = vfprintf(out, dup, ap);
		if (written < 0) /* ignore errors */
			written = 0;
	}

	switch (control) {
	case 0 :
		msg_line_curr += written;
		break;
	case '\n' :
		msg_line_curr += written;
		/* writes spaces to overwrite any previous char */
		while (msg_line_curr < msg_line_prev) {
			fprintf(out, " ");
			--msg_line_prev;
		}
		msg_line_prev = 0;
		msg_line_curr = 0;
		fprintf(out, "\n");
		break;
	case '\r' :
		msg_line_curr += written;
		/* writes spaces to overwrite any previous char */
		while (msg_line_curr < msg_line_prev) {
			fprintf(out, " ");
			--msg_line_prev;
		}
		msg_line_prev = msg_line_curr;
		msg_line_curr = 0;
		fprintf(out, "\r");
	}

	free(dup);
}

void log_fatal(int err, const char* format, ...)
{
	va_list ap;

	lock_msg();

	if (stdlog) {
		if (is_hw(err))
			fprintf(stdlog, "msg:fatal_hardware: ");
		else
			fprintf(stdlog, "msg:fatal: ");

		va_start(ap, format);
		vfprintf(stdlog, format, ap);
		va_end(ap);

		fflush(stdlog);
	}

	va_start(ap, format);
	vmsg(stderr, format, ap);
	va_end(ap);

	fflush(stderr);

	unlock_msg();
}

void log_error(int err, const char* format, ...)
{
	va_list ap;

	lock_msg();

	if (stdlog) {
		if (is_hw(err))
			fprintf(stdlog, "msg:error_hardware: ");
		else
			fprintf(stdlog, "msg:error: ");

		va_start(ap, format);
		vfprintf(stdlog, format, ap);
		va_end(ap);

		fflush(stdlog);
	} else {
		va_start(ap, format);
		vmsg(stderr, format, ap);
		va_end(ap);

		fflush(stderr);
	}

	unlock_msg();
}

void log_expected(int err, const char* format, ...)
{
	va_list ap;

	lock_msg();

	if (stdlog) {
		if (is_hw(err))
			fprintf(stdlog, "msg:error_hardware: "); /* we never expect an hardware error */
		else
			fprintf(stdlog, "msg:expected: ");

		va_start(ap, format);
		vfprintf(stdlog, format, ap);
		va_end(ap);

		fflush(stdlog);
	}

	unlock_msg();
}

void log_tag(const char* format, ...)
{
	va_list ap;

	lock_msg();

	if (stdlog) {
		va_start(ap, format);
		vfprintf(stdlog, format, ap);
		va_end(ap);

		/* here we intentionally don't flush to make the output faster */
	}

	unlock_msg();
}

void log_flush(void)
{
	lock_msg();

	if (stdlog)
		fflush(stdlog);
	fflush(stdout);
	fflush(stderr);

	unlock_msg();
}

void msg_status(const char* format, ...)
{
	va_list ap;

	lock_msg();

	if (stdlog) {
		fprintf(stdlog, "msg:status: ");

		va_start(ap, format);
		vfprintf(stdlog, format, ap);
		va_end(ap);

		fflush(stdlog);
	}

	if (msg_level >= MSG_STATUS) {
		va_start(ap, format);
		vmsg(stdout, format, ap);
		va_end(ap);
	}

	unlock_msg();
}

void msg_info(const char* format, ...)
{
	va_list ap;

	lock_msg();

	/* don't output in stdlog as these messages */
	/* are always paired with a msg_tag() call */

	if (msg_level >= MSG_INFO) {
		va_start(ap, format);
		vmsg(stdout, format, ap);
		va_end(ap);

		fflush(stdout);
	}

	unlock_msg();
}

void msg_progress(const char* format, ...)
{
	va_list ap;

	lock_msg();

	if (stdlog) {
		fprintf(stdlog, "msg:progress: ");

		va_start(ap, format);
		vfprintf(stdlog, format, ap);
		va_end(ap);

		fflush(stdlog);
	}

	if (msg_level >= MSG_PROGRESS) {
		va_start(ap, format);
		vmsg(stdout, format, ap);
		va_end(ap);

		fflush(stdout);
	}

	unlock_msg();
}

void msg_bar(const char* format, ...)
{
	va_list ap;

	lock_msg();

	/* don't output in stdlog as these messages */
	/* are intended for screen only */
	/* also don't flush stdout as they are intended to be partial messages */

	if (msg_level >= MSG_BAR) {
		va_start(ap, format);
		vmsg(stdout, format, ap);
		va_end(ap);
	}

	unlock_msg();
}

void msg_verbose(const char* format, ...)
{
	va_list ap;

	lock_msg();

	if (stdlog) {
		fprintf(stdlog, "msg:verbose: ");

		va_start(ap, format);
		vfprintf(stdlog, format, ap);
		va_end(ap);

		fflush(stdlog);
	}

	if (msg_level >= MSG_VERBOSE) {
		va_start(ap, format);
		vmsg(stdout, format, ap);
		va_end(ap);

		fflush(stdout);
	}

	unlock_msg();
}

void msg_flush(void)
{
	lock_msg();

	fflush(stdout);
	fflush(stderr);

	unlock_msg();
}

void printc(char c, size_t pad)
{
	while (pad) {
		/* group writes in long pieces */
		char buf[128];
		size_t len = pad;

		if (len >= sizeof(buf))
			len = sizeof(buf) - 1;

		memset(buf, c, len);
		buf[len] = 0;

		fputs(buf, stdout);

		pad -= len;
	}
}

void printr(const char* str, size_t pad)
{
	size_t len;

	len = strlen(str);

	if (len < pad)
		printc(' ', pad - len);

	fputs(str, stdout);
}

void printl(const char* str, size_t pad)
{
	size_t len;

	fputs(str, stdout);

	len = strlen(str);

	if (len < pad)
		printc(' ', pad - len);
}

void printp(double v, size_t pad)
{
	char buf[64];
	const char* s = "%";

	if (v > 0.1)
		snprintf(buf, sizeof(buf), "%5.2f%s", v, s);
	else if (v > 0.01)
		snprintf(buf, sizeof(buf), "%6.3f%s", v, s);
	else if (v > 0.001)
		snprintf(buf, sizeof(buf), "%7.4f%s", v, s);
	else if (v > 0.0001)
		snprintf(buf, sizeof(buf), "%8.5f%s", v, s);
	else if (v > 0.00001)
		snprintf(buf, sizeof(buf), "%9.6f%s", v, s);
	else if (v > 0.000001)
		snprintf(buf, sizeof(buf), "%10.7f%s", v, s);
	else if (v > 0.0000001)
		snprintf(buf, sizeof(buf), "%11.8f%s", v, s);
	else if (v > 0.00000001)
		snprintf(buf, sizeof(buf), "%12.9f%s", v, s);
	else if (v > 0.000000001)
		snprintf(buf, sizeof(buf), "%13.10f%s", v, s);
	else if (v > 0.0000000001)
		snprintf(buf, sizeof(buf), "%14.11f%s", v, s);
	else if (v > 0.00000000001)
		snprintf(buf, sizeof(buf), "%15.12f%s", v, s);
	else if (v > 0.000000000001)
		snprintf(buf, sizeof(buf), "%16.13f%s", v, s);
	else
		snprintf(buf, sizeof(buf), "%17.14f%s", v, s);
	printl(buf, pad);
}

#define charcat(c) \
	do { \
		if (p == end) { \
			goto bail; \
		} \
		*p++ = (c); \
	} while (0)

const char* esc_tag(const char* str)
{
	char* buffer = esc_buf();
	char* begin = buffer;
	char* end = begin + ESC_MAX;
	char* p = begin;

	/* copy string with escaping */
	while (*str) {
		char c = *str++;

		switch (c) {
		case '\n' :
			charcat('\\');
			charcat('n');
			break;
		case '\r' :
			charcat('\\');
			charcat('r');
			break;
		case ':' :
			charcat('\\');
			charcat('d');
			break;
		case '\\' :
			charcat('\\');
			charcat('\\');
			break;
		default :
			charcat(c);
			break;
		}
	}

	/* put final 0 */
	if (p == end)
		goto bail;
	*p = 0;

	return begin;

bail:
	/* LCOV_EXCL_START */
	log_fatal(EINTERNAL, "Escape for log is too long\n");
	exit(EXIT_FAILURE);
	/* LCOV_EXCL_STOP */
}

#ifdef _WIN32
static int needs_quote(const char* arg)
{
	while (*arg) {
		char c = *arg;
		if (c == ' ' || c == '\t' || c == '\n' || c == '\r' ||
			c == '&' || c == '|' || c == '(' || c == ')' ||
			c == '<' || c == '>' || c == '^' || c == '"' ||
			c == '%' || c == '!' || c == '=' || c == ';' ||
			(unsigned char)c < 32 || c == 127)
			return 1;
		++arg;
	}

	return 0;
}
#endif

struct stream {
	const char* str;
	unsigned idx;
	const char** map;
	unsigned max;
};

static inline char ssget(struct stream* ss)
{
	/* get the next char */
	char c = *ss->str++;

	/* if one string is finished, go to the next */
	while (c == 0 && ss->idx + 1 < ss->max) {
		ss->str = ss->map[++ss->idx];
		c = *ss->str++;
	}

	return c;
}

#ifdef _WIN32
const char* esc_shell_multi(const char** str_map, unsigned str_max, char* buffer)
{
	char* begin = buffer;
	char* end = begin + ESC_MAX;
	char* p = begin;
	struct stream ss;
	char c;
	unsigned i;
	int has_quote;

	has_quote = 0;
	for (i = 0; i < str_max; ++i) {
		if (needs_quote(str_map[i]))
			has_quote = 1;
	}

	ss.idx = 0;
	ss.str = str_map[0];
	ss.map = str_map;
	ss.max = str_max;

	if (!has_quote) {
		c = ssget(&ss);
		while (c) {
			charcat(c);
			c = ssget(&ss);
		}
	} else {
		/* starting quote */
		charcat('"');

		c = ssget(&ss);
		while (c) {
			int bl = 0;
			while (c == '\\') {
				++bl;
				c = ssget(&ss);
			}

			if (c == 0) {
				/* double backslashes before closing quote */
				bl = bl * 2;
				while (bl--)
					charcat('\\');
				break;
			} else if (c == '"') {
				/* double backslashes + escape the quote */
				bl = bl * 2 + 1;
				while (bl--)
					charcat('\\');
				charcat('"');
			} else {
				/* normal backslashes */
				while (bl--)
					charcat('\\');
				charcat(c);
			}

			c = ssget(&ss);
		}

		/* ending quote */
		charcat('"');
	}

	/* put final 0 */
	charcat(0);

	return begin;

bail:
	/* LCOV_EXCL_START */
	log_fatal(EINTERNAL, "Escape for shell is too long\n");
	exit(EXIT_FAILURE);
	/* LCOV_EXCL_STOP */
}
#else
const char* esc_shell_multi(const char** str_map, unsigned str_max, char* buffer)
{
	char* begin = buffer;
	char* end = begin + ESC_MAX;
	char* p = begin;
	struct stream ss;
	char c;

	ss.idx = 0;
	ss.str = str_map[0];
	ss.map = str_map;
	ss.max = str_max;

	c = ssget(&ss);
	while (c) {
		switch (c) {
		/* special chars that need to be quoted */
		case ' ' : /* space */
		case '\t' : /* tab */
		case '\n' : /* newline */
		case '\r' : /* carriage return */
		case '~' : /* home */
		case '`' : /* command */
		case '#' : /* comment */
		case '$' : /* variable */
		case '&' : /* background job */
		case '*' : /* wildcard */
		case '(' : /* shell */
		case ')' : /* shell */
		case '\\' : /* quote */
		case '|' : /* pipe */
		case '[' : /* wildcard */
		case ']' : /* wildcard */
		case '{' : /* code */
		case '}' : /* code */
		case ';' : /* separator */
		case '\'' : /* quote */
		case '"' : /* quote */
		case '<' : /* redirect */
		case '>' : /* redirect */
		case '?' : /* wildcard */
		case '=' : /* assignment */
		case '!' : /* history expansion */
			charcat('\\');
			charcat(c);
			break;
		default :
			/* check for control characters */
			if ((unsigned char)c < 32 || c == 127) {
				charcat('\\');
				charcat(c);
			} else {
				charcat(c);
			}
			break;
		}

		c = ssget(&ss);
	}

	/* put final 0 */
	charcat(0);

	return begin;

bail:
	/* LCOV_EXCL_START */
	log_fatal(EINTERNAL, "Escape for shell is too long\n");
	exit(EXIT_FAILURE);
	/* LCOV_EXCL_STOP */
}
#endif

char* strpolish(char* s)
{
	char* i = s;

	while (*i) {
		if (isspace((unsigned char)*i) || !isprint((unsigned char)*i))
			*i = ' ';
		++i;
	}

	return s;
}

unsigned strsplit(char** split_map, unsigned split_max, char* str, const char* delimiters)
{
	unsigned mac = 0;

	/* skip initial delimiters */
	str += strspn(str, delimiters);

	while (*str != 0 && mac < split_max) {
		/* start of the token */
		split_map[mac] = str;
		++mac;

		/* find the first delimiter or the end of the string */
		str += strcspn(str, delimiters);

		/* put the final terminator if missing */
		if (*str != 0)
			*str++ = 0;

		/* skip trailing delimiters */
		str += strspn(str, delimiters);
	}

	return mac;
}

char* strtrim(char* str)
{
	char* begin;
	char* end;

	begin = str;
	while (begin[0] && isspace((unsigned char)begin[0]))
		++begin;

	end = begin + strlen(begin);
	while (end > begin && isspace((unsigned char)end[-1]))
		--end;

	end[0] = 0;

	if (begin != end)
		memmove(str, begin, end - begin + 1);

	return str;
}

char* strlwr(char* str)
{
	char* s = str;

	while (*s) {
		*s = tolower((unsigned char)*s);
		++s;
	}

	return str;
}

char* worddigitstr(const char* haystack, const char* needle)
{
	size_t len = strlen(needle);
	const char* s;

	if (len == 0)
		return NULL;

	for (s = haystack; (s = strstr(s, needle)) != NULL; ++s) {

		/* left boundary */
		if (s == haystack || isspace((unsigned char)s[-1]) || isdigit((unsigned char)s[-1])) {
			/* right boundary */
			if (s[len] == '\0' || isspace((unsigned char)s[len]) || isdigit((unsigned char)s[len])) {
				return (char*)s;
			}
		}
	}

	return NULL;
}

/****************************************************************************/
/* path */

void pathcpy(char* dst, size_t size, const char* src)
{
	size_t len = strlen(src);

	if (len + 1 > size) {
		/* LCOV_EXCL_START */
		log_fatal(EINTERNAL, "Path too long '%s'\n", src);
		os_abort();
		/* LCOV_EXCL_STOP */
	}

	memcpy(dst, src, len + 1);
}

void pathcat(char* dst, size_t size, const char* src)
{
	size_t dst_len = strlen(dst);
	size_t src_len = strlen(src);

	if (dst_len + src_len + 1 > size) {
		/* LCOV_EXCL_START */
		log_fatal(EINTERNAL, "Path too long '%s%s'\n", dst, src);
		os_abort();
		/* LCOV_EXCL_STOP */
	}

	memcpy(dst + dst_len, src, src_len + 1);
}

void pathcatl(char* dst, size_t dst_len, size_t size, const char* src)
{
	size_t src_len = strlen(src);

	if (dst_len + src_len + 1 > size) {
		/* LCOV_EXCL_START */
		log_fatal(EINTERNAL, "Path too long '%s%s'\n", dst, src);
		os_abort();
		/* LCOV_EXCL_STOP */
	}

	memcpy(dst + dst_len, src, src_len + 1);
}

void pathcatc(char* dst, size_t size, char c)
{
	size_t dst_len = strlen(dst);

	if (dst_len + 2 > size) {
		/* LCOV_EXCL_START */
		log_fatal(EINTERNAL, "Path too long '%s%c'\n", dst, c);
		os_abort();
		/* LCOV_EXCL_STOP */
	}

	dst[dst_len] = c;
	dst[dst_len + 1] = 0;
}

void pathimport(char* dst, size_t size, const char* src)
{
	pathcpy(dst, size, src);

#ifdef _WIN32
	/* convert the Windows dir separator '\' to C '/', */
	/* and the Windows escaping char '^' to the fnmatch '\' */
	while (*dst) {
		switch (*dst) {
		case '\\' :
			*dst = '/';
			break;
		case '^' :
			*dst = '\\';
			break;
		}
		++dst;
	}
#endif
}

void pathexport(char* dst, size_t size, const char* src)
{
	pathcpy(dst, size, src);

#ifdef _WIN32
	/* invert the import */
	while (*dst) {
		switch (*dst) {
		case '/' :
			*dst = '\\';
			break;
		case '\\' :
			*dst = '^';
			break;
		}
		++dst;
	}
#endif
}

void pathprint(char* dst, size_t size, const char* format, ...)
{
	size_t len;
	va_list ap;

	va_start(ap, format);
	len = vsnprintf(dst, size, format, ap);
	va_end(ap);

	if (len >= size) {
		/* LCOV_EXCL_START */
		if (size > 0) {
			dst[size - 1] = 0;
			log_fatal(EINTERNAL, "Path too long '%s...'\n", dst);
		} else {
			log_fatal(EINTERNAL, "Path too long for empty size'\n");
		}
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void pathslash(char* dst, size_t size)
{
	size_t len = strlen(dst);

	if (len > 0 && dst[len - 1] != '/') {
		if (len + 2 > size) {
			/* LCOV_EXCL_START */
			log_fatal(EINTERNAL, "Path too long '%s/'\n", dst);
			os_abort();
			/* LCOV_EXCL_STOP */
		}

		dst[len] = '/';
		dst[len + 1] = 0;
	}
}

void pathcut(char* dst)
{
	char* slash = strrchr(dst, '/');

	if (slash)
		slash[1] = 0;
	else
		dst[0] = 0;
}

int pathcmp(const char* a, const char* b)
{
#ifdef _WIN32
	char ai[PATH_MAX];
	char bi[PATH_MAX];

	/* import to convert \ to / */
	pathimport(ai, sizeof(ai), a);
	pathimport(bi, sizeof(bi), b);

	/* case insensitive compare in Windows */
	return stricmp(ai, bi);
#else
	return strcmp(a, b);
#endif
}

int path_is_root_of(const char* root, const char* path)
{
	while (*root && *path) {
#ifdef _WIN32
		/* case insensitive compare in Windows */
		char r = tolower((unsigned char)*root);
		char p = tolower((unsigned char)*path);
		if (r == '\\')
			r = '/';
		if (p == '\\')
			p = '/';
		if (r != p)
			return 0;
#else
		if (*root != *path)
			return 0;
#endif
		++root;
		++path;
	}

	return *root == 0 && *path != 0;
}

/****************************************************************************/
/* file-system */

int mkancestor(const char* file)
{
	char dir[PATH_MAX];
	struct stat st;
	char* c;

	pathcpy(dir, sizeof(dir), file);

	c = strrchr(dir, '/');
	if (!c) {
		/* no ancestor */
		return 0;
	}

	/* clear the file */
	*c = 0;

	/* if it's the root dir */
	if (*dir == 0) {
		/* nothing more to do */
		return 0;
	}

#ifdef _WIN32
	/* if it's a drive specification like "C:" */
	if (isalpha((unsigned char)dir[0]) && dir[1] == ':' && dir[2] == 0) {
		/* nothing more to do */
		return 0;
	}
#endif

	/*
	 * Check if the dir already exists using lstat().
	 *
	 * Note that in Windows when dealing with read-only media
	 * you cannot try to create the directory, and expecting
	 * the EEXIST error because the call will fail with ERROR_WRITE_PROTECTED.
	 *
	 * Also in Windows it's better to use lstat() than stat() because it
	 * doesn't need to open the dir with CreateFile().
	 */
	if (lstat(dir, &st) == 0) {
		/* it already exists */
		return 0;
	}

	/* recursively create them all */
	if (mkancestor(dir) != 0) {
		/* LCOV_EXCL_START */
		return -1;
		/* LCOV_EXCL_STOP */
	}

	/* create it */
	int ret = mkdir(dir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
	if (ret != 0 && errno != EEXIST) {
		/* LCOV_EXCL_START */
		log_fatal(errno, "Error creating directory '%s'. %s.\n", dir, strerror(errno));
		return -1;
		/* LCOV_EXCL_STOP */
	}

	return 0;
}

int fmtime(int f, int64_t mtime_sec, int mtime_nsec)
{
#if HAVE_FUTIMENS
	struct timespec tv[2];
#else
	struct timeval tv[2];
#endif
	int ret;

#if HAVE_FUTIMENS /* futimens() is preferred because it gives nanosecond precision */
	tv[0].tv_sec = mtime_sec;
	if (mtime_nsec != STAT_NSEC_INVALID)
		tv[0].tv_nsec = mtime_nsec;
	else
		tv[0].tv_nsec = 0;
	tv[1].tv_sec = tv[0].tv_sec;
	tv[1].tv_nsec = tv[0].tv_nsec;

	ret = futimens(f, tv);
#elif HAVE_FUTIMES /* fallback to futimes() if nanosecond precision is not available */
	tv[0].tv_sec = mtime_sec;
	if (mtime_nsec != STAT_NSEC_INVALID)
		tv[0].tv_usec = mtime_nsec / 1000;
	else
		tv[0].tv_usec = 0;
	tv[1].tv_sec = tv[0].tv_sec;
	tv[1].tv_usec = tv[0].tv_usec;

	ret = futimes(f, tv);
#elif HAVE_FUTIMESAT /* fallback to futimesat() for Solaris, it only has futimesat() */
	tv[0].tv_sec = mtime_sec;
	if (mtime_nsec != STAT_NSEC_INVALID)
		tv[0].tv_usec = mtime_nsec / 1000;
	else
		tv[0].tv_usec = 0;
	tv[1].tv_sec = tv[0].tv_sec;
	tv[1].tv_usec = tv[0].tv_usec;

	ret = futimesat(f, 0, tv);
#else
#error No function available to set file timestamps with sub-second precision
#endif

	return ret;
}

int lmtime(const char* path, int64_t mtime_sec, int mtime_nsec)
{
#if HAVE_UTIMENSAT
	struct timespec tv[2];
#else
	struct timeval tv[2];
#endif
	int ret;

#if HAVE_UTIMENSAT /* utimensat() is preferred because it gives nanosecond precision */
	tv[0].tv_sec = mtime_sec;
	if (mtime_nsec != STAT_NSEC_INVALID)
		tv[0].tv_nsec = mtime_nsec;
	else
		tv[0].tv_nsec = 0;
	tv[1].tv_sec = tv[0].tv_sec;
	tv[1].tv_nsec = tv[0].tv_nsec;

	ret = utimensat(AT_FDCWD, path, tv, AT_SYMLINK_NOFOLLOW);
#elif HAVE_LUTIMES /* fallback to lutimes() if nanosecond precision is not available */
	tv[0].tv_sec = mtime_sec;
	if (mtime_nsec != STAT_NSEC_INVALID)
		tv[0].tv_usec = mtime_nsec / 1000;
	else
		tv[0].tv_usec = 0;
	tv[1].tv_sec = tv[0].tv_sec;
	tv[1].tv_usec = tv[0].tv_usec;

	ret = lutimes(path, tv);
#elif HAVE_FUTIMESAT /* fallback to futimesat() for Solaris, it only has futimesat() */
	tv[0].tv_sec = mtime_sec;
	if (mtime_nsec != STAT_NSEC_INVALID)
		tv[0].tv_usec = mtime_nsec / 1000;
	else
		tv[0].tv_usec = 0;
	tv[1].tv_sec = tv[0].tv_sec;
	tv[1].tv_usec = tv[0].tv_usec;

	ret = futimesat(AT_FDCWD, path, tv);
#else
#error No function available to set file timestamps with sub-second precision
#endif

	return ret;
}

/****************************************************************************/
/* advise */

void advise_init(struct advise_struct* advise, int mode)
{
	advise->mode = mode;
	advise->dirty_begin = 0;
	advise->dirty_end = 0;
}

int advise_flags(struct advise_struct* advise)
{
	int flags = 0;

	if (advise->mode == ADVISE_SEQUENTIAL
		|| advise->mode == ADVISE_FLUSH
		|| advise->mode == ADVISE_FLUSH_WINDOW
		|| advise->mode == ADVISE_DISCARD
		|| advise->mode == ADVISE_DISCARD_WINDOW
	)
		flags |= O_SEQUENTIAL;

#if HAVE_DIRECT_IO
	if (advise->mode == ADVISE_DIRECT)
		flags |= O_DIRECT;
#endif

	return flags;
}

int advise_open(struct advise_struct* advise, int f)
{
	(void)advise;
	(void)f;

#if HAVE_POSIX_FADVISE
	if (advise->mode == ADVISE_SEQUENTIAL
		|| advise->mode == ADVISE_FLUSH
		|| advise->mode == ADVISE_FLUSH_WINDOW
		|| advise->mode == ADVISE_DISCARD
		|| advise->mode == ADVISE_DISCARD_WINDOW
	) {
		int ret;

		/* advise noreuse access, this avoids to pollute the page cache */
		/* supported from Linux Kernel 6.3 with this commit: https://github.com/torvalds/linux/commit/17e810229cb3068b692fa078bd9b3a6527e0866a */
		ret = posix_fadvise_wrapper(f, 0, 0, POSIX_FADV_NOREUSE);
		if (ret == ENOSYS) {
			/* call is not supported */
			ret = 0;
		}
		if (ret != 0) {
			/* LCOV_EXCL_START */
			errno = ret; /* posix_fadvise return the error code */
			return -1;
			/* LCOV_EXCL_STOP */
		}

		/* advise sequential access, this doubles the read-ahead window size */
		ret = posix_fadvise_wrapper(f, 0, 0, POSIX_FADV_SEQUENTIAL);
		if (ret == ENOSYS) {
			/* call is not supported, like in armhf, see posix_fadvise manpage */
			ret = 0;
		}
		if (ret != 0) {
			/* LCOV_EXCL_START */
			errno = ret; /* posix_fadvise return the error code */
			return -1;
			/* LCOV_EXCL_STOP */
		}
	}
#endif

	return 0;
}

int advise_write(struct advise_struct* advise, int f, data_off_t offset, data_off_t size)
{
	data_off_t flush_offset;
	data_off_t flush_size;
	data_off_t discard_offset;
	data_off_t discard_size;

	(void)f;
	(void)flush_offset;
	(void)flush_size;
	(void)discard_offset;
	(void)discard_size;

	flush_offset = 0;
	flush_size = 0;
	discard_offset = 0;
	discard_size = 0;

	/*
	 * Follow Linus recommendations about fast writes.
	 *
	 * Linus "Unexpected splice "always copy" behavior observed"
	 * http://thread.gmane.org/gmane.linux.kernel/987247/focus=988070
	 * ---
	 * I have had _very_ good experiences with even a rather trivial
	 * file writer that basically used (iirc) 8MB windows, and the logic was very
	 * trivial:
	 *
	 *  - before writing a new 8M window, do "start writeback"
	 *    (SYNC_FILE_RANGE_WRITE) on the previous window, and do
	 *    a wait (SYNC_FILE_RANGE_WAIT_AFTER) on the window before that.
	 *
	 * in fact, in its simplest form, you can do it like this (this is from my
	 * "overwrite disk images" program that I use on old disks):
	 *
	 * for (index = 0; index < max_index ;index++) {
	 *   if (write(fd, buffer, BUFSIZE) != BUFSIZE)
	 *     break;
	 *   // This won't block, but will start writeout asynchronously
	 *   sync_file_range(fd, index*BUFSIZE, BUFSIZE, SYNC_FILE_RANGE_WRITE);
	 *   // This does a blocking write-and-wait on any old ranges
	 *   if (index)
	 *     sync_file_range(fd, (index-1)*BUFSIZE, BUFSIZE, SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE|SYNC_FILE_RANGE_WAIT_AFTER);
	 * }
	 *
	 * and even if you don't actually do a discard (maybe we should add a
	 * SYNC_FILE_RANGE_DISCARD bit, right now you'd need to do a separate
	 * fadvise(FADV_DONTNEED) to throw it out) the system behavior is pretty
	 * nice, because the heavy writer gets good IO performance _and_ leaves only
	 * easy-to-free pages around after itself.
	 * ---
	 *
	 * Linus "Unexpected splice "always copy" behavior observed"
	 * http://thread.gmane.org/gmane.linux.kernel/987247/focus=988176
	 * ---
	 * The behavior for dirty page writeback is _not_ well defined, and
	 * if you do POSIX_FADV_DONTNEED, I would suggest you do it as part of that
	 * writeback logic, ie you do it only on ranges that you have just waited on.
	 *
	 * IOW, in my example, you'd couple the
	 *
	 *   sync_file_range(fd, (index-1)*BUFSIZE, BUFSIZE, SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE|SYNC_FILE_RANGE_WAIT_AFTER);
	 *
	 * with a
	 *
	 *   posix_fadvise(fd, (index-1)*BUFSIZE, BUFSIZE, POSIX_FADV_DONTNEED);
	 *
	 * afterwards to throw out the pages that you just waited for.
	 * ---
	 */

	switch (advise->mode) {
	case ADVISE_FLUSH :
		flush_offset = offset;
		flush_size = size;
		break;
	case ADVISE_DISCARD :
		discard_offset = offset;
		discard_size = size;
		break;
	case ADVISE_FLUSH_WINDOW :
		/* if the dirty range can be extended */
		if (advise->dirty_end == offset) {
			/* extent the dirty range */
			advise->dirty_end += size;

			/* if we reached the window size */
			if (advise->dirty_end - advise->dirty_begin >= ADVISE_WINDOW_SIZE) {
				/* flush the window  */
				flush_offset = advise->dirty_begin;
				flush_size = ADVISE_WINDOW_SIZE;

				/* remove it from the dirty range */
				advise->dirty_begin += ADVISE_WINDOW_SIZE;
			}
		} else {
			/* otherwise flush the existing dirty */
			flush_offset = advise->dirty_begin;
			flush_size = advise->dirty_end - advise->dirty_begin;

			/* and set the new range as dirty */
			advise->dirty_begin = offset;
			advise->dirty_end = offset + size;
		}
		break;
	case ADVISE_DISCARD_WINDOW :
		/* if the dirty range can be extended */
		if (advise->dirty_end == offset) {
			/* extent the dirty range */
			advise->dirty_end += size;

			/* if we reached the double window size */
			if (advise->dirty_end - advise->dirty_begin >= 2 * ADVISE_WINDOW_SIZE) {
				/* discard the first window */
				discard_offset = advise->dirty_begin;
				discard_size = ADVISE_WINDOW_SIZE;

				/* remove it from the dirty range */
				advise->dirty_begin += ADVISE_WINDOW_SIZE;

				/* flush the second window */
				flush_offset = advise->dirty_begin;
				flush_size = ADVISE_WINDOW_SIZE;
			}
		} else {
			/* otherwise discard the existing dirty */
			discard_offset = advise->dirty_begin;
			discard_size = advise->dirty_end - advise->dirty_begin;

			/* and set the new range as dirty */
			advise->dirty_begin = offset;
			advise->dirty_end = offset + size;
		}
		break;
	}

#if HAVE_SYNC_FILE_RANGE
	if (flush_size != 0) {
		int ret;

		/* start writing immediately */
		ret = sync_file_range(f, flush_offset, flush_size, SYNC_FILE_RANGE_WRITE);
		if (ret != 0) {
			/* LCOV_EXCL_START */
			return -1;
			/* LCOV_EXCL_STOP */
		}
	}
#endif

#if HAVE_SYNC_FILE_RANGE && HAVE_POSIX_FADVISE
	if (discard_size != 0) {
		int ret;

		/* send the data to the disk and wait until it's written */
		ret = sync_file_range(f, discard_offset, discard_size, SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER);
		if (ret != 0) {
			/* LCOV_EXCL_START */
			return -1;
			/* LCOV_EXCL_STOP */
		}

		/* flush the data from the cache */
		ret = posix_fadvise_wrapper(f, discard_offset, discard_size, POSIX_FADV_DONTNEED);
		if (ret == ENOSYS) {
			/* call is not supported */
			ret = 0;
		}
		if (ret != 0) {
			/* LCOV_EXCL_START */
			errno = ret; /* posix_fadvise return the error code */
			return -1;
			/* LCOV_EXCL_STOP */
		}
	}
#endif

	return 0;
}

int advise_read(struct advise_struct* advise, int f, data_off_t offset, data_off_t size)
{
	(void)advise;
	(void)f;
	(void)offset;
	(void)size;

#if HAVE_POSIX_FADVISE
	if (advise->mode == ADVISE_DISCARD
		|| advise->mode == ADVISE_DISCARD_WINDOW
	) {
		int ret;

		/* flush the data from the cache */
		ret = posix_fadvise_wrapper(f, offset, size, POSIX_FADV_DONTNEED);
		if (ret == ENOSYS) {
			/* call is not supported */
			ret = 0;
		}
		if (ret != 0) {
			/* LCOV_EXCL_START */
			errno = ret; /* posix_fadvise return the error code */
			return -1;
			/* LCOV_EXCL_STOP */
		}
	}
#endif

	/*
	 * Here we cannot call posix_fadvise(..., POSIX_FADV_WILLNEED) for the next block
	 * because it may be blocking.
	 *
	 * Ted Ts'o "posix_fadvise(POSIX_FADV_WILLNEED) waits before returning?"
	 * https://lkml.org/lkml/2010/12/6/122
	 * ---
	 * readahead and posix_fadvise(POSIX_FADV_WILLNEED) work exactly the same
	 * way, and in fact share mostly the same code path (see
	 * force_page_cache_readahead() in mm/readahead.c).
	 *
	 * They are asynchronous in that there is no guarantee the pages will be
	 * in the page cache by the time they return.  But at the same time, they
	 * are not guaranteed to be non-blocking.  That is, the work of doing the
	 * readahead does not take place in a kernel thread.  So if you try to
	 * request I/O than will fit in the request queue, the system call will
	 * block until some I/O is completed so that more I/O requested cam be
	 * loaded onto the request queue.
	 *
	 * The only way to fix this would be to either put the work on a kernel
	 * thread (i.e., some kind of workqueue) or in a userspace thread.  For
	 * ion programmer wondering what to do today, I'd suggest the
	 * latter since it will be more portable across various kernel versions.
	 *
	 * This does leave the question about whether we should change the kernel
	 * to allow readahead() and posix_fadvise(POSIX_FADV_WILLNEED) to be
	 * non-blocking and do this work in a workqueue (or via some kind of
	 * callback/continuation scheme).  My worry is just doing this if a user
	 * application does something crazy, like request gigabytes and gigabytes
	 * of readahead, and then repented of their craziness, there should be a
	 * way of cancelling the readahead request.  Today, the user can just
	 * kill the application.  But if we simply shove the work to a kernel
	 * thread, it becomes a lot harder to cancel the readahead request.  We'd
	 * have to invent a new API, and then have a way to know whether the user
	 * has access to kill a particular readahead request, etc.
	 * ---
	 */

	return 0;
}

void advise_close(struct advise_struct* advise, int f)
{
	data_off_t flush_offset;
	data_off_t flush_size;
	data_off_t discard_offset;
	data_off_t discard_size;

	(void)advise;
	(void)f;
	(void)flush_offset;
	(void)flush_size;
	(void)discard_offset;
	(void)discard_size;

	flush_offset = 0;
	flush_size = 0;
	discard_offset = 0;
	discard_size = 0;

	/* assume it's a write if advise->dirty_begin != advise->dirty_end */
	switch (advise->mode) {
	case ADVISE_FLUSH_WINDOW :
		flush_offset = advise->dirty_begin;
		flush_size = advise->dirty_end - advise->dirty_begin;
		break;
	case ADVISE_DISCARD_WINDOW :
		discard_offset = advise->dirty_begin;
		discard_size = advise->dirty_end - advise->dirty_begin;
		break;
	}

#if HAVE_SYNC_FILE_RANGE
	if (flush_size != 0) {
		/* start writing immediately */
		sync_file_range(f, flush_offset, flush_size, SYNC_FILE_RANGE_WRITE);
	}
#endif

#if HAVE_SYNC_FILE_RANGE && HAVE_POSIX_FADVISE
	if (discard_size != 0) {
		int ret;

		/* send the data to the disk and wait until it's written */
		ret = sync_file_range(f, discard_offset, discard_size, SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER);
		if (ret != 0) {
			/* LCOV_EXCL_START */
			return;
			/* LCOV_EXCL_STOP */
		}

		/* flush the data from the cache */
		posix_fadvise_wrapper(f, discard_offset, discard_size, POSIX_FADV_DONTNEED);
	}
#endif

	advise->dirty_begin = 0;
	advise->dirty_end = 0;
}

/****************************************************************************/
/* memory */

/**
 * Cumulative amount of memory allocated.
 */
static size_t mcounter;

size_t malloc_counter_get(void)
{
	size_t ret;

	lock_memory();

	ret = mcounter;

	unlock_memory();

	return ret;
}

void malloc_counter_inc(size_t inc)
{
	lock_memory();

	mcounter += inc;

	unlock_memory();
}

/* LCOV_EXCL_START */
static ssize_t malloc_print(int f, const char* str)
{
	ssize_t len = 0;

	while (str[len])
		++len;
	return write(f, str, len);
}
/* LCOV_EXCL_STOP */

/* LCOV_EXCL_START */
static ssize_t malloc_printn(int f, size_t value)
{
	char buf[32];
	int i;

	if (!value)
		return write(f, "0", 1);

	i = sizeof(buf);
	while (value) {
		buf[--i] = (value % 10) + '0';
		value /= 10;
	}

	return write(f, buf + i, sizeof(buf) - i);
}
/* LCOV_EXCL_STOP */

/* LCOV_EXCL_START */
void malloc_fail(size_t size)
{
	/* don't use printf to avoid any possible extra allocation */
	int f = 2; /* stderr */

	malloc_print(f, "Failed for Low Memory!\n");
	malloc_print(f, "Allocating ");
	malloc_printn(f, size);
	malloc_print(f, " bytes.\n");
	malloc_print(f, "Already allocated ");
	malloc_printn(f, malloc_counter_get());
	malloc_print(f, " bytes.\n");
	if (sizeof(void*) == 4) {
		malloc_print(f, "You are currently using a 32 bits executable.\n");
		malloc_print(f, "If you have more than 4GB of memory, please upgrade to a 64 bits one.\n");
	}
}
/* LCOV_EXCL_STOP */

void* malloc_nofail(size_t size)
{
	void* ptr = malloc(size);

	if (!ptr) {
		/* LCOV_EXCL_START */
		malloc_fail(size);
		exit(EXIT_FAILURE);
		/* LCOV_EXCL_STOP */
	}

#ifndef CHECKER /* Don't preinitialize when running for valgrind */
	/* Here we preinitialize the memory to ensure that the OS is really allocating it */
	/* and not only reserving the addressable space. */
	/* Otherwise we are risking that the OOM (Out Of Memory) killer in Linux will kill the process. */
	/* Filling the memory doesn't ensure to disable OOM, but it increase a lot the chances to */
	/* get a real error from malloc() instead than a process killed. */
	/* Note that calloc() doesn't have the same effect. */
	memset(ptr, 0xA5, size);
#endif

	malloc_counter_inc(size);

	return ptr;
}

void* calloc_nofail(size_t count, size_t size)
{
	void* ptr;

	size *= count;

	/* see the note in malloc_nofail() of why we don't use calloc() */
	ptr = malloc(size);

	if (!ptr) {
		/* LCOV_EXCL_START */
		malloc_fail(size);
		exit(EXIT_FAILURE);
		/* LCOV_EXCL_STOP */
	}

	memset(ptr, 0, size);

	malloc_counter_inc(size);

	return ptr;
}

char* strdup_nofail(const char* str)
{
	size_t size;
	char* ptr;

	size = strlen(str) + 1;

	ptr = malloc(size);

	if (!ptr) {
		/* LCOV_EXCL_START */
		malloc_fail(size);
		exit(EXIT_FAILURE);
		/* LCOV_EXCL_STOP */
	}

	memcpy(ptr, str, size);

	malloc_counter_inc(size);

	return ptr;
}

/****************************************************************************/
/* smartctl */

/**
 * Match a string with the specified pattern.
 * Like sscanf() a space match any sequence of spaces.
 * Return 0 if it matches.
 */
static int smatch(const char* str, const char* pattern)
{
	while (*pattern) {
		if (isspace((unsigned char)*pattern)) {
			++pattern;
			while (isspace((unsigned char)*str))
				++str;
		} else if (*pattern == *str) {
			++pattern;
			++str;
		} else
			return -1;
	}

	return 0;
}

int is_sep(char c)
{
	return c == '.'
	       || c == ','
	       || c == '\''
	       || c == ' ' /* 0xa0 converted by space for no code page */
	       || c == (char)0xa0;  /* french */
}

/*
 * snumber()
 *
 * Matches a literal prefix (the second argument), then parses the following
 * unsigned 64-bit integer, allowing thousand separators.
 *
 * Whitespace is flexible around the prefix and before the number.
 * Commas are ignored during number parsing but basic validation is applied.
 *
 * Returns:
 *   1 - successfully parsed a valid uint64_t
 *   0 - prefix mismatch, no digits found, invalid format or overflow
 */
int snumber(const char* str, const char* prefix, uint64_t* value)
{
	const char* s = str;
	const char* f = prefix;

	/* match literal prefix, tolerating whitespace differences */
	while (*f) {
		/* skip whitespace in both strings */
		while (isspace((unsigned char)*f))
			++f;
		while (isspace((unsigned char)*s))
			++s;

		if (*f == 0)
			break;
		if (*s != *f)
			return 0; /* prefix mismatch */

		++s;
		++f;
	}

	/* skip whitespace before the number starts */
	while (isspace((unsigned char)*s))
		++s;

	uint64_t num = 0;
	int digits_seen = 0;
	int sep_seen = 0;

	/* parse digits, ignoring commas */
	while (*s) {
		if (*s >= '0' && *s <= '9') {
			unsigned digit = (unsigned)(*s - '0');

			/* check for uint64_t overflow before multiplying */
			if (num > (UINT64_MAX - digit) / 10ULL)
				return 0;

			num = num * 10ULL + digit;
			++digits_seen;
			sep_seen = 0;
		} else if (is_sep(*s)) {
			/* disallow leading separators */
			if (digits_seen == 0)
				return 0;

			/* if two consecutive separators, stop parsing */
			if (sep_seen)
				break;

			sep_seen = 1;
		} else {
			/* any other character ends the number */
			break;
		}

		++s;
	}

	/* require at least one digit */
	if (digits_seen == 0)
		return 0;

	*value = num;
	return 1;
}

int smartctl_attribute(FILE* f, const char* file, const char* name, struct smart_attr* smart, uint64_t* info, char* serial, char* family, char* model, char* inter)
{
	unsigned i;
	int inside;
	struct smart_attr dummy_smart[SMART_COUNT];
	uint64_t dummy_info[INFO_COUNT];
	char dummy_serial[SMART_MAX];
	char dummy_family[SMART_MAX];
	char dummy_model[SMART_MAX];
	char dummy_interface[SMART_MAX];

	/* dummy attributes */
	if (!smart)
		smart = dummy_smart;
	if (!info)
		info = dummy_info;
	if (!serial)
		serial = dummy_serial;
	if (!family)
		family = dummy_family;
	if (!model)
		model = dummy_model;
	if (!inter)
		inter = dummy_interface;

	/* preclear attributes */
	*serial = 0;
	*family = 0;
	*model = 0;
	*inter = 0;
	memset(smart, 0, sizeof(struct smart_attr) * SMART_COUNT);
	for (i = 0; i < SMART_COUNT; ++i) {
		smart[i].raw = SMART_UNASSIGNED;
		smart[i].norm = SMART_UNASSIGNED;
		smart[i].worst = SMART_UNASSIGNED;
		smart[i].thresh = SMART_UNASSIGNED;
	}
	for (i = 0; i < INFO_COUNT; ++i)
		info[i] = SMART_UNASSIGNED;

	/* read the file */
	inside = 0;
	while (1) {
		char buf[256];
		unsigned id;
		uint64_t raw;
		char* s;

		s = fgets(buf, sizeof(buf), f);
		if (s == 0)
			break;

		/* remove extraneous chars */
		s = strpolish(buf);

		log_tag("smartctl:%s:%s:out: %s\n", file, name, s);

		/* skip initial spaces */
		while (isspace((unsigned char)*s))
			++s;

		if (*s == 0) {
			inside = 0;
			/* common */
		} else if (smatch(s, "Rotation Rate: Solid State") == 0) {
			info[INFO_ROTATION_RATE] = 0;
		} else if (sscanf(s, "Rotation Rate: %" SCNu64, &info[INFO_ROTATION_RATE]) == 1) {
		} else if (snumber(s, "User Capacity:", &raw) == 1) {
			info[INFO_SIZE] = raw;
		} else if (sscanf(s, "Model Family: %63[^\n]", family) == 1) {
			strtrim(family);
		} else if (sscanf(s, "Device Model: %63[^\n]", model) == 1) {
			strtrim(model);
		} else if (sscanf(s, "Serial number: %63s", serial) == 1) { /* SCSI */
			strtrim(serial);
		} else if (sscanf(s, "Serial Number: %63s", serial) == 1) { /*ATA / NVMe */
			strtrim(serial);
/* SCSI */
/*
   Vendor:               WD
   Product:              WD4001FYYG-01SL3
   Revision:             VR08
   Compliance:           SPC-4
   User Capacity:        4,000,787,030,016 bytes [4.00 TB]
   Logical block size:   512 bytes
   Rotation Rate:        7200 rpm
   Form Factor:          3.5 inches
   Logical Unit id:      0x50000c0f01f55dd0
   Serial number:        WMC1F0D41KD5
   Device type:          disk
   Transport protocol:   SAS (SPL-3)
   Local Time is:        Fri Jan 28 14:14:51 2022 CET
   SMART support is:     Available - device has SMART capability.
   SMART support is:     Enabled
   Temperature Warning:  Enabled

   Current Drive Temperature:	 38 C
   Drive Trip Temperature:		65 C

   Manufactured in week 25 of year 2012
   Specified cycle count over device lifetime:  10000
   Accumulated start-stop cycles:  103
   Specified load-unload count over device lifetime:  300000
   Accumulated load-unload cycles:  103
   Elements in grown defect list: 5

   Vendor (Seagate) cache information
   Blocks sent to initiator = 569127595
   Blocks received from initiator = 2633690060
   Blocks read from cache and sent to initiator = 1881027254
   Number of read and write commands whose size <= segment size = 181284019
   Number of read and write commands whose size > segment size = 0

   Vendor (Seagate/Hitachi) factory information
   number of hours powered up = 25024.80
   number of minutes until next internal SMART test = 42

 */
		} else if (sscanf(s, "Transport protocol: %63[^\n]", inter) == 1) {
			if (strcmp(inter, "Fibre channel (FCP-4)") == 0)
				strcpy(inter, "Fibre");
			else if (strcmp(inter, "SSA") == 0)
				strcpy(inter, "SSA");
			else if (strcmp(inter, "IEEE 1394 (SBP-3)") == 0)
				strcpy(inter, "FireWire");
			else if (strcmp(inter, "RDMA (SRP)") == 0)
				strcpy(inter, "SCSI");
			else if (strcmp(inter, "iSCSI") == 0)
				strcpy(inter, "iSCSI");
			else if (strcmp(inter, "SAS (SPL-4)") == 0)
				strcpy(inter, "SAS");
			else if (strcmp(inter, "ADT") == 0)
				strcpy(inter, "SCSI");
			else if (strcmp(inter, "ATA (ACS-2)") == 0)
				strcpy(inter, "SATA");
			else if (strcmp(inter, "UAS") == 0)
				strcpy(inter, "USB");
			else if (strcmp(inter, "SOP") == 0)
				strcpy(inter, "NVMe");
			else if (strcmp(inter, "PCIe") == 0)
				strcpy(inter, "PCIe");
			else
				strcpy(inter, "SCSI");
			/* map generic attributes to SMART attr */
		} else if (sscanf(s, "Elements in grown defect list: %" SCNu64, &smart[SMART_REALLOCATED_SECTOR_COUNT].raw) == 1) {
			pathcpy(smart[SMART_REALLOCATED_SECTOR_COUNT].name, sizeof(smart[SMART_REALLOCATED_SECTOR_COUNT].name), "Elements_In_Grown_Defect_List");
		} else if (sscanf(s, "Current Drive Temperature: %" SCNu64, &smart[SMART_TEMPERATURE_CELSIUS].raw) == 1) {
			pathcpy(smart[SMART_TEMPERATURE_CELSIUS].name, sizeof(smart[SMART_TEMPERATURE_CELSIUS].name), "Current_Drive_Temperature");
		} else if (sscanf(s, "Drive Trip Temperature: %" SCNu64, &smart[SMART_AIRFLOW_TEMPERATURE_CELSIUS].raw) == 1) {
			pathcpy(smart[SMART_AIRFLOW_TEMPERATURE_CELSIUS].name, sizeof(smart[SMART_AIRFLOW_TEMPERATURE_CELSIUS].name), "Drive_Trip_Temperature");
		} else if (sscanf(s, "Accumulated start-stop cycles: %" SCNu64, &smart[SMART_START_STOP_COUNT].raw) == 1) {
			pathcpy(smart[SMART_START_STOP_COUNT].name, sizeof(smart[SMART_START_STOP_COUNT].name), "Accumulated_Start-Stop_Cycles");
		} else if (sscanf(s, "Accumulated load-unload cycles: %" SCNu64, &smart[SMART_LOAD_CYCLE_COUNT].raw) == 1) {
			pathcpy(smart[SMART_LOAD_CYCLE_COUNT].name, sizeof(smart[SMART_LOAD_CYCLE_COUNT].name), "Accumulated_Load-Unload_Cycles");
		} else if (sscanf(s, "  number of hours powered up = %" SCNu64, &smart[SMART_POWER_ON_HOURS].raw) == 1) { /* note "n" of "number" lower case */
			pathcpy(smart[SMART_POWER_ON_HOURS].name, sizeof(smart[SMART_POWER_ON_HOURS].name), "Number_Of_Hours_Powered_Up");
			/* special entries */
		} else if (sscanf(s, "Non-medium error count: %" SCNu64, &raw) == 1) {
			smart[SMART_ERROR_PROTOCOL].raw = raw;
/* ATA */
/*
   ID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE      UPDATED  WHEN_FAILED RAW_VALUE
   1 Raw_Read_Error_Rate     0x002f   200   200   051    Pre-fail  Always       -       0
   3 Spin_Up_Time            0x0027   172   169   021    Pre-fail  Always       -       8383
   4 Start_Stop_Count        0x0032   100   100   000    Old_age   Always       -       24
   5 Reallocated_Sector_Ct   0x0033   200   200   140    Pre-fail  Always       -       0
   7 Seek_Error_Rate         0x002e   200   200   000    Old_age   Always       -       0
   9 Power_On_Hours          0x0032   083   083   000    Old_age   Always       -       12471
   10 Spin_Retry_Count        0x0032   100   253   000    Old_age   Always       -       0
   11 Calibration_Retry_Count 0x0032   100   253   000    Old_age   Always       -       0
   12 Power_Cycle_Count       0x0032   100   100   000    Old_age   Always       -       24
   192 Power-Off_Retract_Count 0x0032   200   200   000    Old_age   Always       -       9
   193 Load_Cycle_Count        0x0032   157   157   000    Old_age   Always       -       131565
   194 Temperature_Celsius     0x0022   127   119   000    Old_age   Always       -       25
   196 Reallocated_Event_Count 0x0032   200   200   000    Old_age   Always       -       0
   197 Current_Pending_Sector  0x0032   200   200   000    Old_age   Always       -       0
   198 Offline_Uncorrectable   0x0030   100   253   000    Old_age   Offline      -       0
   199 UDMA_CRC_Error_Count    0x0032   200   200   000    Old_age   Always       -       0
   200 Multi_Zone_Error_Rate   0x0008   200   200   000    Old_age   Offline      -       0
 */
		} else if (smatch(s, "ATA Version is:") == 0) {
			strcpy(inter, "ATA");
		} else if (smatch(s, "SATA Version is:") == 0) {
			strcpy(inter, "SATA");
		} else if (smatch(s, "Transport Type:   PCIe") == 0) {
			strcpy(inter, "PCIe");
		} else if (smatch(s, "Transport Type:   Parallel") == 0) {
			strcpy(inter, "PATA");
			/* special entries */
		} else if (smatch(s, "No Errors Logged") == 0) {
			smart[SMART_ERROR_PROTOCOL].raw = 0;
		} else if (sscanf(s, "ATA Error Count: %" SCNu64, &raw) == 1) {
			smart[SMART_ERROR_PROTOCOL].raw = raw;
/* NVME */
/*
   Critical Warning:                   0x00
   Temperature:                        55 Celsius
   Available Spare:                    100%
   Available Spare Threshold:          10%
   Percentage Used:                    8%
   Data Units Read:                    213,006,510 [109 TB]
   Data Units Written:                 549,370,112 [281 TB]
   Host Read Commands:                 11,210,192,197
   Host Write Commands:                20,687,602,229
   Controller Busy Time:               14,055
   Power Cycles:                       39
   Power On Hours:                     4,204
   Unsafe Shutdowns:                   9
   Media and Data Integrity Errors:    0
   Error Information Log Entries:      1,479,242
   Warning  Comp. Temperature Time:    0
   Critical Comp. Temperature Time:    0
   Temperature Sensor 2:               75 Celsius
   Thermal Temp. 1 Total Time:         58745
 */
		} else if (smatch(s, "NVMe Version:") == 0) {
			strcpy(inter, "NVMe");
			/* special entries */
		} else if (snumber(s, "Media and Data Integrity Errors:", &raw) == 1) {
			smart[SMART_ERROR_MEDIUM].raw = raw;
		} else if (snumber(s, "Error Information Log Entries:", &raw) == 1) {
			smart[SMART_ERROR_PROTOCOL].raw = raw;
		} else if (sscanf(s, "Percentage Used: %" SCNu64 "%%", &raw) == 1) {
			smart[SMART_WEAR_LEVEL].raw = raw; /* take care that it can be greather than 100%, meaning that it's used over the expected lifetime */
			/* map generic attributes to SMART attr */
		} else if (sscanf(s, "Temperature: %" SCNu64, &smart[SMART_TEMPERATURE_CELSIUS].raw) == 1) {
			pathcpy(smart[SMART_TEMPERATURE_CELSIUS].name, sizeof(smart[SMART_TEMPERATURE_CELSIUS].name), "Temperature");
		} else if (snumber(s, "Power On Hours:", &smart[SMART_POWER_ON_HOURS].raw) == 1) {
			pathcpy(smart[SMART_POWER_ON_HOURS].name, sizeof(smart[SMART_POWER_ON_HOURS].name), "Power_On_Hours");
		} else if (snumber(s, "Power Cycles:", &smart[SMART_POWER_CYCLE_COUNT].raw) == 1) {
			pathcpy(smart[SMART_POWER_CYCLE_COUNT].name, sizeof(smart[SMART_POWER_CYCLE_COUNT].name), "Power_Cycles");
			/* map specific attributes to UNUSED SMART attr */
		} else if (sscanf(s, "Critical Warning: %" SCNx64, &smart[SMART_NVME_CRITICAL_WARNING].raw) == 1) {
			pathcpy(smart[SMART_NVME_CRITICAL_WARNING].name, sizeof(smart[SMART_NVME_CRITICAL_WARNING].name), "Critical_Warning");
		} else if (sscanf(s, "Available Spare: %" SCNu64 "%%", &smart[SMART_NVME_AVAILABLE_SPARE].raw) == 1) {
			pathcpy(smart[SMART_NVME_AVAILABLE_SPARE].name, sizeof(smart[SMART_NVME_AVAILABLE_SPARE].name), "Available_Spare");
		} else if (snumber(s, "Data Units Read:", &smart[SMART_NVME_DATA_UNITS_READ].raw) == 1) {
			pathcpy(smart[SMART_NVME_DATA_UNITS_READ].name, sizeof(smart[SMART_NVME_DATA_UNITS_READ].name), "Data_Units_Read");
		} else if (snumber(s, "Data Units Written:", &smart[SMART_NVME_DATA_UNITS_WRITTEN].raw) == 1) {
			pathcpy(smart[SMART_NVME_DATA_UNITS_WRITTEN].name, sizeof(smart[SMART_NVME_DATA_UNITS_WRITTEN].name), "Data_Units_Written");
		} else if (snumber(s, "Host Read Commands:", &smart[SMART_NVME_HOST_READ_COMMANDS].raw) == 1) {
			pathcpy(smart[SMART_NVME_HOST_READ_COMMANDS].name, sizeof(smart[SMART_NVME_HOST_READ_COMMANDS].name), "Host_Read_Commands");
		} else if (snumber(s, "Host Write Commands:", &smart[SMART_NVME_HOST_WRITE_COMMANDS].raw) == 1) {
			pathcpy(smart[SMART_NVME_HOST_WRITE_COMMANDS].name, sizeof(smart[SMART_NVME_HOST_WRITE_COMMANDS].name), "Host_Write_Commands");
		} else if (snumber(s, "Controller Busy Time:", &smart[SMART_NVME_CONTROLLER_BUSY_TIME].raw) == 1) {
			pathcpy(smart[SMART_NVME_CONTROLLER_BUSY_TIME].name, sizeof(smart[SMART_NVME_CONTROLLER_BUSY_TIME].name), "Controller_Busy_Time");
		} else if (snumber(s, "Unsafe Shutdowns:", &smart[SMART_NVME_UNSAFE_SHUTDOWNS].raw) == 1) {
			pathcpy(smart[SMART_NVME_UNSAFE_SHUTDOWNS].name, sizeof(smart[SMART_NVME_UNSAFE_SHUTDOWNS].name), "Unsafe_Shutdowns");
			/* smartctl doesn't print this with command, but %d */
		} else if (snumber(s, "Warning  Comp. Temperature Time:", &smart[SMART_NVME_WARNING_COMP_TEMPERATURE_TIME].raw) == 1) {
			pathcpy(smart[SMART_NVME_WARNING_COMP_TEMPERATURE_TIME].name, sizeof(smart[SMART_NVME_WARNING_COMP_TEMPERATURE_TIME].name), "Warning_Comp_Temperature_Time");
			/* smartctl doesn't print this with command, but %d */
		} else if (snumber(s, "Critical Comp. Temperature Time:", &smart[SMART_NVME_CRITICAL_COMP_TEMPERATURE_TIME].raw) == 1) {
			pathcpy(smart[SMART_NVME_CRITICAL_COMP_TEMPERATURE_TIME].name, sizeof(smart[SMART_NVME_CRITICAL_COMP_TEMPERATURE_TIME].name), "Critical_Comp_Temperature_Time");
			/* ATA Attributes table */
		} else if (smatch(s, "ID#") == 0) {
			inside = 1;
		} else if (inside) {
			char id_name[128] = { 0 };
			char type[64] = { 0 };
			char updated[64] = { 0 };
			char when_failed[64] = { 0 };
			char norm[64] = { 0 };
			char worst[64] = { 0 };
			char thresh[64] = { 0 };
			uint64_t min, max, avg;
			int format_minmax = 0;
			int format_avg = 0;
			int flags;
			unsigned long v;
			char* e;

			/* 194 Temperature_Celsius     0x0002   240   240   000    Old_age   Always       -       27 (Min/Max 16/60) */
			if (sscanf(s, "%u %127s %*s %63s %63s %63s %63s %63s %63s %" SCNu64 " (Min/Max %" SCNu64 "/%" SCNu64 ")", &id, id_name, norm, worst, thresh, type, updated, when_failed, &raw, &min, &max) == 11) {
				format_minmax = 1;
				/*  3 Spin_Up_Time            0x0007   149   149   024    Pre-fail  Always       -       442 (Average 441) */
			} else if (sscanf(s, "%u %127s %*s %63s %63s %63s %63s %63s %63s %" SCNu64 " (Average %" SCNu64 ")", &id, id_name, norm, worst, thresh, type, updated, when_failed, &raw, &avg) == 10) {
				format_avg = 1;
			} else if (sscanf(s, "%u %127s %*s %63s %63s %63s %63s %63s %63s %" SCNu64, &id, id_name, norm, worst, thresh, type, updated, when_failed, &raw) == 9) {
			} else {
				log_fatal(EEXTERNAL, "Invalid smartctl line '%s'.\n", s);
				return -1;
			}

			if (id >= 256) {
				/* LCOV_EXCL_START */
				log_fatal(EEXTERNAL, "Invalid SMART id '%u'.\n", id);
				return -1;
				/* LCOV_EXCL_STOP */
			}

			flags = 0;
			if (strcmp(type, "Pre-fail") == 0)
				flags |= SMART_ATTR_TYPE_PREFAIL;
			else if (strcmp(type, "Old_age") == 0)
				flags |= SMART_ATTR_TYPE_OLDAGE;
			if (strcmp(updated, "Always") == 0)
				flags |= SMART_ATTR_UPDATE_ALWAYS;
			else if (strcmp(updated, "Offline") == 0)
				flags |= SMART_ATTR_UPDATE_OFFLINE;
			if (strcmp(when_failed, "FAILING_NOW") == 0)
				flags |= SMART_ATTR_WHEN_FAILED_NOW;
			else if (strcmp(when_failed, "In_the_past") == 0)
				flags |= SMART_ATTR_WHEN_FAILED_PAST;
			else if (strcmp(when_failed, "-") == 0)
				flags |= SMART_ATTR_WHEN_FAILED_NEVER;

			/* revert the min/max decoding done by smartctl */
			if (format_minmax
				&& raw <= 0xFFFFUL
				&& min <= 0xFFFFUL
				&& max <= 0xFFFFUL) {
				raw |= min << 16;
				raw |= max << 32;
			}

			/* revert the avg decoding done by smartctl */
			if (format_avg
				&& raw <= 0xFFFFUL
				&& avg <= 0xFFFFUL) {
				raw |= avg << 16;
			}

			smart[id].raw = raw;

			v = strtoul(norm, &e, 10);
			if (e != norm || *e == 0) {
				smart[id].norm = v;
			} else {
				smart[id].norm = SMART_UNASSIGNED;
			}

			v = strtoul(worst, &e, 10);
			if (e != worst || *e == 0) {
				smart[id].worst = v;
			} else {
				smart[id].worst = SMART_UNASSIGNED;
			}

			v = strtoul(thresh, &e, 10);
			if (e != thresh || *e == 0) {
				smart[id].thresh = v;
			} else {
				smart[id].thresh = SMART_UNASSIGNED;
			}

			smart[id].flags = flags;
			pathcpy(smart[id].name, sizeof(smart[id].name), id_name);

			/*
			 * Map normalized health percentage to our unified wear level for SSDs
			 * 177: Wear_Leveling_Count (Samsung/Crucial)
			 * 231: SSD_Life_Left (Kingston/WD)
			 * 233: Media_Wearout_Indicator (Intel)
			 */
			if (smart[id].norm != SMART_UNASSIGNED && smart[id].norm <= 100) {
				if (strcmp(id_name, "Wear_Leveling_Count") == 0
					|| strcmp(id_name, "SSD_Life_Left") == 0
					|| strcmp(id_name, "Media_Wearout_Indicator") == 0) {
					smart[SMART_WEAR_LEVEL].raw = 100 - smart[id].norm;
				}
			}
		}
	}

	return 0;
}

int smartctl_flush(FILE* f, const char* file, const char* name)
{
	/* read the file */
	while (1) {
		char buf[256];
		char* s;

		s = fgets(buf, sizeof(buf), f);
		if (s == 0)
			break;

		/* remove extraneous chars */
		s = strpolish(buf);

		log_tag("smartctl:%s:%s:out: %s\n", file, name, s);
	}

	return 0;
}

int smart_temp(devinfo_t* devinfo)
{
	uint64_t t = devinfo->smart[SMART_TEMPERATURE_CELSIUS].raw;
	if (t == SMART_UNASSIGNED)
		t = devinfo->smart[SMART_AIRFLOW_TEMPERATURE_CELSIUS].raw;
	if (t == SMART_UNASSIGNED)
		return -1;

	/* mask out min/max values */
	t &= 0xFFFFUL;

	if (t == 0)
		return -1;
	if (t > 100)
		return -1;

	return t;
}

/****************************************************************************/
/* thread */

#if HAVE_THREAD
void thread_mutex_init(thread_mutex_t* mutex)
{
	if (pthread_mutex_init(mutex, 0) != 0) {
		/* LCOV_EXCL_START */
		fprintf(stderr, "Failed call to pthread_mutex_init().\n"); /* cannot use log_fatal() because it needs locks */
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void thread_mutex_destroy(thread_mutex_t* mutex)
{
	if (pthread_mutex_destroy(mutex) != 0) {
		/* LCOV_EXCL_START */
		fprintf(stderr, "Failed call to thread_mutex_destroy().\n"); /* cannot use log_fatal() because it needs locks */
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void thread_mutex_lock(thread_mutex_t* mutex)
{
	if (pthread_mutex_lock(mutex) != 0) {
		/* LCOV_EXCL_START */
		fprintf(stderr, "Failed call to pthread_mutex_lock().\n"); /* cannot use log_fatal() because it needs locks */
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void thread_mutex_unlock(thread_mutex_t* mutex)
{
	if (pthread_mutex_unlock(mutex) != 0) {
		/* LCOV_EXCL_START */
		fprintf(stderr, "Failed call to pthread_mutex_unlock().\n"); /* cannot use log_fatal() because it needs locks */
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void thread_cond_init(thread_cond_t* cond)
{
	if (pthread_cond_init(cond, 0) != 0) {
		/* LCOV_EXCL_START */
		fprintf(stderr, "Failed call to pthread_cond_init().\n"); /* cannot use log_fatal() because it needs locks */
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void thread_cond_destroy(thread_cond_t* cond)
{
	if (pthread_cond_destroy(cond) != 0) {
		/* LCOV_EXCL_START */
		fprintf(stderr, "Failed call to pthread_cond_destroy().\n"); /* cannot use log_fatal() because it needs locks */
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void thread_cond_signal(thread_cond_t* cond)
{
	if (pthread_cond_signal(cond) != 0) {
		/* LCOV_EXCL_START */
		fprintf(stderr, "Failed call to pthread_cond_signal().\n"); /* cannot use log_fatal() because it needs locks */
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void thread_cond_broadcast(thread_cond_t* cond)
{
	if (pthread_cond_broadcast(cond) != 0) {
		/* LCOV_EXCL_START */
		fprintf(stderr, "Failed call to pthread_cond_broadcast().\n"); /* cannot use log_fatal() because it needs locks */
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void thread_cond_wait(thread_cond_t* cond, thread_mutex_t* mutex)
{
	if (pthread_cond_wait(cond, mutex) != 0) {
		/* LCOV_EXCL_START */
		fprintf(stderr, "Failed call to pthread_cond_wait().\n"); /* cannot use log_fatal() because it needs locks */
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

/**
 * Implementation note about conditional variables.
 *
 * The conditional variables can be signaled inside or outside the mutex,
 * what is better it's debatable but in general doing that outside the mutex,
 * reduces the number of context switches.
 *
 * But when testing with helgrind and drd, this disallows such tools to
 * to see the dependency between the signal and the wait.
 *
 * To avoid it we signal everything inside the mutex. And we do this in both
 * test mode (with CHECKER defined) and release mode (CHECKER not defined),
 * to be on the safe side and avoid any difference in behaviour between test and
 * release.
 *
 * Here some interesting discussion:
 *
 * Condvars: signal with mutex locked or not?
 * http://www.domaigne.com/blog/computing/condvars-signal-with-mutex-locked-or-not/
 *
 * Calling pthread_cond_signal without locking mutex
 * http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex/4544494#4544494
 */

/**
 * Control when to signal the condition variables.
 */
int thread_cond_signal_outside = 0;

void thread_cond_signal_and_unlock(thread_cond_t* cond, thread_mutex_t* mutex)
{
	if (thread_cond_signal_outside) {
		/* without the thread checker unlock before signaling, */
		/* this reduces the number of context switches */
		thread_mutex_unlock(mutex);
	}

	thread_cond_signal(cond);

	if (!thread_cond_signal_outside) {
		/* with the thread checker unlock after signaling */
		/* to make explicit the condition and mutex relation */
		thread_mutex_unlock(mutex);
	}
}

void thread_cond_broadcast_and_unlock(thread_cond_t* cond, thread_mutex_t* mutex)
{
	if (thread_cond_signal_outside) {
		/* without the thread checker unlock before signaling, */
		/* this reduces the number of context switches */
		thread_mutex_unlock(mutex);
	}

	thread_cond_broadcast(cond);

	if (!thread_cond_signal_outside) {
		/* with the thread checker unlock after signaling */
		/* to make explicit the condition and mutex relation */
		thread_mutex_unlock(mutex);
	}
}

void thread_create(thread_id_t* thread, void* (*func)(void*), void* arg)
{
	if (pthread_create(thread, 0, func, arg) != 0) {
		/* LCOV_EXCL_START */
		log_fatal(EINTERNAL, "Failed call to pthread_create().\n");
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void thread_join(thread_id_t thread, void** retval)
{
	if (pthread_join(thread, retval) != 0) {
		/* LCOV_EXCL_START */
		log_fatal(EINTERNAL, "Failed call to pthread_join().\n");
		os_abort();
		/* LCOV_EXCL_STOP */
	}
}

void thread_yield(void)
{
#ifdef __MINGW32__
	Sleep(0);
#else
	sched_yield();
#endif
}
#endif

int thread_key_create(thread_key_t* key, void (*destructor)(void*))
{
	return pthread_key_create(key, destructor);
}

int thread_key_delete(thread_key_t key)
{
	return pthread_key_delete(key);
}

void* thread_getspecific(thread_key_t key)
{
	return pthread_getspecific(key);
}

int thread_setspecific(thread_key_t key, void* value)
{
	return pthread_setspecific(key, value);
}

/****************************************************************************/
/* wnmatch */

/**
 * Helper function for case-insensitive character comparison
 */
static inline int char_match(char p, char t)
{
#ifdef WIN32
	return tolower((unsigned char)p) == tolower((unsigned char)t);
#else
	return p == t;
#endif
}

/**
 * Match character class [...]
 * Return 0 if NOT matched
 */
static const char* match_class(const char* p, char t)
{
	int negate = 0;
	int matched = 0;

	if (*p == '!' || *p == '^') {
		negate = 1;
		++p;
	}

	while (*p && *p != ']') {
		if (p[1] == '-' && p[2] != ']' && p[2] != 0) {
			/* range [a-z] */
			char start = *p;
			char end = p[2];
#ifdef WIN32
			start = tolower((unsigned char)start);
			end = tolower((unsigned char)end);
			t = tolower((unsigned char)t);
#endif
			if (t >= start && t <= end)
				matched = 1;
			p += 3;
		} else {
			/* single character */
			if (char_match(*p, t))
				matched = 1;
			++p;
		}
	}

	if (*p == ']')
		++p;

	if (negate)
		matched = !matched;
	if (!matched)
		return 0;

	return p;
}

int wnmatch_sub(const char* p, const char* t, int match_sub)
{
	char p1 = 0; /* previous char */
	while (*p) {
		char p0 = *p;
		switch (p0) {
		case '?' :
			/* ? matches any single character except / */
			if (*t == 0 || *t == '/')
				return 1;
			++p;
			++t;
			break;
		case '*' :
			/* check for ** */
			if (p[1] == '*') {
				/* skip the ** */
				p += 2;

				/* munge all * */
				while (*p == '*')
					++p;

				/* if its not near a slash, it's like a single * */
				if (p1 == '/' || *p == '/') {
					/* a ** at end matches everything */
					if (*p == 0)
						return 0;

					/*
					 * In between slashes matches to nothing
					 *
					 * Check for /##/ or ^##/ (^ start of string)
					 *
					 * This is required for:
					 * "/##/file.txt" matching "/file.txt"
					 * "##/file.txt" matching "file.txt"
					 * "x##/file.txt" NOT matching "xfile.txt"
					 */
					if (*p == '/' && (p1 == 0 || p1 == '/')) {
						/* try reducing to nothing */
						if (wnmatch_sub(p + 1, t, match_sub) == 0)
							return 0;
						/* otherwise / should match in the text */
					}

					/* try matching with 0 or more characters */
					while (*t) {
						if (wnmatch_sub(p, t, match_sub) == 0)
							return 0;
						++t;
					}

					/* try matching at the end */
					return wnmatch_sub(p, t, match_sub);
				}
			} else {
				/* skip the * */
				++p;
			}

			/* a * at end matches rest of segment */
			if (*p == 0) {
				while (*t && *t != '/')
					++t;
				return *t != 0;
			}

			/* try matching with 0 or more characters */
			while (*t && *t != '/') {
				if (wnmatch_sub(p, t, match_sub) == 0)
					return 0;
				++t;
			}

			/* try matching at the end */
			return wnmatch_sub(p, t, match_sub);
		case '[' :
			/* character class */
			if (*t == 0 || *t == '/')
				return 1;

			p = match_class(p + 1, *t);
			if (!p)
				return 1;

			++t;
			break;
		default :
			/* literal character */
			if (*t == 0 || !char_match(*p, *t))
				return 1;
			++p;
			++t;
			break;
		}
		p1 = p0;
	}

	/* if we match sub directory */
	if (match_sub) {
		/* match successfully only if we are at a directory border */
		return *t != '/';
	} else {
		/* match successfully if we've consumed all text */
		return *t != 0;
	}
}

