// SPDX-FileCopyrightText: 2009-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_util.h>
#include <rz_userconf.h>
#include <stdlib.h>
#include <string.h>

#if defined(__NetBSD__)
#include <sys/param.h>
#include <sys/sysctl.h>
#endif /* defined(__NetBSD__) */

#if defined(__FreeBSD__)
#include <sys/param.h>
#include <sys/sysctl.h>
#endif /* defined(__FreeBSD__) */

#if defined(__DragonFly__)
#include <sys/param.h>
#include <sys/sysctl.h>
#endif /* defined(__DragonFly__) */

#if defined(__OpenBSD__)
#include <sys/sysctl.h>
#include <sys/stat.h>
#endif /* defined(__OpenBSD__) */

#if defined(__HAIKU__)
#include <kernel/image.h>
#include <sys/param.h>
#endif /* defined(__HAIKU__) */

#include <sys/types.h>
#include <rz_types.h>
#include <rz_util.h>
#include <rz_lib.h>

static char **env = NULL;

#if HAVE_DECL_ADDR_NO_RANDOMIZE
#include <sys/personality.h>
#endif /* HAVE_DECL_ADDR_NO_RANDOMIZE */

#if defined(__FreeBSD__) || defined(__DragonFly__)
#if HAVE_DECL_PROCCTL_ASLR_CTL
#include <sys/procctl.h>
#endif /* HAVE_DECL_PROCCTL_ASLR_CTL */
#include <sys/sysctl.h>
#endif /* defined(__FreeBSD__) || defined(__DragonFly__) */

#if HAVE_BACKTRACE
#include <execinfo.h>
#endif /* HAVE_BACKTRACE */

#if __APPLE__
#include <errno.h>

#if HAVE_ENVIRON
#include <execinfo.h>
#endif

// iOS don't have this we can't hardcode
// #include <crt_externs.h>
extern char ***_NSGetEnviron(void);
#ifndef PROC_PIDPATHINFO_MAXSIZE
#define PROC_PIDPATHINFO_MAXSIZE 1024
int proc_pidpath(int pid, void *buffer, ut32 buffersize);
// #  include <libproc.h>
#endif /* PROC_PIDPATHINFO_MAXSIZE */
#endif /* __APPLE__ */

#if __UNIX__
#include <sys/utsname.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
extern char **environ;

#ifdef __HAIKU__
#define Sleep sleep
#endif /* __HAIKU__ */
#endif /* __UNIX__ */

#if __WINDOWS__
#include <io.h>
#include <rz_windows.h>
#include <VersionHelpers.h>
#include <signal.h>
#define TMP_BUFSIZE 4096
#ifdef _MSC_VER
#include <psapi.h>
#include <dbghelp.h>
#include <process.h> // to allow getpid under windows msvc compilation
#include <direct.h> // to allow getcwd under windows msvc compilation
#endif /* _MSC_VER */

typedef BOOL(WINAPI *SetProcessMitigationPolicy_t)(
	PROCESS_MITIGATION_POLICY mitigation_policy,
	PVOID buffer,
	SIZE_T length);

typedef BOOL(WINAPI *GetProcessMitigationPolicy_t)(
	HANDLE h_process,
	PROCESS_MITIGATION_POLICY mitigation_policy,
	PVOID buffer,
	SIZE_T length);

#endif /* __WINDOWS__ */

/* For "openpty" family of funtcions */
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
#if defined(__APPLE__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <util.h>
#elif defined(__FreeBSD__) || defined(__DragonFly__)
#include <libutil.h>
#else
#include <pty.h>
#include <utmp.h>
#endif /* defined(__APPLE__) || defined(__NetBSD__) || defined(__OpenBSD__) */
#endif /* HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY */

RZ_LIB_VERSION(rz_util);

#ifdef __x86_64__
#ifdef _MSC_VER
#define RZ_SYS_ASM_START_ROP() \
	RZ_LOG_ERROR("rz_sys_run_rop: Unsupported architecture\n");
#else
#define RZ_SYS_ASM_START_ROP() \
	__asm__ __volatile__("leaq %0, %%rsp; ret" \
		: \
		: "m"(*bufptr));
#endif
#elif __i386__
#ifdef _MSC_VER
#define RZ_SYS_ASM_START_ROP() \
	__asm { \
		__asm lea esp, bufptr\
		__asm ret \
	}
#else
#define RZ_SYS_ASM_START_ROP() \
	__asm__ __volatile__("leal %0, %%esp; ret" \
		: \
		: "m"(*bufptr));
#endif
#else
#define RZ_SYS_ASM_START_ROP() \
	RZ_LOG_ERROR("rz_sys_run_rop: Unsupported architecture\n");
#endif

static const struct {
	const char *name;
	ut64 bit;
} arch_bit_array[] = {
	{ "x86", RZ_SYS_ARCH_X86 },
	{ "arm", RZ_SYS_ARCH_ARM },
	{ "ppc", RZ_SYS_ARCH_PPC },
	{ "m68k", RZ_SYS_ARCH_M68K },
	{ "java", RZ_SYS_ARCH_JAVA },
	{ "mips", RZ_SYS_ARCH_MIPS },
	{ "sparc", RZ_SYS_ARCH_SPARC },
	{ "xap", RZ_SYS_ARCH_XAP },
	{ "tms320", RZ_SYS_ARCH_TMS320 },
	{ "msil", RZ_SYS_ARCH_MSIL },
	{ "objd", RZ_SYS_ARCH_OBJD },
	{ "bf", RZ_SYS_ARCH_BF },
	{ "sh", RZ_SYS_ARCH_SH },
	{ "avr", RZ_SYS_ARCH_AVR },
	{ "dalvik", RZ_SYS_ARCH_DALVIK },
	{ "z80", RZ_SYS_ARCH_Z80 },
	{ "arc", RZ_SYS_ARCH_ARC },
	{ "i8080", RZ_SYS_ARCH_I8080 },
	{ "rar", RZ_SYS_ARCH_RAR },
	{ "lm32", RZ_SYS_ARCH_LM32 },
	{ "v850", RZ_SYS_ARCH_V850 },
	{ "tricore", RZ_SYS_ARCH_TRICORE },
};

#if __WINDOWS__
static char *sys_windows_error_msg(DWORD dw) {
	LPTSTR lpMsgBuf;
	if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
			    FORMAT_MESSAGE_FROM_SYSTEM |
			    FORMAT_MESSAGE_IGNORE_INSERTS,
		    NULL,
		    dw,
		    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		    (LPTSTR)&lpMsgBuf,
		    0, NULL)) {
		char *message = rz_sys_conv_win_to_utf8(lpMsgBuf);
		LocalFree(lpMsgBuf);
		rz_str_trim_tail(message);
		return message;
	}
	return NULL;
}
#endif /* __WINDOWS__ */

#if HAVE_SIGACTION
RZ_API int rz_sys_sigaction(int *sig, void (*handler)(int)) {
	struct sigaction sigact = {};
	int ret, i;

	if (!sig) {
		return -EINVAL;
	}

	sigact.sa_handler = handler;
	sigemptyset(&sigact.sa_mask);

	for (i = 0; sig[i] != 0; i++) {
		sigaddset(&sigact.sa_mask, sig[i]);
	}

	for (i = 0; sig[i] != 0; i++) {
		ret = sigaction(sig[i], &sigact, NULL);
		if (ret) {
			RZ_LOG_ERROR("Failed to set signal handler for signal '%d': %s\n", sig[i], strerror(errno));
			return ret;
		}
	}

	return 0;
}
#else
RZ_API int rz_sys_sigaction(int *sig, void (*handler)(int)) {
	if (!sig) {
		return -EINVAL;
	}
	size_t i;
	for (i = 0; sig[i] != 0; i++) {
		void (*ret)(int) = signal(sig[i], handler);
		if (ret == SIG_ERR) {
			RZ_LOG_ERROR("Failed to set signal handler for signal '%d': %s\n", sig[i], strerror(errno));
			return -1;
		}
	}
	return 0;
}
#endif

RZ_API int rz_sys_signal(int sig, void (*handler)(int)) {
	int s[2] = { sig, 0 };
	return rz_sys_sigaction(s, handler);
}

RZ_API void rz_sys_exit(int status, bool nocleanup) {
	if (nocleanup) {
		_exit(status);
	} else {
		exit(status);
	}
}

#if __WINDOWS__
static HANDLE sys_opendir(const char *path, WIN32_FIND_DATAW *entry) {
	rz_return_val_if_fail(path, NULL);
	wchar_t dir[MAX_PATH];
	wchar_t *wcpath = 0;
	static bool win_ver_initialized = false;
	static bool is_win_7_or_greater = false;
	if (!win_ver_initialized) {
		is_win_7_or_greater = IsWindows7OrGreater();
		win_ver_initialized = true;
	}
	if (!(wcpath = rz_utf8_to_utf16(path))) {
		return NULL;
	}
	swprintf(dir, MAX_PATH, L"%ls\\*.*", wcpath);
	free(wcpath);
	return FindFirstFileExW(dir, is_win_7_or_greater ? FindExInfoBasic : FindExInfoStandard, entry, FindExSearchNameMatch, NULL, 0);
}
#else
static DIR *sys_opendir(const char *path) {
	rz_return_val_if_fail(path, NULL);
	return opendir(path);
}
#endif

RZ_API RzList /*<char *>*/ *rz_sys_dir(const char *path) {
	RzList *list = NULL;
#if __WINDOWS__
	WIN32_FIND_DATAW entry;
	char *cfname;
	HANDLE fh = sys_opendir(path, &entry);
	if (fh == INVALID_HANDLE_VALUE) {
		// IFDGB eprintf ("Cannot open directory %ls\n", wcpath);
		return list;
	}
	list = rz_list_newf(free);
	if (list) {
		do {
			if ((cfname = rz_utf16_to_utf8(entry.cFileName))) {
				rz_list_append(list, cfname);
			}
		} while (FindNextFileW(fh, &entry));
	}
	FindClose(fh);
#else
	struct dirent *entry;
	DIR *dir = sys_opendir(path);
	if (dir) {
		list = rz_list_new();
		if (list) {
			list->free = free;
			while ((entry = readdir(dir))) {
				rz_list_append(list, rz_str_dup(entry->d_name));
			}
		}
		closedir(dir);
	}
#endif
	return list;
}

RZ_API char *rz_sys_cmd_strf(const char *fmt, ...) {
	char *ret, cmd[4096];
	va_list ap;
	va_start(ap, fmt);
	vsnprintf(cmd, sizeof(cmd), fmt, ap);
	ret = rz_sys_cmd_str(cmd, NULL, NULL);
	va_end(ap);
	return ret;
}

/**
 * \brief Print the backtrace at the point this function is called from.
 */
RZ_API void rz_sys_backtrace(void) {
#if HAVE_BACKTRACE
	void *array[10];
	size_t size = backtrace(array, 10);
	eprintf("Backtrace %zd stack frames.\n", size);
	backtrace_symbols_fd(array, size, 2);
#elif __APPLE__
	void **fp = (void **)__builtin_frame_address(0);
	void *saved_pc = __builtin_return_address(0);
	void *saved_fp = __builtin_frame_address(1);
	int depth = 0;

	eprintf("[%d] pc == %p fp == %p\n", depth++, saved_pc, saved_fp);
	fp = saved_fp;
	while (fp) {
		saved_fp = *fp;
		fp = saved_fp;
		if (!*fp) {
			break;
		}
		saved_pc = *(fp + 2);
		eprintf("[%d] pc == %p fp == %p\n", depth++, saved_pc, saved_fp);
	}
#else
#ifdef _MSC_VER
	SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS);

	HANDLE process = GetCurrentProcess();

	if (!SymInitialize(process, NULL, TRUE)) {
		eprintf("SymInitialize() failed (%d), no backtrace will be generated\n", GetLastError());
		return;
	}

	void *stack[64];
	ut16 frame_count = CaptureStackBackTrace(0, 64, stack, NULL);
	eprintf("Tracing %" PFMT32u " stack frames:\n", frame_count);

	ut8 *buffer = malloc(sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR));

	if (!buffer) {
		eprintf("malloc failed, no backtrace will be generated\n");
		SymCleanup(process);
		return;
	}

	SYMBOL_INFO *symbol = (SYMBOL_INFO *)buffer;
	symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
	symbol->MaxNameLen = MAX_SYM_NAME;

	for (ut16 i = 0; i < frame_count; i++) {
		ut64 offset_from_sym = 0;
		ut32 line_displacement = 0;
		IMAGEHLP_LINE64 line = { .SizeOfStruct = sizeof(IMAGEHLP_LINE64) };

		if (!SymFromAddr(process, (utptr)(stack[i]), &offset_from_sym, symbol)) {
			// Print only symbol name + pc/rip address
			eprintf("  [%" PFMT32u "]: 0x%" PFMT64x "\n", i, (ut64)(stack[i]));
			continue;
		}

		if (SymGetLineFromAddr64(process, (utptr)(stack[i]), &line_displacement, &line)) {
			// Source file and line details available
			eprintf("  [%" PFMT32u "]: %s() at %s:%" PFMT32u "\n", i, symbol->Name, line.FileName, line.LineNumber);
		} else {
			// Print only symbol name + offset from symbol
			eprintf("  [%" PFMT32u "]: %s()+0x%" PFMT64x "\n", i, symbol->Name, offset_from_sym);
		}
	}

	free(buffer);
	SymCleanup(process);
#else
#warning TODO: rz_sys_backtrace : unimplemented
#endif
#endif
}

/**
 * \brief Sleep for \p secs seconds
 */
RZ_API int rz_sys_sleep(int secs) {
	if (secs < 1) {
		return 0;
	}
#if HAVE_CLOCK_NANOSLEEP && defined(CLOCK_MONOTONIC)
	struct timespec rqtp;
	rqtp.tv_sec = secs;
	rqtp.tv_nsec = 0;
	return clock_nanosleep(CLOCK_MONOTONIC, 0, &rqtp, NULL);
#elif __UNIX__
	return sleep(secs);
#else
	Sleep(secs * 1000); // W32
	return 0;
#endif
}

/**
 * \brief Sleep for \p usecs microseconds
 */
RZ_API int rz_sys_usleep(int usecs) {
	if (usecs < 1) {
		return 0;
	}
#if HAVE_CLOCK_NANOSLEEP && defined(CLOCK_MONOTONIC)
	struct timespec rqtp;
	rqtp.tv_sec = usecs / 1000000;
	rqtp.tv_nsec = (usecs - (rqtp.tv_sec * 1000000)) * 1000;
	return clock_nanosleep(CLOCK_MONOTONIC, 0, &rqtp, NULL);
#elif __UNIX__
#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) && (__GLIBC__ <= 2) && (__GLIBC_MINOR__ <= 2)
	// Old versions of GNU libc return void for usleep
	usleep(usecs);
	return 0;
#else
	return usleep(usecs);
#endif
#else
	// w32 api uses milliseconds
	usecs /= 1000;
	Sleep(usecs); // W32
	return 0;
#endif
}

/**
 * \brief Clean all environment variables in the calling process.
 *
 * Please note that environment variables should not be used to store sensitive
 * info as they might be kept elsewhere and there is no access control over that
 * data.
 */
RZ_API int rz_sys_clearenv(void) {
#if __UNIX__
#if __APPLE__ && !HAVE_ENVIRON
	/* do nothing */
	if (!env) {
		rz_sys_env_init();
		return 0;
	}
	char **e = env;
	while (*e) {
		*e++ = NULL;
	}
#else
	if (!environ) {
		return 0;
	}
	while (*environ) {
		*environ++ = NULL;
	}
#endif
	return 0;
#else
#ifdef __WINDOWS__
	LPWCH env = GetEnvironmentStringsW();
	LPWCH var = env;
	while (*var) {
		wchar_t *eq = wcschr(var, L'=');
		if (!eq) {
			FreeEnvironmentStringsW(env);
			return -1;
		}
		const size_t len = eq - var;
		if (!len) {
			var += wcslen(var) + 1;
			continue;
		}
		wchar_t *v = RZ_NEWS0(wchar_t, len + 1);
		if (!v) {
			return -1;
		}
		wcsncpy(v, var, len);
		if (_wputenv_s(v, L"")) {
			free(v);
			break;
		}
		free(v);
		var += wcslen(var) + 1;
	}
	FreeEnvironmentStringsW(env);
#else
#warning rz_sys_clearenv : unimplemented for this platform
#endif
	return 0;
#endif
}

/**
 * \brief Set an environment variable in the calling process
 */
RZ_API int rz_sys_setenv(const char *key, const char *value) {
	if (!key) {
		return 0;
	}
#if __UNIX__
	if (!value) {
		unsetenv(key);
		return 0;
	}
	return setenv(key, value, 1);
#elif __WINDOWS__
	LPWSTR key_ = rz_utf8_to_utf16(key);
	LPWSTR value_ = value ? rz_utf8_to_utf16(value) : L"";
	bool ret = !_wputenv_s(key_, value_);
	free(key_);
	if (value) {
		free(value_);
	}
	return ret ? 0 : -1;
#else
#warning rz_sys_setenv : unimplemented for this platform
	return 0;
#endif
}

/**
 * \brief Get the value of an environment variable named \p key or NULL if none exists.
 */
RZ_API char *rz_sys_getenv(const char *key) {
#if __WINDOWS__
	if (!key) {
		return NULL;
	}
	wchar_t *val;
	wchar_t *wkey = rz_utf8_to_utf16(key);
	if (_wdupenv_s(&val, NULL, wkey) || !val) {
		free(wkey);
		return NULL;
	}
	free(wkey);
	char *ret = rz_utf16_to_utf8(val);
	free(val);
	return ret;
#else
	char *b;
	if (!key) {
		return NULL;
	}
	b = getenv(key);
	return rz_str_dup(b);
#endif
}

/**
 * \brief Return true if the environment variable has the value 1, false otherwise
 */
RZ_API bool rz_sys_getenv_asbool(const char *key) {
	char *env = rz_sys_getenv(key);
	const bool res = (env && *env == '1');
	free(env);
	return res;
}

/**
 * \brief Get current working directory
 */
RZ_API char *rz_sys_getdir(void) {
#if __WINDOWS__
	return _getcwd(NULL, 0);
#else
	return getcwd(NULL, 0);
#endif
}

/**
 * \brief Change current directory to \p s, taking care of home expansion ~.
 */
RZ_API bool rz_sys_chdir(RZ_NONNULL const char *s) {
	rz_return_val_if_fail(s, false);
	char *homepath = rz_path_home_expand(s);
	if (homepath) {
		int ret = chdir(homepath);
		free(homepath);
		return ret == 0;
	}
	return chdir(s) == 0;
}

/**
 * \brief Enable or disable ASLR for the calling process
 *
 * Host is Linux:
 *
 * We can disable ASLR using the personlity flags (does not require root) or
 * system-wide by setting /proc/sys/kernel/randomize_va_space to 0.
 * If we want to enable it we must write 1 or 2 into /proc/sys/kernel/randomize_va_space
 * ASLR support was added in linux 2.6 (year 2005)
 *
 * Host is NetBSD:
 *
 * We can enable/disable ASLR by calling sysctlbyname("security.pax.aslr.enabled").
 * May require root/super-user priviledges.
 *
 * Host is DragonFly:
 *
 * We can enable/disable ASLR by calling sysctlbyname("vm.randomize_mmap")
 * May require root/super-user priviledges.
 *
 * Host is FreeBSD (since version 13.0):
 *
 * We can enable/disable ASLR by calling sysctlbyname("kern.elf32.aslr.enable") and
 * sysctlbyname("kern.elf64.aslr.enable")
 * May require root/super-user priviledges.
 *
 * Host is Windows:
 *
 * We can only enable ASLR and not disable it.
 * To enable it we just need to initialize PROCESS_MITIGATION_ASLR_POLICY by setting to true
 * EnableBottomUpRandomization, EnableForceRelocateImages and EnableHighEntropy, and then call
 * SetProcessMitigationPolicy(ProcessASLRPolicy).
 * https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setprocessmitigationpolicy
 *
 * This feature is available since _WIN32_WINNT >= 0x0602 (Windows 8, Windows Server 2012)
 * It is possible to manually disable ASLR on a binary by removing the PE flags called DLL Characteristics:
 * - IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
 * - IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA
 * or as admin from PowerShell by running `Set-ProcessMitigation -Name file.exe -Disable ForceRelocateImages`
 *
 * Host is Apple:
 *
 * We can enable/disable ASRL when we spawn a process by adding the flag called _POSIX_SPAWN_DISABLE_ASLR (0x0100) to
 * the flag field of posix_spawnattr_setflags.
 * This was reverse engineered and documented in: https://opensource.apple.com/source/gdb/gdb-2831/src/gdb/macosx/macosx-nat-inferior.c.auto.html
 *
 * It is possible to manually disable ASLR on a binary by removing the MH_PIE flag from the mach0 header.
 * https://web.archive.org/web/20140906073648/http://src.chromium.org:80/svn/trunk/src/build/mac/change_mach_o_flags.py
 *
 * On Snow Leopard this also can be achieved by setting the environment variable DYLD_NO_PIE to 1 (export DYLD_NO_PIE=1)
 */
RZ_API bool rz_sys_aslr(bool enable) {
#if __linux__
#if HAVE_DECL_ADDR_NO_RANDOMIZE && !__ANDROID__
	if (!enable) {
		/*
		 * https://manpages.debian.org/testing/manpages-dev/personality.2.en.html
		 * Disable ASLR using personality flags and avoids to change the OS
		 * value system-wide, which may require to have root priviledges.
		 */
		if (personality(ADDR_NO_RANDOMIZE) < 0) {
			RZ_LOG_ERROR("Cannot set personality as ADDR_NO_RANDOMIZE\n");
			return false;
		}
		return true;
	}
#endif /* HAVE_DECL_ADDR_NO_RANDOMIZE && !__ANDROID__ */
	const char *proc_file = "/proc/sys/kernel/randomize_va_space";
	char *contents = rz_file_slurp(proc_file, NULL);

	// best effort at reading current value
	if (RZ_STR_ISNOTEMPTY(contents)) {
		if (enable && (contents[0] == '1' || contents[0] == '2')) {
			// already enabled.
			return true;
		} else if (!enable && contents[0] == '0') {
			// already disabled.
			return true;
		}
	}
	free(contents);

	char buf[8] = { 0 };
	snprintf(buf, sizeof(buf), "%d\n", enable ? 2 : 0);
	int fd = rz_sys_open(proc_file, O_WRONLY, 0644);
	if (fd == -1) {
		RZ_LOG_ERROR("Cannot open /proc/sys/kernel/randomize_va_space\n");
		return false;
	}

	int n_bytes = write(fd, (ut8 *)buf, sizeof(buf));
	close(fd);
	if (n_bytes != sizeof(buf)) {
		RZ_LOG_ERROR("Cannot write %s to /proc/sys/kernel/randomize_va_space\n", buf);
		return false;
	}
#if __FreeBSD__ || __NetBSD__ || __DragonFly__
	int value = enable ? 1 : 0;
	size_t vlen = sizeof(value);
#if __NetBSD__
	const char *ctl_name = "security.pax.aslr.enabled";
#elif __DragonFly__
	const char *ctl_name = "vm.randomize_mmap";
#elif __FreeBSD__ && __FreeBSD_version >= 1300000
	const char *ctl_name = "kern.elf32.aslr.enable";
#if __LP64__
	if (sysctlbyname("kern.elf64.aslr.enable", NULL, 0, &value, vlen) == -1) {
		RZ_LOG_ERROR("Cannot set kern.elf64.aslr.enable\n");
		return false;
	}
#endif /* __LP64__ */
#else /* __FreeBSD__ */
	const char *ctl_name = NULL;
	RZ_LOG_DEBUG("ASLR support is not supported on this FreeBSD version\n");
#endif /* __FreeBSD__ && __FreeBSD_version >= 1300000 */
	// allow to fail.
	if (ctl_name && sysctlbyname(ctl_name, NULL, 0, &value, vlen) == -1) {
		RZ_LOG_ERROR("Cannot set %s\n", ctl_name);
		return false;
	}
#if HAVE_DECL_PROCCTL_ASLR_CTL
	int set_aslr = enable ? PROC_ASLR_FORCE_ENABLE : PROC_ASLR_FORCE_DISABLE;
	if (procctl(P_PID, getpid(), PROC_ASLR_CTL, &set_aslr) == -1) {
		return false;
	}
#endif /* HAVE_DECL_PROCCTL_ASLR_CTL */
#endif /* __FreeBSD__ || __NetBSD__ || __DragonFly__ */
#elif __WINDOWS__
	/*
	 * https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-process_mitigation_aslr_policy
	 * SetProcessMitigationPolicy allows to enable or disable ASLR on windows per process, but
	 * ASLR mitigation policies cannot be made less restrictive after they have been applied.
	 * so a process that has already ASLR enable, cannot disable ASLR programmatically.
	 * ASLR is also set in the PE flags called DLL Characteristics:
	 * - IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
	 * - IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA
	 */
	HMODULE module = GetModuleHandleA("kernel32.dll");
	if (!module) {
		RZ_LOG_ERROR("Cannot get kernel32.dll module pointer\n");
		return false;
	}
	SetProcessMitigationPolicy_t setProcessMitigationPolicy = (SetProcessMitigationPolicy_t)GetProcAddress(module, "SetProcessMitigationPolicy");
	GetProcessMitigationPolicy_t getProcessMitigationPolicy = (GetProcessMitigationPolicy_t)GetProcAddress(module, "GetProcessMitigationPolicy");

	if (!setProcessMitigationPolicy || !getProcessMitigationPolicy) {
		RZ_LOG_INFO("Get/SetProcessMitigationPolicy is not supported in this kernel32.dll, thus ASLR cannot be handled\n");
		return true;
	}

	PROCESS_MITIGATION_ASLR_POLICY policy = { 0 };

	HANDLE handle = GetCurrentProcess();

	if (!getProcessMitigationPolicy(handle, ProcessASLRPolicy, &policy, sizeof(policy))) {
		DWORD dw = GetLastError();
		char *err = sys_windows_error_msg(dw);
		RZ_LOG_ERROR("Cannot get ProcessASLRPolicy: %s (0x%x)\n", err, dw);
		free(err);
		return false;
	}

	if (enable && policy.EnableForceRelocateImages) {
		// ASLR is already enabled.
		RZ_LOG_INFO("ASLR is already enabled in the current process\n");
		return true;
	} else if (!enable && !policy.EnableForceRelocateImages) {
		// ASLR is already disabled.
		RZ_LOG_INFO("ASLR is already disabled in the current process\n");
		return true;
	} else if (!enable && policy.EnableForceRelocateImages) {
		RZ_LOG_ERROR("On Windows, ASLR mitigation policies cannot be made less restrictive if they are already enabled.\n");
		RZ_LOG_ERROR("It is possible to disable ASLR by modifying the PE header and removing the following flags:\n");
		RZ_LOG_ERROR("- IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE\n");
		RZ_LOG_ERROR("- IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA.\n");
		RZ_LOG_ERROR("Or as Administrator from PowerShell by running: `Set-ProcessMitigation -Name file.exe -Disable ForceRelocateImages`\n");
		return true;
	}

	memset(&policy, 0, sizeof(policy));
	if (enable) {
		policy.EnableBottomUpRandomization = 1;
		policy.EnableForceRelocateImages = 1;
		policy.EnableHighEntropy = 1;
		policy.DisallowStrippedImages = 0;
	}
	if (!setProcessMitigationPolicy(ProcessASLRPolicy, &policy, sizeof(policy))) {
		DWORD dw = GetLastError();
		char *err = sys_windows_error_msg(dw);
		RZ_LOG_ERROR("Cannot set ProcessASLRPolicy: %s (0x%x)\n", err, dw);
		free(err);
		return false;
	}
#elif __APPLE__
	/**
	 * Apple supports this by using the spawn api.
	 * https://opensource.apple.com/source/gdb/gdb-2831/src/gdb/macosx/macosx-nat-inferior.c.auto.html
	 * so we always return true.
	 */
#else /* any other platform */
	/* not supported on the platform */
	RZ_LOG_INFO("ASLR support is not available on this platform.\n");
#endif /* __linux__ */
	return true;
}

RZ_API int rz_sys_cmd_str_full(const char *cmd, const char *input, char **output, int *len, char **sterr) {
	int argc;
	char **argv = rz_str_argv(cmd, &argc);
	if (!argv) {
		return false;
	}
	RzSubprocessOpt opts = {
		.file = argv[0],
		.args = (const char **)&argv[1],
		.args_size = argc - 1,
		.envvars = NULL,
		.envvals = NULL,
		.env_size = 0,
		.stdin_pipe = input ? RZ_SUBPROCESS_PIPE_CREATE : RZ_SUBPROCESS_PIPE_NONE,
		.stdout_pipe = output ? RZ_SUBPROCESS_PIPE_CREATE : RZ_SUBPROCESS_PIPE_NONE,
		.stderr_pipe = sterr ? RZ_SUBPROCESS_PIPE_CREATE : RZ_SUBPROCESS_PIPE_NONE,
		.pty = NULL,
		.make_raw = false,
	};

	if (!rz_subprocess_init()) {
		RZ_LOG_ERROR("Failed to initialize subprocess\n");
		return false;
	}
	RzSubprocess *subprocess = rz_subprocess_start_opt(&opts);
	if (!subprocess) {
		rz_subprocess_fini();
		return false;
	}
	if (input) {
		rz_subprocess_stdin_write(subprocess, (ut8 *)input, strlen(input) + 1);
	}
	rz_subprocess_wait(subprocess, UT64_MAX);
	if (output) {
		*output = (char *)rz_subprocess_out(subprocess, len);
	}
	if (sterr) {
		*sterr = (char *)rz_subprocess_err(subprocess, NULL);
	}
	rz_subprocess_free(subprocess);
	rz_subprocess_fini();
	rz_str_argv_free(argv);
	return true;
}

RZ_API int rz_sys_cmdf(const char *fmt, ...) {
	int ret;
	char cmd[4096];
	va_list ap;
	va_start(ap, fmt);
	vsnprintf(cmd, sizeof(cmd), fmt, ap);
	ret = rz_sys_system(cmd);
	va_end(ap);
	return ret;
}

RZ_API char *rz_sys_cmd_str(const char *cmd, const char *input, int *len) {
	char *output = NULL;
	if (rz_sys_cmd_str_full(cmd, input, &output, len, NULL)) {
		return output;
	}
	free(output);
	return NULL;
}

RZ_API bool rz_sys_mkdir(const char *dir) {
	bool ret;

#if __WINDOWS__
	wchar_t *dir_utf16 = rz_utf8_to_utf16(dir);
	ret = _wmkdir(dir_utf16) != -1;
	free(dir_utf16);
#else
	ret = mkdir(dir, 0755) != -1;
#endif
	return ret;
}

RZ_API bool rz_sys_mkdirp(const char *dir) {
	bool ret = true;
	char slash = RZ_SYS_DIR[0];
	char *path = rz_str_dup(dir), *ptr = path;
	if (!path) {
		RZ_LOG_ERROR("rz_sys_mkdirp: Unable to allocate memory\n");
		return false;
	}
	if (*ptr == slash) {
		ptr++;
	}
#if __WINDOWS__
	{
		char *p = strstr(ptr, ":\\");
		if (p) {
			ptr = p + 3;
		}
	}
#endif
	for (;;) {
		// find next slash
		for (; *ptr; ptr++) {
			if (*ptr == '/' || *ptr == '\\') {
				slash = *ptr;
				break;
			}
		}
		if (!*ptr) {
			break;
		}
		*ptr = 0;
		if (!rz_sys_mkdir(path) && rz_sys_mkdir_failed()) {
			RZ_LOG_ERROR("rz_sys_mkdirp: fail '%s' of '%s'\n", path, dir);
			free(path);
			return false;
		}
		*ptr = slash;
		ptr++;
	}
	if (!rz_sys_mkdir(path) && rz_sys_mkdir_failed()) {
		ret = false;
	}
	free(path);
	return ret;
}

RZ_API void rz_sys_perror_str(const char *fun) {
#if __UNIX__
#pragma push_macro("perror")
#undef perror
	perror(fun);
#pragma pop_macro("perror")
#elif __WINDOWS__
	DWORD dw = GetLastError();
	char *err = sys_windows_error_msg(dw);
	if (err) {
		eprintf("%s: (%#lx) %s\n", fun, dw, err);
		free(err);
	} else {
		eprintf("%s\n", fun);
	}
#endif /* __WINDOWS__ */
}

RZ_API int rz_sys_arch_id(const char *arch) {
	for (size_t i = 0; i < RZ_ARRAY_SIZE(arch_bit_array); i++) {
		if (!strcmp(arch, arch_bit_array[i].name)) {
			return arch_bit_array[i].bit;
		}
	}
	return 0;
}

RZ_API const char *rz_sys_arch_str(int arch) {
	for (size_t i = 0; i < RZ_ARRAY_SIZE(arch_bit_array); i++) {
		if (arch == arch_bit_array[i].bit) {
			return arch_bit_array[i].name;
		}
	}
	return "none";
}

#define USE_FORK 0
RZ_API int rz_sys_run(const ut8 *buf, int len) {
	const int sz = 4096;
	int pdelta, ret, (*cb)();
#if USE_FORK
	int st, pid;
#endif
	// TODO: define RZ_SYS_ALIGN_FORWARD in rz_util.h
	ut8 *ptr, *p = malloc((sz + len) << 1);
	ptr = p;
	pdelta = ((size_t)(p)) & (4096 - 1);
	if (pdelta) {
		ptr += (4096 - pdelta);
	}
	if (!ptr || !buf) {
		RZ_LOG_ERROR("rz_sys_run: Cannot run empty buffer\n");
		free(p);
		return false;
	}
	memcpy(ptr, buf, len);
	rz_mem_protect(ptr, sz, "rx");
	// rz_mem_protect (ptr, sz, "rwx"); // try, ignore if fail
	cb = (int (*)())ptr;
#if USE_FORK
#if __UNIX__
	pid = rz_sys_fork();
#else
	pid = -1;
#endif
	if (pid < 0) {
		return cb();
	}
	if (!pid) {
		ret = cb();
		exit(ret);
		return ret;
	}
	st = 0;
	waitpid(pid, &st, 0);
	if (WIFSIGNALED(st)) {
		int num = WTERMSIG(st);
		RZ_LOG_WARN("Got signal %d\n", num);
		ret = num;
	} else {
		ret = WEXITSTATUS(st);
	}
#else
	ret = (*cb)();
#endif
	free(p);
	return ret;
}

RZ_API int rz_sys_run_rop(const ut8 *buf, int len) {
#if USE_FORK
	int st;
#endif
	// TODO: define RZ_SYS_ALIGN_FORWARD in rz_util.h
	ut8 *bufptr = malloc(len);
	if (!bufptr) {
		RZ_LOG_ERROR("rz_sys_run_rop: Cannot allocate buffer\n");
		return false;
	}

	if (!buf) {
		RZ_LOG_ERROR("rz_sys_run_rop: Cannot execute empty rop chain\n");
		free(bufptr);
		return false;
	}
	memcpy(bufptr, buf, len);
#if USE_FORK
#if __UNIX__
	pid_t pid = rz_sys_fork();
#else
	pid = -1;
#endif
	if (pid < 0) {
		RZ_SYS_ASM_START_ROP();
	} else {
		RZ_SYS_ASM_START_ROP();
		exit(0);
		return 0;
	}
	st = 0;
	if (waitpid(pid, &st, 0) == -1) {
		RZ_LOG_ERROR("rz_sys_run_rop: waitpid failed\n");
		free(bufptr);
		return -1;
	}
	if (WIFSIGNALED(st)) {
		int num = WTERMSIG(st);
		RZ_LOG_WARN("Got signal %d\n", num);
		ret = num;
	} else {
		ret = WEXITSTATUS(st);
	}
#else
	RZ_SYS_ASM_START_ROP();
#endif
	free(bufptr);
	return 0;
}

RZ_API bool rz_is_heap(void *p) {
	void *q = malloc(8);
	ut64 mask = UT64_MAX;
	mask >>= 16;
	mask <<= 16;
	free(q);
	return (((ut64)(size_t)p) == mask);
}

RZ_API char *rz_sys_pid_to_path(int pid) {
#if __WINDOWS__
	// TODO: add maximum path length support
	HANDLE processHandle;
	const DWORD maxlength = MAX_PATH;
	WCHAR filename[MAX_PATH];
	char *result = NULL;

	processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
	if (!processHandle) {
		RZ_LOG_ERROR("rz_sys_pid_to_path: Cannot open process.\n");
		return NULL;
	}
	DWORD length = GetModuleFileNameExW(processHandle, NULL, filename, maxlength);
	if (length == 0) {
		// Upon failure fallback to GetProcessImageFileName
		length = GetProcessImageFileNameW(processHandle, filename, maxlength);
		CloseHandle(processHandle);
		if (length == 0) {
			RZ_LOG_ERROR("rz_sys_pid_to_path: Error calling GetProcessImageFileName\n");
			return NULL;
		}
		// Convert NT path to win32 path
		char *name = rz_utf16_to_utf8(filename);
		if (!name) {
			RZ_LOG_ERROR("rz_sys_pid_to_path: Error converting to utf8\n");
			return NULL;
		}
		char *tmp = strchr(name + 1, '\\');
		if (!tmp) {
			free(name);
			RZ_LOG_ERROR("rz_sys_pid_to_path: Malformed NT path\n");
			return NULL;
		}
		tmp = strchr(tmp + 1, '\\');
		if (!tmp) {
			free(name);
			RZ_LOG_ERROR("rz_sys_pid_to_path: Malformed NT path\n");
			return NULL;
		}
		length = tmp - name;
		tmp = malloc(length + 1);
		if (!tmp) {
			free(name);
			RZ_LOG_ERROR("rz_sys_pid_to_path: Error allocating memory\n");
			return NULL;
		}
		strncpy(tmp, name, length);
		tmp[length] = '\0';
		WCHAR device[MAX_PATH];
		for (WCHAR drv[] = L"A:"; drv[0] <= L'Z'; drv[0]++) {
			if (QueryDosDeviceW(drv, device, maxlength) > 0) {
				char *dvc = rz_utf16_to_utf8(device);
				if (!dvc) {
					free(name);
					free(tmp);
					RZ_LOG_ERROR("rz_sys_pid_to_path: Error converting to utf8\n");
					return NULL;
				}
				if (!strcmp(tmp, dvc)) {
					free(tmp);
					free(dvc);
					char *d = rz_utf16_to_utf8(drv);
					if (!d) {
						free(name);
						RZ_LOG_ERROR("rz_sys_pid_to_path: Error converting to utf8\n");
						return NULL;
					}
					tmp = rz_str_newf("%s%s", d, &name[length]);
					free(d);
					if (!tmp) {
						free(name);
						RZ_LOG_ERROR("rz_sys_pid_to_path: Error calling rz_str_newf\n");
						return NULL;
					}
					result = rz_str_dup(tmp);
					break;
				}
				free(dvc);
			}
		}
		free(name);
		free(tmp);
	} else {
		CloseHandle(processHandle);
		result = rz_utf16_to_utf8(filename);
	}
	return result;
#elif __APPLE__
	char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
	pathbuf[0] = 0;
	int ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf));
	if (ret <= 0) {
		return NULL;
	}
	return rz_str_dup(pathbuf);
#else
#if __FreeBSD__ || __DragonFly__
	char pathbuf[PATH_MAX];
	size_t pathbufl = sizeof(pathbuf);
	int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid };
	int ret = sysctl(mib, 4, pathbuf, &pathbufl, NULL, 0);
	if (ret != 0) {
		return NULL;
	}
#elif __OpenBSD__
	// Taken from https://stackoverflow.com/questions/31494901/how-to-get-the-executable-path-on-openbsd
	char pathbuf[PATH_MAX];
	int mib[4] = { CTL_KERN, KERN_PROC_ARGS, pid, KERN_PROC_ARGV };
	size_t len;

	pathbuf[0] = '\0';
	int ret = sysctl(mib, 4, NULL, &len, NULL, 0);
	if (ret < 0) {
		return NULL;
	}
	char **argv = malloc(len);
	ret = sysctl(mib, 4, argv, &len, NULL, 0);
	if (ret < 0) {
		free(argv);
		return NULL;
	}
	const char *comm = argv[0];
	int ok = 0;
	if (*comm == '/' || *comm == '.') {
		if (!realpath(comm, pathbuf)) {
			free(argv);
			return NULL;
		}
	} else {
		char *sp;
		char *xpath = rz_str_dup(getenv("PATH"));
		char *path = strtok_r(xpath, ":", &sp);
		struct stat st;

		if (!xpath) {
			free(argv);
			return NULL;
		}

		while (path) {
			snprintf(pathbuf, PATH_MAX, "%s/%s", path, comm);
			if (!stat(pathbuf, &st) && (st.st_mode & S_IXUSR)) {
				ok = 1;
				break;
			}
			path = strtok_r(NULL, ":", &sp);
		}
		free(xpath);
	}

	if (ok) {
		char *p = strrchr(pathbuf, '/');
		if (p) {
			*p = '\0';
		}
	}
	free(argv);
#elif __HAIKU__
	char pathbuf[MAXPATHLEN];
	int32_t group = 0;
	image_info ii;

	while (get_next_image_info((team_id)pid, &group, &ii) == B_OK) {
		if (ii.type == B_APP_IMAGE) {
			break;
		}
	}

	if (ii.type == B_APP_IMAGE) {
		rz_str_ncpy(pathbuf, ii.name, MAXPATHLEN);
	} else {
		pathbuf[0] = '\0';
	}
#else
	char buf[128], pathbuf[1024];
	snprintf(buf, sizeof(buf), "/proc/%d/exe", pid);
	int ret = readlink(buf, pathbuf, sizeof(pathbuf) - 1);
	if (ret < 1) {
		return NULL;
	}
	pathbuf[ret] = 0;
#endif
	return rz_str_dup(pathbuf);
#endif
}

RZ_API void rz_sys_env_init(void) {
	char **envp = rz_sys_get_environ();
	if (envp) {
		env = envp;
	}
}

RZ_API char **rz_sys_get_environ(void) {
#if __APPLE__ && !HAVE_ENVIRON
	env = *_NSGetEnviron();
#elif HAVE_ENVIRON
	env = environ;
#endif
	// return environ if available??
	if (!env) {
		env = rz_sys_dlsym(NULL, "environ");
	}
	return env;
}

RZ_API void rz_sys_set_environ(char **e) {
	env = e;
#if __APPLE__ && !HAVE_ENVIRON
	*_NSGetEnviron() = e;
#elif __WINDOWS__
	char **oe = e;
	rz_sys_clearenv();
	while (*e) {
		char *var = *e;
		char *val = strchr(var, '=');
		wchar_t *val_utf16 = NULL;
		if (*val) {
			*val++ = '\0';
		}
		rz_sys_setenv(var, val);
		free(*e);
		e++;
	}
	free(oe);
	env = environ;
#elif HAVE_ENVIRON
	environ = e;
#endif
}

RZ_API char *rz_sys_whoami(char *buf) {
	char _buf[32];
	int pid = getpid();
	int hasbuf = (buf) ? 1 : 0;
	if (!hasbuf) {
		buf = _buf;
	}
	sprintf(buf, "pid%d", pid);
	return hasbuf ? buf : rz_str_dup(buf);
}

RZ_API int rz_sys_getpid(void) {
#if __UNIX__
	return getpid();
#elif __WINDOWS__
	return GetCurrentProcessId();
#else
#warning rz_sys_getpid not implemented for this platform
	return -1;
#endif
}

RZ_API RSysInfo *rz_sys_info(void) {
#if __UNIX__
	struct utsname un = { { 0 } };
	if (uname(&un) != -1) {
		RSysInfo *si = RZ_NEW0(RSysInfo);
		if (si) {
			si->sysname = rz_str_dup(un.sysname);
			si->nodename = rz_str_dup(un.nodename);
			si->release = rz_str_dup(un.release);
			si->version = rz_str_dup(un.version);
			si->machine = rz_str_dup(un.machine);
			return si;
		}
	}
#elif __WINDOWS__
	HKEY key;
	DWORD type;
	DWORD size;
	DWORD major;
	DWORD minor;
	char tmp[256] = { 0 };
	RSysInfo *si = RZ_NEW0(RSysInfo);
	if (!si) {
		return NULL;
	}

	if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0,
		    KEY_QUERY_VALUE, &key) != ERROR_SUCCESS) {
		rz_sys_perror("rz_sys_info/RegOpenKeyExA");
		rz_sys_info_free(si);
		return NULL;
	}

	size = sizeof(tmp);
	if (RegQueryValueExA(key, "ProductName", NULL, &type,
		    (LPBYTE)&tmp, &size) != ERROR_SUCCESS ||
		type != REG_SZ) {
		goto beach;
	}
	si->sysname = rz_str_dup(tmp);

	size = sizeof(major);
	if (RegQueryValueExA(key, "CurrentMajorVersionNumber", NULL, &type,
		    (LPBYTE)&major, &size) != ERROR_SUCCESS ||
		type != REG_DWORD) {
		goto beach;
	}
	size = sizeof(minor);
	if (RegQueryValueExA(key, "CurrentMinorVersionNumber", NULL, &type,
		    (LPBYTE)&minor, &size) != ERROR_SUCCESS ||
		type != REG_DWORD) {
		goto beach;
	}

	size = sizeof(tmp);
	if (RegQueryValueExA(key, "CurrentBuild", NULL, &type,
		    (LPBYTE)&tmp, &size) != ERROR_SUCCESS ||
		type != REG_SZ) {
		goto beach;
	}
	si->version = rz_str_newf("%lu.%lu.%s", major, minor, tmp);

	size = sizeof(tmp);
	if (RegQueryValueExA(key, "ReleaseId", NULL, &type,
		    (LPBYTE)tmp, &size) != ERROR_SUCCESS ||
		type != REG_SZ) {
		goto beach;
	}
	si->release = rz_str_dup(tmp);
beach:
	RegCloseKey(key);
	return si;
#endif
	return NULL;
}

RZ_API void rz_sys_info_free(RSysInfo *si) {
	free(si->sysname);
	free(si->nodename);
	free(si->release);
	free(si->version);
	free(si->machine);
	free(si);
}

#if __UNIX__ && HAVE_PIPE2
#include <fcntl.h>
#include <unistd.h>

RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
	return pipe2(pipefd, close_on_exec ? O_CLOEXEC : 0);
}

RZ_API int rz_sys_pipe_close(int fd) {
	return close(fd);
}
#elif __UNIX__ && HAVE_PIPE && defined(O_CLOEXEC)
// Use this lock to wraps pipe, close, exec*, system to ensure all pipe file
// descriptors are either created AND set as CLOEXEC or not created at all.
static RzThreadLock *sys_pipe_mutex;
static bool is_child = false;

#ifdef RZ_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA
#pragma RZ_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(sys_pipe_constructor)
#endif
RZ_DEFINE_CONSTRUCTOR(sys_pipe_constructor)
static void sys_pipe_constructor(void) {
	sys_pipe_mutex = rz_th_lock_new(true);
}

#ifdef RZ_DEFINE_DESTRUCTOR_NEEDS_PRAGMA
#pragma RZ_DEFINE_DESTRUCTOR_PRAGMA_ARGS(sys_pipe_destructor)
#endif
RZ_DEFINE_DESTRUCTOR(sys_pipe_destructor)
static void sys_pipe_destructor(void) {
	rz_th_lock_free(sys_pipe_mutex);
}

static bool set_close_on_exec(int fd) {
	int flags = fcntl(fd, F_GETFD);
	if (flags == -1) {
		return false;
	}
	flags |= FD_CLOEXEC;
	return fcntl(fd, F_SETFD, flags) != -1;
}

static void parent_lock_enter(void) {
	if (!is_child) {
		rz_th_lock_enter(sys_pipe_mutex);
	}
}

static void parent_lock_leave(void) {
	if (!is_child) {
		rz_th_lock_leave(sys_pipe_mutex);
	}
}

/**
 * \brief Create a pipe and use O_CLOEXEC flag when \p close_on_exec is true
 *
 * If \p rz_sys functions are used to exec/system the new process, no race
 * condition happen even on systems that don't support atomic creation of pipes
 * with O_CLOEXEC.
 */
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
	int res = -1;
	parent_lock_enter();
	if ((res = pipe(pipefd)) == -1) {
		perror("pipe");
		goto err;
	}
	if (close_on_exec && (!set_close_on_exec(pipefd[0]) || !set_close_on_exec(pipefd[1]))) {
		perror("close-on-exec");
		close(pipefd[0]);
		close(pipefd[1]);
		goto err;
	}
err:
	parent_lock_leave();
	return res;
}

/**
 * \brief Close a file descriptor previously created pipe \p rz_sys_pipe
 */
RZ_API int rz_sys_pipe_close(int fd) {
	return close(fd);
}

#elif __UNIX__ && HAVE_PIPE
#include <rz_util/ht_uu.h>
static HtUU *fd2close;
// Use this lock to wraps pipe, close, exec*, system to ensure all pipe file
// descriptors are either created AND added to fd2close or not created at all.
static RzThreadLock *sys_pipe_mutex;
static bool is_child = false;

#ifdef RZ_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA
#pragma RZ_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(sys_pipe_constructor)
#endif
RZ_DEFINE_CONSTRUCTOR(sys_pipe_constructor)
static void sys_pipe_constructor(void) {
	sys_pipe_mutex = rz_th_lock_new(false);
	fd2close = ht_uu_new();
}

#ifdef RZ_DEFINE_DESTRUCTOR_NEEDS_PRAGMA
#pragma RZ_DEFINE_DESTRUCTOR_PRAGMA_ARGS(sys_pipe_destructor)
#endif
RZ_DEFINE_DESTRUCTOR(sys_pipe_destructor)
static void sys_pipe_destructor(void) {
	ht_uu_free(fd2close);
	rz_th_lock_free(sys_pipe_mutex);
}

static void parent_lock_enter(void) {
	if (!is_child) {
		rz_th_lock_enter(sys_pipe_mutex);
	}
}

static void parent_lock_leave(void) {
	if (!is_child) {
		rz_th_lock_leave(sys_pipe_mutex);
	}
}

static bool set_close_on_exec(int fd, bool close_on_exec) {
	bool res = ht_uu_insert(fd2close, fd, close_on_exec);
	rz_warn_if_fail(res);
	return res;
}

static bool close_on_exec_fd_cb(void *user, const ut64 key, const ut64 val) {
	bool close_on_exec = (bool)val;
	if (close_on_exec) {
		close((int)key);
	}
	return true;
}

static void close_fds(void) {
	ht_uu_foreach(fd2close, close_on_exec_fd_cb, NULL);
}

/**
 * \brief Create a pipe and simulates the O_CLOEXEC flag when \p close_on_exec is true
 *
 * If \p rz_sys functions are used to exec/system a new process, pipes created
 * with this function would be closed before executing the new executable,
 * making sure these file descriptors don't leak.
 */
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
	int res = -1;
	parent_lock_enter();
	if ((res = pipe(pipefd)) == -1) {
		perror("pipe");
		goto err;
	}
	if (!set_close_on_exec(pipefd[0], close_on_exec) || !set_close_on_exec(pipefd[1], close_on_exec)) {
		perror("close-on-exec");
		close(pipefd[0]);
		close(pipefd[1]);
		goto err;
	}
err:
	parent_lock_leave();
	return res;
}

/**
 * \brief Close a file descriptor previously created pipe \p rz_sys_pipe
 *
 * This is necessary to ensure that the file descriptor is not "closed again"
 * when an \p rz_sys exec/system is executed later.
 */
RZ_API int rz_sys_pipe_close(int fd) {
	parent_lock_enter();
	bool deleted = ht_uu_delete(fd2close, fd);
	rz_warn_if_fail(deleted);
	int res = close(fd);
	parent_lock_leave();
	return res;
}
#elif HAVE_PIPE
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
	return pipe(pipefd);
}

RZ_API int rz_sys_pipe_close(int fd) {
	return close(fd);
}
#elif __WINDOWS__
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
	return _pipe(pipefd, 0x1000, O_TEXT);
}

RZ_API int rz_sys_pipe_close(int fd) {
	return _close(fd);
}
#else
RZ_API int rz_sys_pipe(int pipefd[2], bool close_on_exec) {
	return -1;
}

RZ_API int rz_sys_pipe_close(int fd) {
	return -1;
}
#endif

#if __UNIX__ && HAVE_EXECV && HAVE_PIPE && defined(O_CLOEXEC) && !HAVE_PIPE2
RZ_API int rz_sys_execv(RZ_NONNULL const char *pathname, RZ_NONNULL char *const argv[]) {
	rz_return_val_if_fail(RZ_STR_ISNOTEMPTY(pathname) && argv && RZ_STR_ISNOTEMPTY(argv[0]), -1);
	parent_lock_enter();
	int res = execv(pathname, argv);
	parent_lock_leave();
	return res;
}
#elif __UNIX__ && HAVE_EXECV && HAVE_PIPE && !HAVE_PIPE2
RZ_API int rz_sys_execv(RZ_NONNULL const char *pathname, RZ_NONNULL char *const argv[]) {
	rz_return_val_if_fail(RZ_STR_ISNOTEMPTY(pathname) && argv && RZ_STR_ISNOTEMPTY(argv[0]), -1);
	parent_lock_enter();
	close_fds();
	int res = execv(pathname, argv);
	parent_lock_leave();
	return res;
}
#elif !HAVE_EXECV
/**
 * \brief Executes a program with a given set of arguments.
 * This is an implementation of execv on systems that do not have support for this function by using
 * RzSubprocess as layer of compatibility.
 *
 * Beware that argv[0] must be always allocated and should match pathname.
 *
 * \param pathname The path of the new program to execute
 * \param argv The array of pointers to null-terminated strings that represent the argument list available to the new program.
 *
 * \return Returns -1 on failure otherwise the exit(n) value returned by the executed program.
 */
RZ_API int rz_sys_execv(RZ_NONNULL const char *pathname, RZ_NONNULL char *const argv[]) {
	rz_return_val_if_fail(RZ_STR_ISNOTEMPTY(pathname) && argv && RZ_STR_ISNOTEMPTY(argv[0]), -1);

	if (!rz_subprocess_init()) {
		RZ_LOG_ERROR("Cannot initialize subprocess in execv.\n");
		return -1;
	}

	int argc = 0;
	for (int i = 0; argv[i]; ++i) {
		argc = i + 1;
	}

	RzSubprocessOpt opt = {
		.file = pathname,
		.args = (const char **)&argv[1],
		.args_size = argc - 1,
		.envvars = NULL,
		.envvals = NULL,
		.env_size = 0,
		.stdin_pipe = RZ_SUBPROCESS_PIPE_NONE,
		.stdout_pipe = RZ_SUBPROCESS_PIPE_NONE,
		.stderr_pipe = RZ_SUBPROCESS_PIPE_NONE,
		.pty = NULL,
		.make_raw = false,
	};

	RzSubprocess *proc = rz_subprocess_start_opt(&opt);
	if (!proc) {
		RZ_LOG_ERROR("Cannot start subprocess in execv.\n");
		rz_subprocess_fini();
		return -1;
	}

	rz_subprocess_wait(proc, UT64_MAX);

	int ret = rz_subprocess_ret(proc);
	rz_subprocess_free(proc);
	rz_subprocess_fini();
	return ret;
}
#endif

#if __UNIX__ && HAVE_EXECVE && HAVE_PIPE && defined(O_CLOEXEC) && !HAVE_PIPE2
RZ_API int rz_sys_execve(const char *pathname, char *const argv[], char *const envp[]) {
	parent_lock_enter();
	int res = execve(pathname, argv, envp);
	parent_lock_leave();
	return res;
}
#elif __UNIX__ && HAVE_EXECVE && HAVE_PIPE && !HAVE_PIPE2
RZ_API int rz_sys_execve(const char *pathname, char *const argv[], char *const envp[]) {
	parent_lock_enter();
	close_fds();
	int res = execve(pathname, argv, envp);
	parent_lock_leave();
	return res;
}
#elif !HAVE_EXECVE
RZ_API int rz_sys_execve(const char *pathname, char *const argv[], char *const envp[]) {
	return -1;
}
#endif

#if __UNIX__ && HAVE_EXECVP && HAVE_PIPE && defined(O_CLOEXEC) && !HAVE_PIPE2
RZ_API int rz_sys_execvp(const char *file, char *const argv[]) {
	parent_lock_enter();
	int res = execvp(file, argv);
	parent_lock_leave();
	return res;
}
#elif __UNIX__ && HAVE_EXECVP && HAVE_PIPE && !HAVE_PIPE2
RZ_API int rz_sys_execvp(const char *file, char *const argv[]) {
	parent_lock_enter();
	close_fds();
	int res = execvp(file, argv);
	parent_lock_leave();
	return res;
}
#elif !HAVE_EXECVP
RZ_API int rz_sys_execvp(const char *file, char *const argv[]) {
	return -1;
}
#endif

#if __UNIX__ && HAVE_EXECL && HAVE_PIPE && defined(O_CLOEXEC) && !HAVE_PIPE2
RZ_API int rz_sys_execl(const char *pathname, const char *arg, ...) {
	va_list count_args, args;
	va_start(args, arg);
	va_copy(count_args, args);
	size_t i, argc = 0;
	while (va_arg(count_args, char *) != NULL) {
		argc++;
	}
	va_end(count_args);
	char **argv = RZ_NEWS0(char *, argc + 2);
	argv[0] = rz_str_dup(pathname);
	for (i = 1; i <= argc; i++) {
		argv[i] = va_arg(args, char *);
	}
	va_end(args);
	parent_lock_enter();
	int res = execv(pathname, argv);
	parent_lock_leave();
	return res;
}
#elif __UNIX__ && HAVE_EXECL && HAVE_PIPE && !HAVE_PIPE2
RZ_API int rz_sys_execl(const char *pathname, const char *arg, ...) {
	va_list count_args, args;
	va_start(args, arg);
	va_copy(count_args, args);
	size_t i, argc = 0;
	while (va_arg(count_args, char *) != NULL) {
		argc++;
	}
	va_end(count_args);
	char **argv = RZ_NEWS0(char *, argc + 2);
	argv[0] = rz_str_dup(pathname);
	for (i = 1; i <= argc; i++) {
		argv[i] = va_arg(args, char *);
	}
	va_end(args);

	parent_lock_enter();
	close_fds();
	int res = execv(pathname, argv);
	parent_lock_leave();
	return res;
}
#elif !HAVE_EXECL
RZ_API int rz_sys_execl(const char *pathname, const char *arg, ...) {
	return -1;
}
#endif

#if __UNIX__ && HAVE_SYSTEM && HAVE_PIPE && defined(O_CLOEXEC) && !HAVE_PIPE2
RZ_API int rz_sys_system(const char *command) {
	parent_lock_enter();
	int res = system(command);
	parent_lock_leave();
	return res;
}
#elif __UNIX__ && HAVE_SYSTEM && HAVE_PIPE && !HAVE_PIPE2
RZ_API int rz_sys_system(const char *command) {
	parent_lock_enter();
	close_fds();
	int res = system(command);
	parent_lock_leave();
	return res;
}
#elif !HAVE_SYSTEM && APPLE_SDK_IPHONEOS
#include <spawn.h>
RZ_API int rz_sys_system(const char *command) {
	int argc;
	char *cmd = rz_str_dup(command);
	char **argv = rz_str_argv(cmd, &argc);
	if (argv) {
		char *argv0 = rz_file_path(argv[0]);
		pid_t pid = 0;
		int r = posix_spawn(&pid, argv0, NULL, NULL, argv, NULL);
		int status;
		int s = waitpid(pid, &status, 0);
		return WEXITSTATUS(s);
	}
}
#elif !HAVE_SYSTEM && HAVE_FORK
#include <spawn.h>
RZ_API int rz_sys_system(const char *command) {
	if (!strchr(command, '|')) {
		char **argv, *cmd = rz_str_dup(command);
		int rc, pid, argc;
		char *isbg = strchr(cmd, '&');
		// XXX this is hacky
		if (isbg) {
			*isbg = 0;
		}
		argv = rz_str_argv(cmd, &argc);
		if (argv) {
			char *argv0 = rz_file_path(argv[0]);
			if (!argv0) {
				RZ_LOG_ERROR("Cannot find '%s'\n", argv[0]);
				return -1;
			}
			pid = 0;
			posix_spawn(&pid, argv0, NULL, NULL, argv, NULL);
			if (isbg) {
				// XXX. wait for children
				rc = 0;
			} else {
				rc = waitpid(pid, NULL, 0);
			}
			rz_str_argv_free(argv);
			free(argv0);
			return rc;
		}
		RZ_LOG_ERROR("Error parsing command arguments\n");
		return -1;
	}
	int child = rz_sys_fork();
	if (child == -1) {
		return -1;
	}
	if (child) {
		return waitpid(child, NULL, 0);
	}
	char *bin_sh = rz_file_binsh();
	if (rz_sys_execl(bin_sh, "sh", "-c", command, (const char *)NULL) == -1) {
		perror("execl");
	}
	free(bin_sh);
	exit(1);
}
#elif !HAVE_SYSTEM
RZ_API int rz_sys_system(const char *command) {
	return -1;
}
#endif

#if HAVE_FORK
RZ_API int rz_sys_fork(void) {
#if __UNIX__ && HAVE_PIPE && !HAVE_PIPE2
	parent_lock_enter();
#endif
	pid_t child = fork();
	if (child == -1) {
		perror("fork");
	}
#if __UNIX__ && HAVE_PIPE && !HAVE_PIPE2
	if (child == 0) {
		is_child = true;
	} else if (child > 0) {
		parent_lock_leave();
	}
#endif
	return child;
}
#else
RZ_API int rz_sys_fork(void) {
	return -1;
}
#endif

/**
 * \brief Wrapper for forkpty(3)
 *
 * \param amaster The master end of the PTY is stored here
 * \param name The name of the slave end of the PTY is stored here
 * \param termp (const struct termios) The terminal attributes
 * \param winp (const struct winsize) The window size attributes
 *
 * \return int (pid_t) PID of the forked process
 */
RZ_API /* pid_t */ int rz_sys_forkpty(int *amaster, char *name, void /* const struct termios */ *termp, void /* const struct winsize */ *winp) {
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
	pid_t ret = forkpty(amaster, name, termp, winp);
	if (ret == -1) {
		perror("forkpty");
	}
	return ret;
#else
	RZ_LOG_ERROR("forkpty() not found\n");
	return -1;
#endif
}

/**
 * \brief Wrapper for openpty(3)
 *
 * \param amaster The master end of the PTY is stored here
 * \param aslave The slave end of the PTY is stored here
 * \param name The name of the slave end of the PTY is stored here
 * \param termp (const struct termios) The terminal attributes
 * \param winp (const struct winsize) The window size attributes
 *
 * \return int Return code
 */
RZ_API int rz_sys_openpty(int *amaster, int *aslave, char *name, void /* const struct termios */ *termp, void /* const struct winsize */ *winp) {
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
	int ret = openpty(amaster, aslave, name, termp, winp);
	if (ret == -1) {
		perror("openpty");
	}
	return ret;
#else
	RZ_LOG_ERROR("openpty() not found\n");
	return -1;
#endif
}

/**
 * \brief Wrapper for login_tty(3)
 *
 * \param fd File descriptor for the slave end of the PTY; To be made the controlling terminal
 * \return int Return code
 */
RZ_API int rz_sys_login_tty(int fd) {
#if HAVE_OPENPTY && HAVE_FORKPTY && HAVE_LOGIN_TTY
	int ret = login_tty(fd);
	if (ret == -1) {
		perror("login_tty");
	}
	return ret;
#else
	RZ_LOG_ERROR("login_tty() not found\n");
	return -1;
#endif
}

RZ_API int rz_sys_truncate_fd(int fd, ut64 length) {
#ifdef _MSC_VER
	return _chsize_s(fd, length);
#else
	return ftruncate(fd, (off_t)length);
#endif
}

RZ_API int rz_sys_truncate(const char *file, int sz) {
#if __WINDOWS__
	int fd = rz_sys_open(file, O_RDWR, 0644);
	if (fd == -1) {
		return false;
	}
	int r = rz_sys_truncate_fd(fd, sz);
	if (r != 0) {
		RZ_LOG_ERROR("Could not resize '%s' file\n", file);
		close(fd);
		return false;
	}
	close(fd);
	return true;
#else
	return truncate(file, sz) == 0;
#endif
}

/**
 * \brief Convert rizin permissions (RZ_PERM_*) to posix permissions that can be passed to \b rz_sys_open .
 *
 * \b rz_sys_open accepts posix permissions for now, not the arch-independent
 * ones provided by RZ_PERM_*. This function is an helper to convert from rizin
 * permissions to posix ones.
 */
RZ_API int rz_sys_open_perms(int rizin_perms) {
	int res = 0;
	if ((rizin_perms & RZ_PERM_R) && (rizin_perms & RZ_PERM_W)) {
		res |= O_RDWR;
		// NOTE: O_CREAT is added here because Rizin for now assumes Write means
		// ability to create a file as well.
		res |= O_CREAT;
	} else if (rizin_perms & RZ_PERM_R) {
		res |= O_RDONLY;
	} else if (rizin_perms & RZ_PERM_W) {
		res |= O_WRONLY;
		// NOTE: O_CREAT is added here because Rizin for now assumes Write means
		// ability to create a file as well.
		res |= O_CREAT;
	}
	if (rizin_perms & RZ_PERM_CREAT) {
		res |= O_CREAT;
	}
	return res;
}

/* perm <-> mode */
RZ_API int rz_sys_open(const char *path, int perm, int mode) {
	rz_return_val_if_fail(path, -1);
	char *epath = rz_path_home_expand(path);
	int ret = -1;
#if __WINDOWS__
	if (!strcmp(path, "/dev/null")) {
		path = "NUL";
	}
	{
		DWORD flags = 0;
		DWORD sharing = FILE_SHARE_READ;
		if (perm & O_RANDOM) {
			flags = FILE_FLAG_RANDOM_ACCESS;
		} else if (perm & O_SEQUENTIAL) {
			flags = FILE_FLAG_SEQUENTIAL_SCAN;
		}
		if (perm & O_TEMPORARY) {
			flags |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
			sharing |= FILE_SHARE_DELETE;
		} else if (perm & _O_SHORT_LIVED) {
			flags |= FILE_ATTRIBUTE_TEMPORARY;
		} else {
			flags |= FILE_ATTRIBUTE_NORMAL;
		}
		DWORD creation = 0;
		bool read_only = false;
		if (perm & O_CREAT) {
			if (perm & O_EXCL) {
				creation = CREATE_NEW;
			} else {
				creation = OPEN_ALWAYS;
			}
			if (mode & S_IREAD && !(mode & S_IWRITE)) {
				flags = FILE_ATTRIBUTE_READONLY;
				read_only = true;
			}
		} else if (perm & O_TRUNC) {
			creation = TRUNCATE_EXISTING;
		}
		if (!creation || !strcasecmp("NUL", path)) {
			creation = OPEN_EXISTING;
		}
		DWORD permission = 0;
		if (perm & O_WRONLY) {
			permission = GENERIC_WRITE;
		} else if (perm & O_RDWR) {
			permission = GENERIC_WRITE | GENERIC_READ;
		} else {
			permission = GENERIC_READ;
		}
		if (perm & O_APPEND) {
			permission |= FILE_APPEND_DATA;
		}
		if (!read_only) {
			sharing |= FILE_SHARE_WRITE;
		}

		wchar_t *wepath = rz_utf8_to_utf16(epath);
		if (!wepath) {
			free(epath);
			return -1;
		}
		HANDLE h = CreateFileW(wepath, permission, sharing, NULL, creation, flags, NULL);
		if (h != INVALID_HANDLE_VALUE) {
			ret = _open_osfhandle((intptr_t)h, perm);
		}
		free(wepath);
	}
#else // __WINDOWS__
	ret = open(epath, perm, mode);
#endif // __WINDOWS__
	free(epath);
	return ret;
}

RZ_API FILE *rz_sys_fopen(const char *path, const char *mode) {
	rz_return_val_if_fail(path && mode, NULL);
	FILE *ret = NULL;
	char *epath = rz_path_home_expand(path);
	if ((strchr(mode, 'w') || strchr(mode, 'a') || rz_file_is_regular(epath))) {
#if __WINDOWS__
		wchar_t *wepath = rz_utf8_to_utf16(epath);
		if (!wepath) {
			free(epath);
			return ret;
		}
		wchar_t *wmode = rz_utf8_to_utf16(mode);
		if (!wmode) {
			free(wepath);
			free(epath);
			return ret;
		}
		ret = _wfopen(wepath, wmode);
		free(wmode);
		free(wepath);
#else // __WINDOWS__
		ret = fopen(epath, mode);
#endif // __WINDOWS__
	}
	free(epath);
	return ret;
}

/**
 * \brief Send signal \p sig to process with pid \p pid.
 *
 * \param pid PID of the process to send the signal to
 * \param sig Signal to send to the process.
 * \return 0 on success, -1 on failure.
 */
RZ_API int rz_sys_kill(int pid, int sig) {
	rz_return_val_if_fail(pid != -1, -1);
#if __UNIX__
	return kill(pid, sig);
#else
	return -1;
#endif
}

/**
 * \brief Send SIGTSTP signal to every process in this process group
 *
 * \return true if at least one signal was sent, false otherwise
 */
RZ_API bool rz_sys_stop(void) {
#if __UNIX__
	return !rz_sys_kill(0, SIGTSTP);
#else
	return false;
#endif
}

/**
 * \brief Implementation across systems to open a dynamic library
 */
RZ_API void *rz_sys_dlopen(RZ_NULLABLE const char *libname) {
	void *ret = NULL;
#if WANT_DYLINK
#if __UNIX__
	if (libname) {
		ret = dlopen(libname, RTLD_GLOBAL | RTLD_LAZY);
	} else {
		ret = dlopen(NULL, RTLD_NOW);
	}
	if (!ret) {
		RZ_LOG_ERROR("rz_sys_dlopen: error: %s (%s)\n", libname, dlerror());
	}
#elif __WINDOWS__
	LPTSTR libname_;
	if (libname && *libname) {
		libname_ = rz_sys_conv_utf8_to_win(libname);
	} else {
		libname_ = calloc(MAX_PATH, sizeof(TCHAR));
		if (!libname_) {
			RZ_LOG_ERROR("rz_sys_dlopen: failed to allocate memory.\n");
			return NULL;
		}
		if (!GetModuleFileName(NULL, libname_, MAX_PATH)) {
			libname_[0] = '\0';
		}
	}
	ret = LoadLibrary(libname_);
	free(libname_);
	if (!ret) {
		DWORD dw = GetLastError();
		char *err = sys_windows_error_msg(dw);
		RZ_LOG_ERROR("rz_sys_dlopen: error: %s (%s)\n", libname, err ? err : "unknown");
		free(err);
	}
#endif
#endif
	return ret;
}

/**
 * \brief Implementation across systems to get the address of a symbol in a
 * dynamic library
 */
RZ_API void *rz_sys_dlsym(void *handler, const char *name) {
#if WANT_DYLINK
#if __UNIX__
	return dlsym(handler, name);
#elif __WINDOWS__
	return GetProcAddress(handler, name);
#else
	return NULL;
#endif
#else
	return NULL;
#endif
}

/**
 * \brief Implementation across systems to close a previously opened dynamic
 * library.
 */
RZ_API int rz_sys_dlclose(void *handler) {
#if __UNIX__
	return dlclose(handler);
#else
	return handler ? 0 : -1;
#endif
}
