Blob Blame History Raw
/*********************************************************/
/*  ptycon: a pty-console bridge                         */
/*  Copyright (C) 2016--2017  SysDeer Technologies, LLC  */
/*  Released under GPLv2 and GPLv3; see COPYING.PTYCON.  */
/*********************************************************/

#include <ntcon/ntcon.h>
#include <ntapi/ntapi.h>
#include <ntapi/nt_atomic.h>
#include <gdi/gdi.h>

#include <stdint.h>

#include <ptycon/ptycon.h>
#include "ptycon_init_impl.h"
#include "ptycon_nolibc_impl.h"

#define ARGV_DRIVER

#include "ptycon_version.h"
#include "ptycon_daemon_impl.h"
#include "ptycon_driver_impl.h"
#include "argv/argv.h"

/* pty integration */
#include <psxtypes/section/freestd.h>

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

/* ntapi accessor table */
const ntapi_vtbl * ptyc_ntapi;

/* ntcon accessor table */
static ntcon_vtbl ptyc_ntcon_vtbl;
const  ntcon_vtbl * const ptyc_ntcon = &ptyc_ntcon_vtbl;

/* gdi accessor table */
static gdi_vtbl ptyc_gdi_vtbl;
const  gdi_vtbl * const ptyc_gdi = &ptyc_gdi_vtbl;

/* daemon */
static struct ptyc_daemon_ctx ptyc_daemon_ctx;

/* package info */
static const struct ptyc_source_version ptyc_src_version = {
	PTYC_TAG_VER_MAJOR,
	PTYC_TAG_VER_MINOR,
	PTYC_TAG_VER_PATCH,
	PTYCON_GIT_VERSION
};

struct ptyc_driver_ctx_alloc {
	struct argv_meta *		meta;
	struct ptyc_driver_ctx_impl	ctx;
	uint64_t			guard;
	const char *			units[];
};

struct ptyc_split_vector {
	char **		targv;
	char **		eargv;
};

static int32_t ptyc_once = 0;

static int32_t ptyc_vtbl_init(void)
{
	int32_t		status;
	nt_timeout	timeout;

	(void)tty_affiliation;

	switch (at_locked_cas_32(&ptyc_once,0,1)) {
		case 0:
			if ((status = ntcon_vtbl_init(&ptyc_ntcon_vtbl))) {
				at_locked_add_32(&ptyc_once,2);
				return status;
			}

			if ((status = gdi_vtbl_init(&ptyc_gdi_vtbl))) {
				at_locked_add_32(&ptyc_once,2);
				return status;
			}

			at_locked_inc_32(&ptyc_once);
			return 0;

		case 1:
			timeout.quad = -10;

			for (; (at_locked_cas_32(&ptyc_once,0,1) == 1); )
				ntapi->zw_delay_execution(
					NT_SYNC_ALERTABLE,
					&timeout);

			return (ptyc_once == 2)
				? 0 : -1;

		case 2:
			return 0;

		case 3:
		default:
			return -1;
	}
}

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

	if (flags & PTYC_DRIVER_VERBOSITY_NONE)
		ret |= ARGV_VERBOSITY_NONE;

	if (flags & PTYC_DRIVER_VERBOSITY_ERRORS)
		ret |= ARGV_VERBOSITY_ERRORS;

	if (flags & PTYC_DRIVER_VERBOSITY_STATUS)
		ret |= ARGV_VERBOSITY_STATUS;

	return ret;
}

static int ptyc_driver_usage(
	const char *			program,
	const char *			arg,
	const struct argv_option **	optv,
	struct argv_meta *		meta)
{
	char header[512];

	snprintf(header,sizeof(header),
		"Usage: %s [options] <file>...\n" "Options:\n",
		program);

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

	return PTYC_USAGE;
}

static struct ptyc_driver_ctx_impl * ptyc_driver_ctx_alloc(
	struct argv_meta *		meta,
	const struct ptyc_common_ctx *	cctx,
	size_t				nunits)
{
	struct ptyc_driver_ctx_alloc *	ictx;
	size_t				size;
	struct argv_entry *		entry;
	const char **			units;
	nt_runtime_data *		rtdata;

	if (ntapi->tt_get_runtime_data(&rtdata,0))
		return 0;

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

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

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

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

	ictx->meta = meta;
	ictx->ctx.ctx.units = ictx->units;
	ictx->ctx.rtdata = rtdata;

	ptyc_daemon_ctx.driver_ctx = &ictx->ctx.ctx;

	return &ictx->ctx;
}

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

#define PTYC_SARGV_ELEMENTS		1024

static int ptyc_split_argv(
	char **				argv,
	struct ptyc_split_vector *	sargv)
{
	ptrdiff_t	argc;
	char **		parg;

	/* argc */
	for (parg=argv; *parg; )
		parg++;

	if ((argc = parg - argv) >= PTYC_SARGV_ELEMENTS)
		return -1;

	/* clone argv into targv */
	ntapi->tt_aligned_block_memset(
		(uintptr_t *)sargv->targv,
		0,PTYC_SARGV_ELEMENTS*sizeof(char *));

	ntapi->tt_aligned_block_memcpy(
		(uintptr_t *)sargv->targv,
		(uintptr_t *)argv,
		argc*sizeof(char *));

	/* eargv */
	for (parg=sargv->targv; *parg; parg++) {
		if (!(strcmp(*parg,"-e")) || !(strcmp(*parg,"--exec"))) {
			sargv->eargv = &argv[parg-sargv->targv];
			sargv->eargv++;
			*parg = 0;
			return 0;
		}
	}

	return 0;
}

int ptyc_get_driver_ctx(
	char **				argv,
	char **				envp,
	uint32_t			flags,
	struct ptyc_driver_ctx **	pctx)
{
	struct ptyc_driver_ctx_impl *	ctx;
	struct ptyc_common_ctx		cctx;
	struct ptyc_split_vector	sargv;
	const struct argv_option *	optv[PTYC_OPTV_ELEMENTS];
	struct argv_meta *		meta;
	struct argv_entry *		entry;
	size_t				nunits;
	const char *			program;
	char *				targv[PTYC_SARGV_ELEMENTS];

	(void)envp;

	if (ptyc_init())
		return -1;

	if (ptyc_vtbl_init())
		return -1;

	argv_optv_init(ptyc_default_options,optv);

	sargv.targv = targv;
	sargv.eargv = 0;

	if (ptyc_split_argv(argv,&sargv))
		return -1;

	if (!(meta = argv_get(
			sargv.targv,optv,
			ptyc_argv_flags(flags),
			STDERR_FILENO)))
		return -1;

	nunits	= 0;
	program = argv_program_name(argv[0]);
	memset(&cctx,0,sizeof(cctx));
	cctx.drvflags = flags;
	cctx.eargv = sargv.eargv;

	if (!argv[1] && (flags & PTYC_DRIVER_VERBOSITY_USAGE))
		return ptyc_driver_usage(program,0,optv,meta);

	/* get options, count units */
	for (entry=meta->entries; entry->fopt || entry->arg; entry++) {
		if (entry->fopt) {
			switch (entry->tag) {
				case TAG_HELP:
					if (flags & PTYC_DRIVER_VERBOSITY_USAGE)
						return ptyc_driver_usage(program,entry->arg,optv,meta);

				case TAG_VERSION:
					cctx.drvflags |= PTYC_DRIVER_VERSION;
					break;

				case TAG_DAEMON:
					if (!strcmp("always",entry->arg))
						cctx.drvflags |= PTYC_DRIVER_DAEMON_ALWAYS;

					else if (!strcmp("never",entry->arg))
						cctx.drvflags |= PTYC_DRIVER_DAEMON_NEVER;

					break;

				case TAG_DEBUG:
					if (!entry->arg) {
						cctx.drvflags |= PTYC_DRIVER_DBG_EVENT;

					} else if (!strcmp("event",entry->arg)) {
						cctx.drvflags |= PTYC_DRIVER_DBG_EVENT;

					} else if (!strcmp("oven",entry->arg)) {
						cctx.drvflags &= ~(uint64_t)PTYC_DRIVER_DBG_RAW;
						cctx.drvflags |= PTYC_DRIVER_DBG_OVEN;

					} else if (!strcmp("raw",entry->arg)) {
						cctx.drvflags &= ~(uint64_t)PTYC_DRIVER_DBG_OVEN;
						cctx.drvflags |= PTYC_DRIVER_DBG_RAW;
					}

					break;

				case TAG_BELL:
					if ((!entry->arg) || !strcmp("on",entry->arg))
						cctx.drvflags |= PTYC_DRIVER_BELL;
					break;

				case TAG_CAT:
					nunits++;
					break;

				case TAG_SYSROOT:
					cctx.sysroot = entry->arg;
					break;

				case TAG_WAIT:
					cctx.drvflags |= PTYC_DRIVER_DBG_WAIT;
					break;
			}
		} else
			/* strict */
			return ptyc_driver_usage(program,0,optv,meta);
	}

	if (cctx.sysroot && ptyc_open_dir(&cctx.hroot,0,cctx.sysroot,false)) {
		if (flags & PTYC_DRIVER_VERBOSITY_ERRORS)
			ptyc_dprintf(STDERR_FILENO,
				"%s: error: could not open sysroot directory '%s'",
				program,cctx.sysroot);
		return ptyc_get_driver_ctx_fail(meta);
	}

	if (!(ctx = ptyc_driver_ctx_alloc(meta,&cctx,nunits)))
		return ptyc_get_driver_ctx_fail(meta);

	if (ptyc_daemon_init(&ptyc_daemon_ctx,cctx.drvflags))
		return ptyc_get_driver_ctx_fail(meta);

	ctx->ctx.program	= program;
	ctx->ctx.cctx		= &ctx->cctx;

	*pctx = &ctx->ctx;
	return PTYC_OK;
}

static void ptyc_free_driver_ctx_impl(struct ptyc_driver_ctx_alloc * ictx)
{
	ptyc_free_pty(&ictx->ctx.ctx);
	argv_free(ictx->meta);
	free(ictx);
}

void ptyc_free_driver_ctx(struct ptyc_driver_ctx * ctx)
{
	struct ptyc_driver_ctx_alloc *	ictx;
	uintptr_t			addr;

	if (ctx) {
		addr = (uintptr_t)ctx - offsetof(struct ptyc_driver_ctx_impl,ctx);
		addr = addr - offsetof(struct ptyc_driver_ctx_alloc,ctx);
		ictx = (struct ptyc_driver_ctx_alloc *)addr;
		ptyc_free_driver_ctx_impl(ictx);
	}
}

const struct ptyc_source_version * ptyc_source_version(void)
{
	return &ptyc_src_version;
}