Blame src/argv/ntapi_tt_get_option.c

dd89bb
/********************************************************/
dd89bb
/*  ntapi: Native API core library                      */
4256e2
/*  Copyright (C) 2013--2016  Z. Gilboa                 */
dd89bb
/*  Released under GPLv2 and GPLv3; see COPYING.NTAPI.  */
dd89bb
/********************************************************/
dd89bb
dd89bb
#include <psxtypes/psxtypes.h>
dd89bb
#include <ntapi/ntapi.h>
dd89bb
#include "ntapi_impl.h"
dd89bb
dd89bb
dd89bb
/**
dd89bb
 *  a simple facility for minimal programs or system libraries
dd89bb
 *  with no libc available at the time of invocation, as well
dd89bb
 *  as applications using the midipix free-standing development
dd89bb
 *  environment.
dd89bb
 *
dd89bb
 *  the approach taken by this module to the support of short
dd89bb
 *  and long options reflects the above constraint, namely
dd89bb
 *  the absence of a callable libc at the time of invocation;
dd89bb
 *  there is no intent for interfaces in this module to
dd89bb
 *  be POSIXLY correct or otherwise portable.  the sole
dd89bb
 *  purpose of all functions in this module is to serve
dd89bb
 *  internal or otherwise free-standing midipix applications,
dd89bb
 *  and their relevance otherwise is accordingly non-existent.
dd89bb
 *
dd89bb
 *  all options are encoded in utf-16; note, however, that
dd89bb
 *  short options may only use code points that are located
dd89bb
 *  in the basic multilingual plane.
dd89bb
 *
dd89bb
 *  option values are either required or not allowed altogether,
dd89bb
 *  and the first character of an option value may not be a hyphen.
dd89bb
 *  if you need the first character of an option value to be a
dd89bb
 *  hyphen, then make sure you escape it somehow (for instance by
dd89bb
 *  enclosing it in quotation marks).
dd89bb
 *
dd89bb
 *  a short option and its value must reside in two separate
dd89bb
 *  argv[] elements (in other words: -ooutput is illegal).
dd89bb
 *
dd89bb
 *  a long option and its value must reside in the same argv[]
dd89bb
 *  element and be separated by a single equal sign.
dd89bb
 *
dd89bb
 *  Examples of valid options and option values:
dd89bb
 *  --------------------------------------------
dd89bb
 *  -o
dd89bb
 *  -o value
dd89bb
 *  --long-option-with-no-value
dd89bb
 *  --long-option=value
dd89bb
**/
dd89bb
dd89bb
#define HYPHEN		0x2D
dd89bb
#define EQUAL_SIGN	0x3D
dd89bb
dd89bb
dd89bb
static int __inline__ __fastcall __is_bmp_code_point(wchar16_t code_point)
dd89bb
{
dd89bb
	return (((code_point >= 0x0000) && (code_point < 0xD800)) \
188aac
		|| (code_point >= 0xE000));
dd89bb
}
dd89bb
dd89bb
dd89bb
static int __inline__ __fastcall __is_last_program_option(
dd89bb
	__in	nt_program_option *	option)
dd89bb
{
dd89bb
	return (!(option->short_name_code))
dd89bb
		&& (!(option->long_name))
dd89bb
		&& (!(option->long_name_hash));
dd89bb
}
dd89bb
dd89bb
dd89bb
static int __fastcall __is_short_option(wchar16_t * wch)
dd89bb
{
dd89bb
	return ((wch) && (*wch == HYPHEN)
dd89bb
		&& __is_bmp_code_point(*++wch)
dd89bb
		&& (*++wch == 0));
dd89bb
}
dd89bb
dd89bb
static int __fastcall __is_long_option(wchar16_t * wch)
dd89bb
{
dd89bb
	return ((wch) && (*wch == HYPHEN)
dd89bb
		&& (++wch) && (*wch == HYPHEN)
dd89bb
		&& (*++wch));
dd89bb
}
dd89bb
dd89bb
dd89bb
static int __fastcall __is_last_option_argument(wchar16_t * wch)
dd89bb
{
dd89bb
	return ((wch) && (*wch == HYPHEN)
dd89bb
		&& (*++wch == HYPHEN)
dd89bb
		&& (*++wch == 0));
dd89bb
}
dd89bb
dd89bb
dd89bb
static uint32_t __fastcall __compute_crc32_utf16_str(
dd89bb
	__in	const uint32_t *	crc32_table,
dd89bb
	__in	wchar16_t *		wch)
dd89bb
{
dd89bb
	uint32_t	crc32;
dd89bb
	unsigned char *	byte_buffer;
dd89bb
dd89bb
	/* crc32 hash... */
dd89bb
	crc32 = 0 ^ 0xFFFFFFFF;
dd89bb
dd89bb
	/* initialize byte_buffer */
dd89bb
	byte_buffer = (unsigned char *)wch;
dd89bb
dd89bb
	/* iterate */
dd89bb
	while (*byte_buffer) {
dd89bb
		/* two bytes at a time */
dd89bb
		crc32 = (crc32 >> 8) ^ crc32_table[(crc32 ^ *byte_buffer) & 0xFF];
dd89bb
		byte_buffer++;
dd89bb
		crc32 = (crc32 >> 8) ^ crc32_table[(crc32 ^ *byte_buffer) & 0xFF];
dd89bb
		byte_buffer++;
dd89bb
	}
dd89bb
dd89bb
	return crc32;
dd89bb
}
dd89bb
dd89bb
dd89bb
static uint32_t __fastcall __compute_crc32_long_option_name(
dd89bb
	__in	const uint32_t *	crc32_table,
dd89bb
	__in	wchar16_t *		wch_arg,
dd89bb
	__in	wchar16_t *		wch_termination)
dd89bb
{
dd89bb
	uint32_t	crc32;
dd89bb
	unsigned char *	byte_buffer;
dd89bb
dd89bb
	/* crc32 hash... */
dd89bb
	crc32 = 0 ^ 0xFFFFFFFF;
dd89bb
dd89bb
	/* initialize byte_buffer */
dd89bb
	byte_buffer = (unsigned char *)wch_arg;
dd89bb
dd89bb
	/* iterate */
dd89bb
	while ((uintptr_t)byte_buffer < (uintptr_t)wch_termination) {
dd89bb
		/* two bytes at a time */
dd89bb
		crc32 = (crc32 >> 8) ^ crc32_table[(crc32 ^ *byte_buffer) & 0xFF];
dd89bb
		byte_buffer++;
dd89bb
		crc32 = (crc32 >> 8) ^ crc32_table[(crc32 ^ *byte_buffer) & 0xFF];
dd89bb
		byte_buffer++;
dd89bb
	}
dd89bb
dd89bb
	return crc32;
dd89bb
}
dd89bb
dd89bb
dd89bb
static void __fastcall __init_cmd_option_meta_utf16(
dd89bb
	__in	nt_cmd_option_meta_utf16 *	cmd_opt_meta)
dd89bb
{
dd89bb
	cmd_opt_meta->short_name	= (wchar16_t *)0;
dd89bb
	cmd_opt_meta->short_name_code	= 0;
dd89bb
	cmd_opt_meta->long_name		= (wchar16_t *)0;
dd89bb
	cmd_opt_meta->long_name_hash	= 0;
dd89bb
	cmd_opt_meta->value		= (wchar16_t *)0;
dd89bb
	cmd_opt_meta->value_hash	= 0;
dd89bb
	cmd_opt_meta->argv_index	= 0;
dd89bb
	cmd_opt_meta->flags		= 0;
dd89bb
dd89bb
	return;
dd89bb
}
dd89bb
dd89bb
dd89bb
int32_t __stdcall __ntapi_tt_get_short_option_meta_utf16(
dd89bb
	__in	const uint32_t *		crc32_table,
dd89bb
	__in	wchar16_t			option_name,
dd89bb
	__in	wchar16_t *			argv[],
dd89bb
	__out	nt_cmd_option_meta_utf16 *	cmd_opt_meta)
dd89bb
{
dd89bb
	int		idx;
dd89bb
	wchar16_t *	wch;
dd89bb
dd89bb
	if (!crc32_table)
dd89bb
		return NT_STATUS_INVALID_PARAMETER_1;
dd89bb
	else if (!option_name)
dd89bb
		return NT_STATUS_INVALID_PARAMETER_2;
dd89bb
	else if (!argv)
dd89bb
		return NT_STATUS_INVALID_PARAMETER_3;
dd89bb
dd89bb
	/* initialize cmd_opt_meta  */
dd89bb
	__init_cmd_option_meta_utf16(cmd_opt_meta);
dd89bb
dd89bb
	/* step 1: attempt to find the short option in argv[] */
dd89bb
	idx = 0;
dd89bb
	while (argv[idx] && (!cmd_opt_meta->short_name_code)) {
dd89bb
		wch = argv[idx];
dd89bb
dd89bb
		/* is this our option? */
dd89bb
		if ((*wch == HYPHEN)
dd89bb
			&& (*++wch == option_name)
dd89bb
			&& (*++wch == 0)) {
dd89bb
dd89bb
			/* found it, get ready to hash the value */
dd89bb
			cmd_opt_meta->short_name_code = option_name;
dd89bb
			cmd_opt_meta->short_name = argv[idx];
dd89bb
			cmd_opt_meta->argv_index = idx;
dd89bb
		} else {
dd89bb
			idx++;
dd89bb
		}
dd89bb
	}
dd89bb
dd89bb
	/* if the next argument is also an option (or is null), just exit */
dd89bb
	idx++;
dd89bb
	if ((!argv[idx]) || (*argv[idx] == HYPHEN))
dd89bb
		return NT_STATUS_SUCCESS;
dd89bb
dd89bb
	/* step 2: hash the value */
dd89bb
	cmd_opt_meta->value = argv[idx];
dd89bb
	cmd_opt_meta->value_hash =
dd89bb
		__compute_crc32_utf16_str(
dd89bb
			crc32_table,
dd89bb
			argv[idx]);
dd89bb
dd89bb
	return NT_STATUS_SUCCESS;
dd89bb
}
dd89bb
dd89bb
dd89bb
int32_t __stdcall __ntapi_tt_get_long_option_meta_utf16(
dd89bb
	__in	const uint32_t *		crc32_table,
dd89bb
	__in	wchar16_t *			option_name,
dd89bb
	__in	uint32_t			option_name_hash __optional,
dd89bb
	__in	wchar16_t *			argv[],
dd89bb
	__out	nt_cmd_option_meta_utf16 *	cmd_opt_meta)
dd89bb
{
dd89bb
	/**
dd89bb
	 *  option_name must always include the two-hyphen prefix;
dd89bb
	 *  and the option value must be preceded by an equal sign.
dd89bb
	 *
dd89bb
	 *  the only valid long option forms in argv[] are therefore:
dd89bb
	 *  --long-option
dd89bb
	 *  --long-option=value
dd89bb
	**/
dd89bb
dd89bb
	int		idx;
dd89bb
	uint32_t	crc32;
dd89bb
	wchar16_t *	wch;
dd89bb
dd89bb
	/* validation */
dd89bb
	if (!crc32_table)
dd89bb
		return NT_STATUS_INVALID_PARAMETER_1;
dd89bb
	else if ((!option_name) && (!option_name_hash))
dd89bb
		return NT_STATUS_INVALID_PARAMETER;
dd89bb
	else if ((option_name) && (option_name_hash))
dd89bb
		return NT_STATUS_INVALID_PARAMETER_MIX;
dd89bb
	else if (!argv)
dd89bb
		return NT_STATUS_INVALID_PARAMETER_4;
dd89bb
dd89bb
	/* initialize cmd_opt_meta  */
dd89bb
	__init_cmd_option_meta_utf16(cmd_opt_meta);
dd89bb
dd89bb
	/* step 1: crc32 of the target option_name */
dd89bb
	if (option_name_hash)
dd89bb
		crc32 = option_name_hash;
dd89bb
	else
dd89bb
		option_name_hash =
dd89bb
			__compute_crc32_utf16_str(
dd89bb
				crc32_table,
dd89bb
				option_name);
dd89bb
dd89bb
	/* step 2: attempt to find the long option in argv[] */
dd89bb
	idx = 0;
dd89bb
	while (argv[idx] && (!cmd_opt_meta->value))  {
dd89bb
		wch = argv[idx];
dd89bb
dd89bb
		if (__is_long_option(wch)) {
dd89bb
			/* find the equal sign or null termination */
dd89bb
			while ((*wch) && (*wch != EQUAL_SIGN))
dd89bb
				wch++;
dd89bb
dd89bb
			crc32 = __compute_crc32_long_option_name(
dd89bb
				crc32_table,
dd89bb
				argv[idx],
dd89bb
				wch);
dd89bb
dd89bb
			if (crc32 == option_name_hash) {
dd89bb
				/* found it, get ready to hash the value */
dd89bb
				cmd_opt_meta->long_name_hash = option_name_hash;
dd89bb
				cmd_opt_meta->long_name	 = argv[idx];
dd89bb
				cmd_opt_meta->argv_index = idx;
dd89bb
dd89bb
				if (*wch)
dd89bb
					/* skip the equal sign */
dd89bb
					wch++;
dd89bb
dd89bb
				cmd_opt_meta->value = wch;
dd89bb
			} else
dd89bb
				idx++;
dd89bb
		}
dd89bb
	}
dd89bb
dd89bb
	if (cmd_opt_meta->value)
dd89bb
		cmd_opt_meta->value_hash =
dd89bb
			__compute_crc32_utf16_str(
dd89bb
				crc32_table,
dd89bb
				cmd_opt_meta->value);
dd89bb
dd89bb
	return NT_STATUS_SUCCESS;
dd89bb
}
dd89bb
dd89bb
dd89bb
int32_t __stdcall __ntapi_tt_validate_program_options(
dd89bb
	__in	const uint32_t *		crc32_table,
dd89bb
	__in	wchar16_t *			argv[],
dd89bb
	__in	nt_program_option *		options[],
dd89bb
	__in	nt_program_options_meta *	options_meta)
dd89bb
{
dd89bb
	int				idx;
dd89bb
	int				idx_arg;
dd89bb
	int				idx_option;
dd89bb
	int				idx_max;
dd89bb
	uint32_t			crc32;
dd89bb
	nt_program_option *		option;
dd89bb
	wchar16_t *			parg;
dd89bb
	wchar16_t *			pvalue;
dd89bb
dd89bb
	/* validation */
dd89bb
	if (!crc32_table)
dd89bb
		return NT_STATUS_INVALID_PARAMETER_1;
dd89bb
	else if (!argv)
dd89bb
		return NT_STATUS_INVALID_PARAMETER_2;
dd89bb
	else if (!options)
dd89bb
		return NT_STATUS_INVALID_PARAMETER_3;
dd89bb
	else if (!options_meta)
dd89bb
		return NT_STATUS_INVALID_PARAMETER_4;
dd89bb
dd89bb
dd89bb
	/* step 1: validate options[] hash the long option names */
dd89bb
	idx		= 0;
dd89bb
	idx_option	= 0;
dd89bb
	option		= options[0];
dd89bb
	pvalue  	= (wchar16_t *)0;
dd89bb
dd89bb
	while (!__is_last_program_option(option)) {
dd89bb
		if (option->short_name_code) {
dd89bb
			if (!(__is_bmp_code_point(option->short_name_code))) {
dd89bb
				options_meta->idx_invalid_short_name = idx;
dd89bb
				return NT_STATUS_INVALID_PARAMETER;
dd89bb
			}
dd89bb
		}
dd89bb
dd89bb
		if (option->long_name) {
dd89bb
			if (!(__is_long_option(option->long_name))) {
dd89bb
				options_meta->idx_invalid_long_name = idx;
dd89bb
				return NT_STATUS_INVALID_PARAMETER;
dd89bb
			}
dd89bb
dd89bb
			/* update the long name hash (unconditionally) */
dd89bb
			option->long_name_hash =
dd89bb
				__compute_crc32_utf16_str(
dd89bb
					crc32_table,
dd89bb
					option->long_name);
dd89bb
		}
dd89bb
dd89bb
		idx++;
dd89bb
		option++;
dd89bb
	}
dd89bb
dd89bb
	/* book keeping */
dd89bb
	idx_max = idx;
dd89bb
dd89bb
	/* step 2: validate argv[] */
dd89bb
	parg	= argv[0];
dd89bb
	idx_arg	= 0;
dd89bb
dd89bb
	while ((parg) && (!(__is_last_option_argument(parg)))) {
dd89bb
		if (__is_short_option(parg)) {
dd89bb
			idx = 0;
dd89bb
			idx_option = 0;
dd89bb
dd89bb
			while ((idx < idx_max) && (!idx_option)) {
dd89bb
				option = options[idx];
dd89bb
dd89bb
				if (*(parg+1) == option->short_name_code)
dd89bb
					idx_option = idx;
dd89bb
				else
dd89bb
					idx++;
dd89bb
			}
dd89bb
dd89bb
			if (idx == idx_max) {
dd89bb
				options_meta->idx_invalid_argument = idx_arg;
dd89bb
				return NT_STATUS_INVALID_PARAMETER;
dd89bb
			} else {
dd89bb
				/* get ready for the next element (or value) */
dd89bb
				parg++;
dd89bb
				idx_arg++;
dd89bb
				pvalue = parg;
dd89bb
			}
dd89bb
		} else if (__is_long_option(parg)) {
dd89bb
			idx = 0;
dd89bb
			idx_option = 0;
dd89bb
			/* find the equal sign or null termination */
dd89bb
			pvalue = parg;
dd89bb
			while ((*pvalue) && (*pvalue != EQUAL_SIGN))
dd89bb
				pvalue++;
dd89bb
dd89bb
			while ((idx < idx_max) && (!idx_option)) {
dd89bb
				option = options[idx];
dd89bb
				crc32  = __compute_crc32_long_option_name(
dd89bb
						crc32_table,
dd89bb
						parg,
dd89bb
						pvalue);
dd89bb
dd89bb
				if (crc32 == option->long_name_hash)
dd89bb
					idx_option = idx;
dd89bb
				else
dd89bb
					idx++;
dd89bb
			}
dd89bb
dd89bb
			if (idx == idx_max) {
dd89bb
				options_meta->idx_invalid_argument = idx_arg;
dd89bb
				return NT_STATUS_INVALID_PARAMETER;
dd89bb
			} else {
dd89bb
				if (*pvalue != EQUAL_SIGN)
dd89bb
					/* skip the equal sign */
dd89bb
					pvalue++;
dd89bb
				pvalue = (wchar16_t *)0;
dd89bb
			}
dd89bb
		}
dd89bb
dd89bb
		/* validate the occurrence */
dd89bb
		if (idx_option) {
188aac
			if (option->flags & NT_OPTION_ALLOWED_ONCE) {
dd89bb
				if (option->option_count) {
dd89bb
					options_meta->idx_invalid_argument
dd89bb
						= idx_arg;
dd89bb
					return NT_STATUS_INVALID_PARAMETER;
dd89bb
				} else {
dd89bb
					option->option_count++;
dd89bb
				}
dd89bb
			}
dd89bb
188aac
			if (option->flags & NT_OPTION_VALUE_REQUIRED) {
dd89bb
				if ((!(*pvalue)) || (*pvalue == HYPHEN)) {
dd89bb
					options_meta->idx_missing_option_value
dd89bb
						= idx_arg;
dd89bb
					return NT_STATUS_INVALID_PARAMETER;
dd89bb
				} else {
dd89bb
					option->value 	   = pvalue;
dd89bb
					option->value_hash =
dd89bb
						__compute_crc32_utf16_str(
dd89bb
							crc32_table,
dd89bb
							option->value);
dd89bb
				}
dd89bb
			}
dd89bb
		}
dd89bb
dd89bb
		parg++;
dd89bb
		idx_arg++;
dd89bb
	}
dd89bb
dd89bb
	return NT_STATUS_SUCCESS;
dd89bb
}