Blob Blame History Raw
/***********************************************************/
/*  ntux: native translation und extension                 */
/*  Copyright (C) 2016--2021  SysDeer Technologies, LLC    */
/*  Released under GPLv2 and GPLv3; see COPYING.NTUX.      */
/***********************************************************/

#include <psxabi/sys_abitypes.h>
#include <psxabi/sys_fcntl.h>
#include <psxabi/sys_process.h>
#include <psxabi/sys_sysapi.h>
#include <psxxfi/xfi_framework.h>
#include <psxscl/psxscl.h>

#include <ntapi/nt_tty.h>
#include <stdint.h>
#include <stdbool.h>

#include <ntux/ntux.h>
#include "ntux_nolibc_impl.h"

#define ARGV_DRIVER

#include "ntux_version.h"
#include "ntux_driver_impl.h"
#include "argv/argv.h"

/* framework integration */
#include <psxtypes/section/midipix.h>

__attr_section_decl__(".midipix")
static const nt_tty_affiliation tty_affiliation
	__attr_section__(".midipix")
	= NT_TTY_AFFILIATION_DEFAULT;

/* ntux command names */
static const char * const ntux_cmd_name[NTUX_CMD_CAP] = {
	[NTUX_CMD_STAT]       = "stat",
	[NTUX_CMD_SPAWN]      = "spawn",
	[NTUX_CMD_STRACE]     = "strace",
	[NTUX_CMD_CHMOD]      = "chmod",
	[NTUX_CMD_ACEIT]      = "aceit",
	[NTUX_CMD_FSPATH]     = "fspath",
	[NTUX_CMD_BRIDGE]     = "bridge",
};

/* ntux exec commands */
static const bool const ntux_cmd_exec[NTUX_CMD_CAP] = {
	[NTUX_CMD_STAT]       = false,
	[NTUX_CMD_SPAWN]      = true,
	[NTUX_CMD_STRACE]     = true,
	[NTUX_CMD_CHMOD]      = false,
	[NTUX_CMD_ACEIT]      = false,
	[NTUX_CMD_FSPATH]     = false,
	[NTUX_CMD_BRIDGE]     = true,
};

/* ntux command options */
static const struct argv_option * ntux_cmd_options[NTUX_CMD_CAP] = {
	[NTUX_CMD_DEFAULT]    = ntux_default_options,
	[NTUX_CMD_STAT]       = ntux_default_options,
	[NTUX_CMD_SPAWN]      = ntux_spawn_options,
	[NTUX_CMD_STRACE]     = ntux_strace_options,
	[NTUX_CMD_CHMOD]      = ntux_chmod_options,
	[NTUX_CMD_ACEIT]      = ntux_aceit_options,
	[NTUX_CMD_FSPATH]     = ntux_fspath_options,
	[NTUX_CMD_BRIDGE]     = ntux_bridge_options,
};

/* package info */
static const struct ntux_source_version ntux_src_version = {
	NTUX_TAG_VER_MAJOR,
	NTUX_TAG_VER_MINOR,
	NTUX_TAG_VER_PATCH,
	NTUX_GIT_VERSION
};

struct ntux_driver_ctx_alloc {
	struct argv_meta *		meta;
	struct ntux_driver_ctx_impl	ctx;
	uint64_t			guard;
	const char *			units[];
};

static uint32_t ntux_argv_flags(uint32_t flags)
{
	uint32_t ret = ARGV_CLONE_VECTOR;

	if (flags & NTUX_DRIVER_VERBOSITY_NONE)
		ret |= ARGV_VERBOSITY_NONE;

	if (flags & NTUX_DRIVER_VERBOSITY_ERRORS)
		ret |= ARGV_VERBOSITY_ERRORS;

	if (flags & NTUX_DRIVER_VERBOSITY_STATUS)
		ret |= ARGV_VERBOSITY_STATUS;

	return ret;
}

static int ntux_driver_usage(
	int				fdout,
	const char *			program,
	const char *			arg,
	const struct argv_option **	optv,
	struct argv_meta *		meta,
	enum ntux_cmd			cmd)
{
	char		header[512];
	char *		cmdarg[2];
	const char *	cmdname;

	if (cmd == NTUX_CMD_DEFAULT) {
		cmdarg[0] = "";
		cmdarg[1] = "";
		cmdname   = "";
	} else {
		cmdarg[0] = " (--cmd=";
		cmdarg[1] = ")";
		cmdname   = ntux_cmd_name[cmd];
	}

	snprintf(header,sizeof(header),
		"Usage: %s [options] ...\n"
		"Usage: %s [options] [--cmd=<command>] <arg> <arg> ...\n\n"
		"Notes: --cmd must precede all non-option arguments, as well as\n"
		"       all arguments that are specific to the selected command;\n"
		"       for commands that spawn other programs (e.g. spawn, strace),\n"
		"       the first non-option argument must specify the name of the\n"
		"       program to be executed.\n\n"
		"Options%s%s%s:\n",
		program,program,cmdarg[0],cmdname,cmdarg[1]);

	argv_usage(fdout,header,optv,arg);
	argv_free(meta);

	return NTUX_USAGE;
}

static struct ntux_driver_ctx_impl * ntux_driver_ctx_alloc(
	struct argv_meta *		meta,
	const struct ntux_fd_ctx *	fdctx,
	const struct ntux_common_ctx *	cctx,
	size_t				nunits)
{
	struct ntux_driver_ctx_alloc *	ictx;
	size_t				size;
	struct argv_entry *		entry;
	const char **			units;
	int				elements;

	(void)tty_affiliation;

	size =  sizeof(struct ntux_driver_ctx_alloc);
	size += (nunits+1)*sizeof(const char *);

	if (!(ictx = calloc(1,size)))
		return 0;

	elements = sizeof(ictx->ctx.erribuf) / sizeof(*ictx->ctx.erribuf);

	ictx->ctx.errinfp  = &ictx->ctx.erriptr[0];
	ictx->ctx.erricap  = &ictx->ctx.erriptr[--elements];

	memcpy(&ictx->ctx.fdctx,fdctx,sizeof(*fdctx));
	memcpy(&ictx->ctx.cctx,cctx,sizeof(*cctx));

	for (entry=meta->entries,units=ictx->units; entry->fopt || entry->arg; entry++)
		if (!entry->fopt)
			*units++ = entry->arg;

	ictx->meta = meta;
	ictx->ctx.ctx.units = ictx->units;
	ictx->ctx.ctx.errv  = ictx->ctx.errinfp;
	return &ictx->ctx;
}

static int ntux_cctx_update(
	const char *			program,
	const struct argv_option **	optv,
	struct argv_meta *		meta,
	uint32_t			flags,
	const struct ntux_fd_ctx *	fdctx,
	struct ntux_common_ctx *	cctx,
	size_t *			nunits)
{
	struct argv_entry *		entry;

	/* get options, count units */
	for (entry=meta->entries; entry->fopt || entry->arg; entry++) {
		if (entry->fopt) {
			switch (entry->tag) {
				case TAG_HELP:
					if (flags & NTUX_DRIVER_VERBOSITY_USAGE)
						return ntux_driver_usage(
							fdctx->fdout,
							program,entry->arg,
							optv,0,cctx->cmd);

				case TAG_VERSION:
					cctx->drvflags |= NTUX_DRIVER_VERSION;
					break;

				case TAG_VERBOSE:
					cctx->drvflags |= NTUX_DRIVER_VERBOSE;
					break;

				case TAG_SILENT:
					cctx->drvflags |= NTUX_DRIVER_SILENT;
					break;

				case TAG_CMD:
					if (*nunits)
						return ntux_driver_usage(
							fdctx->fderr,
							program,entry->arg,
							optv,0,cctx->cmd);

					if (!strcmp(entry->arg,"stat"))
						cctx->cmd = NTUX_CMD_STAT;

					else if (!strcmp(entry->arg,"spawn"))
						cctx->cmd = NTUX_CMD_SPAWN;

					else if (!strcmp(entry->arg,"strace"))
						cctx->cmd = NTUX_CMD_STRACE;

					else if (!strcmp(entry->arg,"chmod"))
						cctx->cmd = NTUX_CMD_CHMOD;

					else if (!strcmp(entry->arg,"aceit"))
						cctx->cmd = NTUX_CMD_ACEIT;

					else if (!strcmp(entry->arg,"fspath"))
						cctx->cmd = NTUX_CMD_FSPATH;

					else if (!strcmp(entry->arg,"bridge"))
						cctx->cmd = NTUX_CMD_BRIDGE;

					break;

				case TAG_LOADER:
					cctx->loader = entry->arg;
					break;

				case TAG_LOGFILE:
					cctx->logfile = entry->arg;
					break;

				case TAG_REFOBJ:
					cctx->refobj = entry->arg;
					break;

				case TAG_STRMODE:
					cctx->strmode = entry->arg;
					break;

				case TAG_RECURSIVE:
					cctx->drvflags |= NTUX_DRIVER_RECURSIVE;
					break;

				case TAG_CHANGES:
					cctx->drvflags |= NTUX_DRIVER_CHANGES;
					break;

				case TAG_OWNER:
					cctx->owner = entry->arg;
					break;

				case TAG_GROUP:
					cctx->group = entry->arg;
					break;

				case TAG_ROOTED:
					cctx->drvflags &= ~(uint64_t)NTUX_DRIVER_ROOTLESS;
					cctx->drvflags |= NTUX_DRIVER_ROOTED;
					break;

				case TAG_ROOTLESS:
					cctx->drvflags &= ~(uint64_t)NTUX_DRIVER_ROOTED;
					cctx->drvflags |= NTUX_DRIVER_ROOTLESS;
					break;

				case TAG_DUMP:
					cctx->drvflags |= NTUX_DRIVER_DUMP;
					break;

				case TAG_INTERP:
					cctx->interp = entry->arg;
					break;

				case TAG_OPTARG:
					cctx->optarg = entry->arg;
					break;

				case TAG_SYNTAX:
				case TAG_RPATH:
				case TAG_APATH:
				case TAG_NPATH:
				case TAG_DPATH:
					cctx->drvflags &= ~(uint64_t)NTUX_DRIVER_RPATH;
					cctx->drvflags &= ~(uint64_t)NTUX_DRIVER_APATH;
					cctx->drvflags &= ~(uint64_t)NTUX_DRIVER_NPATH;
					cctx->drvflags &= ~(uint64_t)NTUX_DRIVER_DPATH;

					if (entry->tag == TAG_SYNTAX) {
						if (!strcmp(entry->arg,"relative"))
							cctx->drvflags |= NTUX_DRIVER_RPATH;

						else if (!strcmp(entry->arg,"absolute"))
							cctx->drvflags |= NTUX_DRIVER_APATH;

						else if (!strcmp(entry->arg,"native"))
							cctx->drvflags |= NTUX_DRIVER_NPATH;

						else if (!strcmp(entry->arg,"driver"))
							cctx->drvflags |= NTUX_DRIVER_DPATH;

					} else if (entry->tag == TAG_RPATH) {
						cctx->drvflags |= NTUX_DRIVER_RPATH;

					} else if (entry->tag == TAG_APATH) {
						cctx->drvflags |= NTUX_DRIVER_APATH;

					} else if (entry->tag == TAG_NPATH) {
						cctx->drvflags |= NTUX_DRIVER_NPATH;

					} else if (entry->tag == TAG_DPATH) {
						cctx->drvflags |= NTUX_DRIVER_DPATH;
					}

					break;
			}
		} else {
			(*nunits)++;
		}
	}

	return 0;
}

static int ntux_get_driver_ctx_fail(struct argv_meta * meta)
{
	argv_free(meta);
	return -1;
}

static int ntux_cmd_program_is_bridge(const char * program)
{
	const char *	slash;
	const char *	mark;

	mark = (slash = strrchr(program,'/'))
		? ++slash : program;

	if (!strcmp(mark,"ntux.bridge.exe"))
		return true;

	else if (!strcmp(mark,"ntux.bridge.exe.interp"))
		return true;

	else if (!strcmp(mark,"ntux.bridge.exe.interp.abspath"))
		return true;

	else if (!strcmp(mark,"ntux.bridge.exe.interp.cwdpath"))
		return true;

	return false;
}

static int ntux_cmd_from_program(const char * program)
{
	const char *	dot;
	const char *	hyphen;
	const char *	mark;

	dot    = strrchr(program,'.');
	hyphen = strrchr(program,'-');

	if (hyphen > dot)
		mark = ++hyphen;
	else if (dot > hyphen)
		mark = ++dot;
	else
		mark = program;

	if (!strcmp(mark,"stat")) {
		return NTUX_CMD_STAT;

	} else if (!strcmp(mark,"spawn")) {
		return NTUX_CMD_SPAWN;

	} else if (!strcmp(mark,"strace")) {
		return NTUX_CMD_STRACE;

	} else if (!strcmp(mark,"chmod")) {
		return NTUX_CMD_CHMOD;

	} else if (!strcmp(mark,"aceit")) {
		return NTUX_CMD_ACEIT;

	} else if (!strcmp(mark,"fspath")) {
		return NTUX_CMD_FSPATH;

	} else if (!strcmp(mark,"bridge")) {
		return NTUX_CMD_BRIDGE;

	} else if (ntux_cmd_program_is_bridge(program)) {
		return NTUX_CMD_BRIDGE;

	} else {
		return NTUX_CMD_DEFAULT;
	}
}

int ntux_get_driver_ctx(
	char **				argv,
	char **				envp,
	uint32_t			flags,
	const struct ntux_fd_ctx *	fdctx,
	struct ntux_driver_ctx **	pctx)
{
	int				ret;
	int				val;
	struct ntux_driver_ctx_impl *	ictx;
	struct ntux_common_ctx		cctx;
	const struct argv_option *	optv[NTUX_OPTV_ELEMENTS];
	struct argv_meta *		meta;
	size_t				nunits;
	const char *			program;
	char **				parg;
	char **				pargcap;
	char **				cmdargv;
	char *				cmdmark;
	char **				execargv;
	char *				execarg;
	char *				interp;
	char *				pathbuf;
	size_t				pathlen;
	struct argv_ctx			ctx = {ARGV_VERBOSITY_NONE,
                                               ARGV_MODE_SCAN,
                                               0,0,0,0,0,0,0};

	/* fdctx */
	if (!fdctx) {
		fdctx = &(const struct ntux_fd_ctx) {
			.fdin  = STDIN_FILENO,
			.fdout = STDOUT_FILENO,
			.fderr = STDERR_FILENO,
			.fdlog = (-1),
			.fdcwd = AT_FDCWD,
			.fddst = AT_FDCWD,
		};
	}

	/* cctx */
	memset(&cctx,0,sizeof(cctx));

	nunits	      = 0;
	execargv      = 0;
	program       = argv_program_name(argv[0]);
	cctx.cmd      = ntux_cmd_from_program(program);
	cctx.drvflags = flags;

	switch (cctx.cmd) {
		case NTUX_CMD_BRIDGE:
			break;

		default:
			/* potential .exe bridge setup? */
			pathlen = ntux_strlen(argv[0]);

			if (pathlen + NTUX_EXE_SUFFIX_LEN >= NTUX_MAX_PATH)
				break;


			/* intermediate buffers */
			if (!(pathbuf = ntux_calloc(1,NTUX_MAX_PATH)))
				return NTUX_ERROR;

			if (!(interp = ntux_calloc(1,NTUX_MAX_PATH))) {
				ntux_free(pathbuf);
				return NTUX_ERROR;
			}

			/* is argv[0] a symlink to ntux's bridge symlink? */
			ret = __sys_readlinkat(
				fdctx->fdcwd,
				(const unsigned char *)argv[0],
				pathbuf,NTUX_MAX_PATH);

			if ((ret > 0) && (ret < NTUX_MAX_PATH)) {
				if (ntux_cmd_program_is_bridge(pathbuf)) {
					ntux_strcpy(pathbuf,argv[0]);
					ntux_strcpy(&pathbuf[pathlen],NTUX_EXE_SUFFIX);

					ret = __sys_readlinkat(
						fdctx->fdcwd,
						(const unsigned char *)pathbuf,
						interp,NTUX_MAX_PATH);

					ntux_free(pathbuf);

					if ((ret < 0) || (ret >= NTUX_MAX_PATH)) {
						ntux_free(interp);
						return NTUX_ERROR;
					}

					cctx.cmd    = NTUX_CMD_BRIDGE;
					cctx.interp = interp;
				}
			}

			break;
	}

	/* missing arguments? */
	argv_optv_init(ntux_cmd_options[cctx.cmd],optv);

	if (!argv[1] && (flags & NTUX_DRIVER_VERBOSITY_USAGE))
		return ntux_driver_usage(
			fdctx->fderr,
			program,0,
			optv,0,cctx.cmd);

	/* initial argv scan: ... --cmd=xxx ... */
	argv_scan(argv,optv,&ctx,0);

	/* position of last base ntux argument */
	if (ctx.erridx && ctx.unitidx)
		pargcap = &argv[ctx.unitidx];

	else if (ctx.erridx)
		pargcap = &argv[ctx.erridx];

	else
		for (pargcap=argv; *pargcap; pargcap++)
			(void)0;

	/* scan for --cmd */
	for (parg=argv, cmdargv=0; (parg<pargcap) && !cmdargv; parg++) {
		if (!strcmp(*parg,"--cmd") && parg[1]) {
				cmdargv = &parg[2];
				cmdmark = parg[2];
		} else if (!strncmp(*parg,"--cmd=",6)) {
				cmdargv = &parg[1];
				cmdmark = parg[1];
		}
	}

	/* invalid ntux arguments? */
	if (!ctx.erridx) {
		(void)0;

	} else if (&argv[ctx.erridx] >= cmdargv) {
		(void)0;

	} else if ((cctx.cmd == NTUX_CMD_SPAWN) && (ctx.erridx > ctx.unitidx)) {
		(void)0;

	} else if ((cctx.cmd == NTUX_CMD_STRACE) && (ctx.erridx > ctx.unitidx)) {
		(void)0;

	} else {
		if (flags & NTUX_DRIVER_VERBOSITY_ERRORS)
			argv_get(
				argv,optv,
				ARGV_VERBOSITY_ERRORS,
				fdctx->fderr);
		return -1;
	}

	/* process argv entries preceding --cmd */
	if (cmdargv) {
		*cmdargv = 0;

		if (!(meta = argv_get(
				argv,optv,
				ntux_argv_flags(flags),
				fdctx->fderr)))
			return -1;

		if (ntux_cctx_update(
				program,optv,meta,flags,
				fdctx,&cctx,&nunits)) {
			argv_free(meta);
			return -1;
		}

		argv_free(meta);

		*cmdargv-- = cmdmark;
		*cmdargv   = argv[0];
		argv       = cmdargv;
	}

	/* set option vector by command */
	if (cctx.cmd == NTUX_CMD_SPAWN)
		argv_optv_init(ntux_spawn_options,optv);

	else if (cctx.cmd == NTUX_CMD_STRACE)
		argv_optv_init(ntux_strace_options,optv);

	else if (cctx.cmd == NTUX_CMD_CHMOD)
		argv_optv_init(ntux_chmod_options,optv);

	else if (cctx.cmd == NTUX_CMD_ACEIT)
		argv_optv_init(ntux_aceit_options,optv);

	else if (cctx.cmd == NTUX_CMD_FSPATH)
		argv_optv_init(ntux_fspath_options,optv);

	else if (cctx.cmd == NTUX_CMD_BRIDGE)
		argv_optv_init(ntux_bridge_options,optv);

	/* spawn, strace, bridge */
	if (ntux_cmd_exec[cctx.cmd]) {
		argv_scan(argv,optv,&ctx,0);

		if (ctx.erridx && !ctx.unitidx) {
			if (flags & NTUX_DRIVER_VERBOSITY_ERRORS)
				argv_get(
					argv,optv,
					ntux_argv_flags(flags),
					fdctx->fderr);
			return -1;
		}

		if (!ctx.unitidx && (flags & NTUX_DRIVER_VERBOSITY_USAGE))
			ntux_driver_usage(
				fdctx->fderr,
				program,0,
				optv,0,cctx.cmd);

		if (!ctx.unitidx)
			return NTUX_ERROR;

		execargv  = &argv[ctx.unitidx];
		execarg   = argv[ctx.unitidx];
		*execargv = 0;
	}

	/* obtain ntux's own arguments */
	if (!(meta = argv_get(
			argv,optv,
			ntux_argv_flags(flags),
			fdctx->fderr)))
		return -1;

	if (ntux_cctx_update(
			program,optv,meta,flags,
			fdctx,&cctx,&nunits)) {
		argv_free(meta);
		return -1;
	}

	/* spawn, strace, bridge: exec argv */
	if (ntux_cmd_exec[cctx.cmd]) {
		*execargv  = execarg;
		cctx.sargv = execargv;
		cctx.senvp = envp;
		nunits     = 0;

		ret = __xfi_framework_get_int32_slot_value(
				PSX_RTDATA_UDAT32_ARGV0_IS_INTERP,
				&val);

		if (ret < 0)
			return NTUX_ERROR;

		if (val)
			cctx.sargv--;
	}

	/* finalize */
	if (nunits && !cctx.cmd)
		return ntux_driver_usage(
			fdctx->fderr,
			program,0,
			optv,meta,
			NTUX_CMD_DEFAULT);

	if (!(ictx = ntux_driver_ctx_alloc(meta,fdctx,&cctx,nunits)))
		return ntux_get_driver_ctx_fail(meta);

	ictx->interp      = interp;
	ictx->ctx.program = program;
	ictx->ctx.cctx    = &ictx->cctx;
	*pctx             = &ictx->ctx;

	return 0;
}

static void ntux_free_driver_ctx_impl(struct ntux_driver_ctx_alloc * ictx)
{
	if (ictx->ctx.interp)
		ntux_free(ictx->ctx.interp);

	argv_free(ictx->meta);
	free(ictx);
}

void ntux_free_driver_ctx(struct ntux_driver_ctx * ctx)
{
	struct ntux_driver_ctx_alloc *	ictx;
	uintptr_t			addr;

	if (ctx) {
		addr = (uintptr_t)ctx - offsetof(struct ntux_driver_ctx_impl,ctx);
		addr = addr - offsetof(struct ntux_driver_ctx_alloc,ctx);
		ictx = (struct ntux_driver_ctx_alloc *)addr;
		ntux_free_driver_ctx_impl(ictx);
	}
}

const struct ntux_source_version * ntux_source_version(void)
{
	return &ntux_src_version;
}

int ntux_get_driver_fdctx(
	const struct ntux_driver_ctx *	dctx,
	struct ntux_fd_ctx *		fdctx)
{
	struct ntux_driver_ctx_impl *	ictx;

	ictx = ntux_get_driver_ictx(dctx);

	fdctx->fdin  = ictx->fdctx.fdin;
	fdctx->fdout = ictx->fdctx.fdout;
	fdctx->fderr = ictx->fdctx.fderr;
	fdctx->fdlog = ictx->fdctx.fdlog;
	fdctx->fdcwd = ictx->fdctx.fdcwd;
	fdctx->fddst = ictx->fdctx.fddst;

	return 0;
}

int ntux_set_driver_fdctx(
	struct ntux_driver_ctx *	dctx,
	const struct ntux_fd_ctx *	fdctx)
{
	struct ntux_driver_ctx_impl *	ictx;

	ictx = ntux_get_driver_ictx(dctx);

	ictx->fdctx.fdin  = fdctx->fdin;
	ictx->fdctx.fdout = fdctx->fdout;
	ictx->fdctx.fderr = fdctx->fderr;
	ictx->fdctx.fdlog = fdctx->fdlog;
	ictx->fdctx.fdcwd = fdctx->fdcwd;
	ictx->fdctx.fddst = fdctx->fddst;

	return 0;
}