/* radare - LGPL - Copyright 2012-2026 - pancake */

#include <r_util.h>
#include <signal.h>
#if _MSC_VER
#include <process.h> // to compile execl under msvc windows
#include <direct.h>  // to compile chdir under msvc windows
#endif

#if R2__UNIX__ && !__wasi__
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#if HAVE_CAPSICUM
#include <sys/capsicum.h>
#endif

#if LIBC_HAVE_PRIV_SET
#include <priv.h>
#endif

static R_TH_LOCAL bool G_enabled = false;
static R_TH_LOCAL bool G_disabled = false;
static R_TH_LOCAL int G_graintype = R_SANDBOX_GRAIN_ALL;

typedef struct {
	int grain;
	const char *path;
} RSandboxScope;

#if HAVE_TH_LOCAL || !WANT_THREADS
static R_TH_LOCAL RSandboxScope *G_scope;

static RSandboxScope *sandbox_scope_current(void) {
	return G_scope;
}

static bool sandbox_scope_set(RSandboxScope *scope) {
	G_scope = scope;
	return true;
}
#elif HAVE_PTHREAD
static pthread_key_t scope_key;
static pthread_once_t scope_once = PTHREAD_ONCE_INIT;
static bool scope_key_ready;

static void sandbox_scope_init(void) {
	scope_key_ready = pthread_key_create (&scope_key, NULL) == 0;
}

static RSandboxScope *sandbox_scope_current(void) {
	pthread_once (&scope_once, sandbox_scope_init);
	return scope_key_ready? pthread_getspecific (scope_key): NULL;
}

static bool sandbox_scope_set(RSandboxScope *scope) {
	pthread_once (&scope_once, sandbox_scope_init);
	return scope_key_ready && pthread_setspecific (scope_key, scope) == 0;
}
#elif R2__WINDOWS__
static DWORD scope_key;
static LONG scope_key_state;

static bool sandbox_scope_init(void) {
	if (!InterlockedCompareExchange (&scope_key_state, 1, 0)) {
		scope_key = TlsAlloc ();
		InterlockedExchange (&scope_key_state, scope_key == TLS_OUT_OF_INDEXES? -1: 2);
	}
	while (InterlockedCompareExchange (&scope_key_state, 0, 0) == 1) {
		Sleep (0);
	}
	return InterlockedCompareExchange (&scope_key_state, 0, 0) == 2;
}

static RSandboxScope *sandbox_scope_current(void) {
	return sandbox_scope_init ()? TlsGetValue (scope_key): NULL;
}

static bool sandbox_scope_set(RSandboxScope *scope) {
	return sandbox_scope_init () && TlsSetValue (scope_key, scope);
}
#endif

#define R_SANDBOX_GUARD(x,y) if (!r_sandbox_check (x)) { return (y); }

R_API void *r_sandbox_run(int grain, RSandboxCallback callback, void *user) {
	return r_sandbox_run_path (grain, NULL, callback, user);
}

R_API void *r_sandbox_run_path(int grain, const char *path, RSandboxCallback callback, void *user) {
	R_RETURN_VAL_IF_FAIL (callback, NULL);
	RSandboxScope *previous = sandbox_scope_current ();
	int allowed = previous? previous->grain: G_enabled? G_graintype: R_SANDBOX_GRAIN_ALL;
	RSandboxScope scope = {
		.grain = grain & allowed,
		.path = previous? previous->path: G_enabled? NULL: path
	};
	if (!sandbox_scope_set (&scope)) {
		R_LOG_ERROR ("Cannot bind sandbox policy to this thread");
		return NULL;
	}
	void *result = callback (user);
	if (!sandbox_scope_set (previous)) {
		R_LOG_ERROR ("Cannot restore the thread's sandbox policy");
		abort ();
	}
	return result;
}

R_API bool r_sandbox_check_writepath(const char *path) {
	RSandboxScope *scope = sandbox_scope_current ();
	if (!scope || R_STR_ISEMPTY (scope->path) || R_STR_ISEMPTY (path)) {
		return false;
	}
#if R2__UNIX__ && !__wasi__
	struct stat st;
	bool exists = !lstat (path, &st);
	if (exists? !S_ISDIR (st.st_mode) && (!S_ISREG (st.st_mode) || st.st_nlink > 1): errno != ENOENT) {
		return false;
	}
	char *resolved = NULL;
	if (exists) {
		resolved = realpath (path, NULL);
	} else {
		char *absolute = r_file_abspath (path);
		char *parent = absolute? r_file_dirname (absolute): NULL;
		char *directory = parent? realpath (*parent? parent: "/", NULL): NULL;
		if (directory) {
			resolved = r_str_newf ("%s%s%s", directory, strcmp (directory, "/")? "/": "", r_file_basename (absolute));
		}
		free (directory);
		free (parent);
		free (absolute);
	}
	size_t len = strlen (scope->path);
	while (len > 1 && scope->path[len - 1] == '/') {
		len--;
	}
	bool allowed = resolved && !strncmp (resolved, scope->path, len)
		&& (!resolved[len] || resolved[len] == '/' || (len == 1 && scope->path[0] == '/'));
	free (resolved);
	return allowed;
#else
	return false;
#endif
}

// returns the path relative to root when it is inside it, or NULL
static const char *path_in_root(const char *path, const char *root) {
	if (R_STR_ISEMPTY (root) || !r_str_startswith (path, root)) {
		return NULL;
	}
	path += strlen (root);
	if (*path && *path != '/' && !r_str_endswith (root, "/")) {
		return NULL;
	}
	while (*path == '/') {
		path++;
	}
	return path;
}

static const char *path_in_trusted_root(const char *path) {
	const char *rest = path_in_root (path, R2_LIBDIR "/radare2");
	if (!rest) {
		rest = path_in_root (path, R2_DATDIR "/radare2");
	}
	if (!rest) {
		rest = path_in_root (path, R2_WWWROOT);
	}
	if (!rest) {
		char *home_www = r_xdg_datadir ("www");
#if R2__WINDOWS__
		r_str_replace_char (home_www, '\\', '/');
#endif
		rest = path_in_root (path, home_www);
		free (home_www);
	}
	return rest;
}

static bool has_parent_component(const char *path) {
	return !strcmp (path, "..") || r_str_startswith (path, "../")
		|| strstr (path, "/../") || r_str_endswith (path, "/..");
}

#if R2__UNIX__
// checks every component from start to the end of path, the prefix before start is trusted
static bool path_has_symlink(char *path, char *start) {
	char *slash = start;
	for (;;) {
		slash = strchr (slash, '/');
		if (slash) {
			*slash = 0;
		}
		const bool is_link = *path && r_file_is_symlink (path);
		if (slash) {
			*slash = '/';
		}
		if (is_link) {
			return true;
		}
		if (!slash) {
			return false;
		}
		slash++;
	}
}
#endif

static bool path_is_allowed(char *path) {
	if (!r_sandbox_check (R_SANDBOX_GRAIN_HIDDEN) && (*path == '.' || strstr (path, "/."))) {
		return false;
	}
	// install and webroot dirs may be absolute, but must not be escaped with ..
	char *rel = path;
	const char *rest = path_in_trusted_root (path);
	if (rest) {
		rel = (char *)rest;
	}
	if (*rel == '/' || r_str_startswith (rel, "./") || has_parent_component (rel)) {
		return false;
	}
#if R2__WINDOWS__
	// drive letters, also drive relative paths like C:foo
	if (strchr (rel, ':')) {
		return false;
	}
#endif
#if R2__UNIX__
	if (*rel && path_has_symlink (path, rel)) {
		return false;
	}
#endif
	return true;
}

/**
 * Verify that the given path is allowed. Paths are allowed only if they are relative and
 * don't contain .. components. Paths inside the install and webroot directories may be absolute.
 */
R_API bool r_sandbox_check_path(const char *path) {
	R_RETURN_VAL_IF_FAIL (path, false);
	char *p = strdup (path);
#if R2__WINDOWS__
	// checks only deal with forward slashes
	r_str_replace_char (p, '\\', '/');
#endif
	const bool allowed = p && path_is_allowed (p);
	free (p);
	return allowed;
}

R_API bool r_sandbox_disable(bool e) {
	if (sandbox_scope_current ()) {
		return true;
	}
	if (e) {
#if LIBC_HAVE_PLEDGE
		if (G_enabled) {
			R_LOG_ERROR ("sandbox mode couldn't be G_disabled when pledged");
			return G_enabled;
		}
#endif
#if HAVE_CAPSICUM
		if (G_enabled) {
			R_LOG_ERROR ("sandbox mode couldn't be G_disabled in capability mode");
			return G_enabled;
		}
#endif
#if LIBC_HAVE_PRIV_SET
		if (G_enabled) {
			R_LOG_ERROR ("sandbox mode couldn't be G_disabled in priv mode");
			return G_enabled;
		}
#endif
		G_disabled = G_enabled;
		G_enabled = false;
	} else {
		G_enabled = G_disabled;
		G_disabled = false;
	}
	return G_enabled;
}

R_API int r_sandbox_grain(int mask) {
	RSandboxScope *scope = sandbox_scope_current ();
	if (scope) {
		return scope->grain;
	}
	int old_grain = G_graintype;
	G_graintype = (mask & R_SANDBOX_GRAIN_ALL);
	return old_grain;
}

R_API bool r_sandbox_grain_parse(const char *R_NONNULL expr, int *R_NONNULL mask) {
	R_RETURN_VAL_IF_FAIL (expr && mask, false);
	static const struct {
		const char *name;
		int mask;
	} grains[] = {
		{ "all", R_SANDBOX_GRAIN_ALL },
		{ "none", R_SANDBOX_GRAIN_NONE },
		{ "exec", R_SANDBOX_GRAIN_EXEC },
		{ "socket", R_SANDBOX_GRAIN_SOCKET },
		{ "file", R_SANDBOX_GRAIN_FILES },
		{ "files", R_SANDBOX_GRAIN_FILES },
		{ "disk", R_SANDBOX_GRAIN_DISK },
		{ "network", R_SANDBOX_GRAIN_NETWORK },
		{ "environ", R_SANDBOX_GRAIN_ENVIRON },
		{ "hidden", R_SANDBOX_GRAIN_HIDDEN },
		{ "script", R_SANDBOX_GRAIN_SCRIPT }
	};
	expr = r_str_trim_head_ro (expr);
	int result = *expr == '!'? R_SANDBOX_GRAIN_ALL: R_SANDBOX_GRAIN_NONE;
	for (;;) {
		bool remove = *expr == '!';
		if (remove) {
			expr = r_str_trim_head_ro (expr + 1);
		}
		const char *end = expr + strcspn (expr, ",");
		size_t len = end - expr;
		while (len && IS_WHITECHAR (expr[len - 1])) {
			len--;
		}
		size_t i;
		for (i = 0; i < R_ARRAY_SIZE (grains); i++) {
			if (strlen (grains[i].name) == len && !memcmp (expr, grains[i].name, len)) {
				break;
			}
		}
		if (i == R_ARRAY_SIZE (grains)) {
			return false;
		}
		if (remove) {
			result &= ~grains[i].mask;
		} else if (grains[i].mask == R_SANDBOX_GRAIN_NONE) {
			result = R_SANDBOX_GRAIN_NONE;
		} else {
			result |= grains[i].mask;
		}
		if (!*end) {
			break;
		}
		expr = r_str_trim_head_ro (end + 1);
	}
	*mask = result;
	return true;
}

R_API bool r_sandbox_check(int mask) {
	RSandboxScope *scope = sandbox_scope_current ();
	if (scope) {
		return (scope->grain & mask) != 0;
	}
	return !G_enabled || (G_graintype & mask) != 0;
}

// only the dotted quad form of 127.0.0.0/8, "127.example.com" is a name that resolves anywhere
static bool is_ipv4_loopback(const char *host) {
	ut32 b, c, d;
	int len = 0;
	if (host[strspn (host, "0123456789.")]) {
		return false;
	}
	return sscanf (host, "127.%3u.%3u.%3u%n", &b, &c, &d, &len) == 3
		&& !host[len] && b < 256 && c < 256 && d < 256;
}

R_API bool r_sandbox_check_localhost(const char *str) {
	R_RETURN_VAL_IF_FAIL (str, false);
	const char *peekaboo = strstr (str, "://");
	if (peekaboo) {
		str = peekaboo + 3;
	}
	char *host = r_str_ndup (str, strcspn (str, "/?#"));
	char *authority = strrchr (host, '@');
	if (authority) {
		char *nhost = strdup (authority + 1);
		free (host);
		host = nhost;
	}
	// chop the numeric ":port" so only complete host names are compared below
	char *port = (*host == '[')? strchr (host, ']'): host;
	port = port? strchr (port, ':'): NULL;
	if (port && r_str_isnumber (port + 1)) {
		*port = 0;
	}
	bool ret = 0
		|| !strcmp (host, "0.0.0.0")
		|| !strcmp (host, "localhost")
		|| is_ipv4_loopback (host)
		|| !strcmp (host, "[::1]")
		|| !strcmp (host, "::1")
		|| !strcmp (host, "::");
	free (host);
	return ret;
}

R_API bool r_sandbox_enable(bool e) {
	if (sandbox_scope_current ()) {
		return true;
	}
	if (G_enabled) {
		if (!e) {
			// R_LOG_ERROR ("Can't disable sandbox");
		}
		return true;
	}
	G_enabled = e;
#if LIBC_HAVE_PLEDGE
	if (G_enabled && pledge ("stdio rpath tty prot_exec inet", NULL) == -1) {
		R_LOG_ERROR ("sandbox: pledge call failed");
		return false;
	}
#endif
#if HAVE_CAPSICUM
	if (G_enabled) {
#if __FreeBSD_version >= 1000000
		cap_rights_t wrt, rdr;

		if (!cap_rights_init (&wrt, CAP_READ, CAP_WRITE)) {
			R_LOG_ERROR ("sandbox: write descriptor failed");
			return false;
		}

		if (!cap_rights_init (&rdr, CAP_READ, CAP_EVENT, CAP_FCNTL)) {
			R_LOG_ERROR ("sandbox: read descriptor failed");
			return false;
		}

		if (cap_rights_limit (STDIN_FILENO, &rdr) == -1) {
			R_LOG_ERROR ("sandbox: stdin protection failed");
			return false;
		}

		if (cap_rights_limit (STDOUT_FILENO, &wrt) == -1) {
			R_LOG_ERROR ("sandbox: stdout protection failed");
			return false;
		}

		if (cap_rights_limit (STDERR_FILENO, &wrt) == -1) {
			R_LOG_ERROR ("sandbox: stderr protection failed");
			return false;
		}
#endif

		if (cap_enter () != 0) {
			R_LOG_ERROR ("sandbox: call_enter failed");
			return false;
		}
	}
#endif
#if LIBC_HAVE_PRIV_SET
	if (G_enabled) {
		priv_set_t *priv = priv_allocset();
		const char *const privrules[] = {
			PRIV_PROC_INFO,
			PRIV_PROC_SESSION,
			PRIV_PROC_ZONE,
			PRIV_NET_OBSERVABILITY
		};

		size_t i, privrulescnt = sizeof (privrules) / sizeof (privrules[0]);
		if (!priv) {
			R_LOG_ERROR ("sandbox: priv_allocset failed");
			return false;
		}
		priv_basicset (priv);
		for (i = 0; i < privrulescnt; i ++) {
			if (priv_delset (priv, privrules[i]) != 0) {
				priv_emptyset (priv);
				priv_freeset (priv);
				R_LOG_ERROR ("sandbox: priv_delset failed");
				return false;
			}
		}

		priv_freeset (priv);
	}
#endif
	return G_enabled;
}

static inline int bytify(int v) {
#if R2__WINDOWS__ || __wasi__
	// on windows, there are no WEXITSTATUS, the return value is right there
	unsigned int uv = (unsigned int)v;
	return (int) ((uv > 255)? 1: 0);
#else
	// on unix, return code is (system() >> 8) & 0xff
	if (v == -1 || WIFEXITED (v)) {
		return WEXITSTATUS (v);
	}
#endif
	return 1;
}

R_API FILE *r_sandbox_popen(const char *cmd, const char *mode) {
	R_RETURN_VAL_IF_FAIL (cmd && mode, NULL);
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_EXEC, NULL);
#if R2__WINDOWS__
	return _popen (cmd, mode);
#elif LIBC_HAVE_SYSTEM
	return popen (cmd, mode);
#else
	return NULL;
#endif
}

R_API int r_sandbox_system(const char *x, int n) {
	R_RETURN_VAL_IF_FAIL (x, -1);
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_EXEC, -1);
#if R2__WINDOWS__
	return system (x);
#elif LIBC_HAVE_FORK
#if LIBC_HAVE_SYSTEM
	if (n) {
#if APPLE_SDK_IPHONEOS
#include <spawn.h>
		int argc;
		char *cmd = strdup (x);
		char **argv = r_str_argv (cmd, &argc);
		if (argv) {
			char *argv0 = r_file_path (argv[0]);
			pid_t pid = 0;
			int r = posix_spawn (&pid, argv0, NULL, NULL, argv, NULL);
			if (r != 0) {
				return bytify (-1);
			}
			int status;
			int s = waitpid (pid, &status, 0);
			return bytify (WEXITSTATUS (s));
		}
		int child = fork ();
		if (child == -1) {
			return bytify (-1);
		}
		if (child) {
			return bytify (waitpid (child, NULL, 0));
		}
#else
		// the most common execution path
		return bytify (system (x));
#endif
	}
#else
	#include <spawn.h>
	if (n && !strchr (x, '|')) {
		char **argv, *cmd = strdup (x);
		int rc, pid, argc;
		char *isbg = strchr (cmd, '&');
		// XXX this is hacky
		if (isbg) {
			*isbg = 0;
		}
		argv = r_str_argv (cmd, &argc);
		if (argv) {
			char *argv0 = r_file_path (argv[0]);
			if (!argv0) {
				R_LOG_ERROR ("Cannot find '%s'", argv[0]);
				return bytify (-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);
			}
			r_str_argv_free (argv);
			free (argv0);
			return bytify (rc);
		}
		R_LOG_ERROR ("parsing command arguments");
		return bytify (-1);
	}
#endif
	char *bin_sh = r_file_binsh ();
	int rc = execlp (bin_sh, bin_sh, "-c", x, (const char*)NULL);
	if (rc == -1) {
		r_sys_perror ("execl");
	}
	free (bin_sh);
	exit (bytify (rc));
#endif
	return bytify (-1);
}

R_API bool r_sandbox_creat(const char *path, int mode) {
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_DISK, false);
	if (r_sandbox_enable (false)) {
		return false; // creating files is not allowed in sandbox even with DISK grain
	}
	int fd = open (path, O_CREAT | O_TRUNC | O_WRONLY, mode);
	if (fd != -1) {
		close (fd);
		return true;
	}
	return false;
}

static inline char *expand_home(const char *p) {
	return (*p == '~')? r_file_home (p): strdup (p);
}

R_API int r_sandbox_lseek(int fd, ut64 addr, int whence) {
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_DISK, -1);
	return lseek (fd, (off_t)addr, whence);
}

R_API int r_sandbox_truncate(int fd, ut64 length) {
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_DISK, -1);
#ifdef _MSC_VER
	return _chsize_s (fd, length);
#else
	return ftruncate (fd, (off_t)length);
#endif
}

R_API int r_sandbox_read(int fd, ut8 *buf, int len) {
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_DISK, -1);
	return read (fd, buf, len);
}

R_API int r_sandbox_write(int fd, const ut8* buf, int len) {
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_DISK, -1);
	return write (fd, buf, len);
}

R_API int r_sandbox_close(int fd) {
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_DISK, -1);
	return close (fd);
}

/* perm <-> mode */
R_API int r_sandbox_open(const char *path, int perm, int mode) {
	R_RETURN_VAL_IF_FAIL (path, -1);
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_DISK, -1);
	char *epath = expand_home (path);
	int ret = -1;
#if R2__WINDOWS__
	if (!strcmp (path, "/dev/null")) {
		path = "NUL";
	}
#endif
	if (r_sandbox_enable (false)) {
		if ((perm & (O_CREAT | O_RDWR | O_WRONLY | O_TRUNC | O_APPEND))
			|| (!r_sandbox_check_path (epath))) {
			free (epath);
			return -1;
		}
	}
#if R2__WINDOWS__
	{
		DWORD flags = 0;
		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;
		} 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;
		}

		wchar_t *wepath = r_utf8_to_utf16 (epath);
		if (!wepath) {
			free (epath);
			return -1;
		}
		HANDLE h = CreateFileW (wepath, permission, FILE_SHARE_READ | (read_only ? 0 : FILE_SHARE_WRITE), NULL, creation, flags, NULL);
		if (h != INVALID_HANDLE_VALUE) {
			ret = _open_osfhandle ((intptr_t)h, perm);
		}
		free (wepath);
	}
#else // R2__WINDOWS__
	ret = open (epath, perm, mode);
#endif // R2__WINDOWS__
	free (epath);
	return ret;
}

R_API FILE *r_sandbox_fopen(const char *path, const char *mode) {
	R_RETURN_VAL_IF_FAIL (path && mode, NULL);
	FILE *ret = NULL;
	char *epath = expand_home (path);
	if (!epath) {
		return NULL;
	}
	if (!r_sandbox_check_writepath (epath)
			&& (!r_sandbox_check (R_SANDBOX_GRAIN_FILES | R_SANDBOX_GRAIN_DISK)
				|| (r_sandbox_enable (false) && (strpbrk (mode, "wa+") || !r_sandbox_check_path (epath))))) {
		free (epath);
		return NULL;
	}
	if ((strchr (mode, 'w') || strchr (mode, 'a') || r_file_is_regular (epath))) {
#if R2__WINDOWS__
		wchar_t *wepath = r_utf8_to_utf16 (epath);
		if (wepath) {
			wchar_t *wmode = r_utf8_to_utf16 (mode);
			if (wmode) {
				ret = _wfopen (wepath, wmode);
				free (wmode);
			}
			free (wepath);
		}
#else // R2__WINDOWS__
		ret = fopen (epath, mode);
#endif // R2__WINDOWS__
	}
	free (epath);
	return ret;
}

R_API int r_sandbox_chdir(const char *path) {
	R_RETURN_VAL_IF_FAIL (path, -1);
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_FILES | R_SANDBOX_GRAIN_DISK, -1);
	if (r_sandbox_enable (false) && !r_sandbox_check_path (path)) {
		return -1;
	}
	return chdir (path);
}

R_API int r_sandbox_kill(int pid, int sig) {
	R_RETURN_VAL_IF_FAIL (pid != -1, -1);
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_EXEC, -1);
#if HAVE_SYSTEM && R2__UNIX__
	int ret = kill (pid, sig);
	const bool sync_kill = false;
	if (sync_kill) {
		waitpid (pid, NULL, 0);
	}
	return ret;
#endif
	return -1;
}
#if R2__WINDOWS__
R_API HANDLE r_sandbox_opendir(const char *path, WIN32_FIND_DATAW *entry) {
	R_RETURN_VAL_IF_FAIL (path, NULL);
	if (!r_sandbox_check_writepath (path)) {
		R_SANDBOX_GUARD (R_SANDBOX_GRAIN_FILES | R_SANDBOX_GRAIN_DISK, NULL);
		if (r_sandbox_enable (false) && !r_sandbox_check_path (path)) {
			return NULL;
		}
	}
	wchar_t dir[MAX_PATH];
	wchar_t *wcpath = 0;
	if (!(wcpath = r_utf8_to_utf16 (path))) {
		return NULL;
	}
	swprintf (dir, MAX_PATH, L"%ls\\*.*", wcpath);
	free (wcpath);
	return FindFirstFileW (dir, entry);
}
#else
R_API DIR* r_sandbox_opendir(const char *path) {
	R_RETURN_VAL_IF_FAIL (path, NULL);
	if (!r_sandbox_check_writepath (path)) {
		R_SANDBOX_GUARD (R_SANDBOX_GRAIN_FILES | R_SANDBOX_GRAIN_DISK, NULL);
		if (r_sandbox_enable (false) && !r_sandbox_check_path (path)) {
			return NULL;
		}
	}
	return opendir (path);
}
#endif
R_API bool r_sys_stop(void) {
	R_SANDBOX_GUARD (R_SANDBOX_GRAIN_EXEC, false);
#if R2__UNIX__
	return !r_sandbox_kill (0, SIGTSTP);
#else
	return false;
#endif
}
