/*
 * Check decoding of prctl PR_SET_TAGGED_ADDR_CTRL and PR_GET_TAGGED_ADDR_CTRL
 * operations.
 *
 * Copyright (c) 2021 The strace developers.
 * All rights reserved.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "tests.h"
#include "scno.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/prctl.h>

#if !XLAT_RAW
static void
print_tagged_addr_arg(kernel_ulong_t arg)
{
	kernel_ulong_t val = arg & PR_TAGGED_ADDR_ENABLE;

	printf("%sPR_TAGGED_ADDR_ENABLE", val ? "" : "!");
	arg &= ~val;
	val = arg & PR_MTE_TCF_MASK;
	switch (val) {
	case PR_MTE_TCF_NONE:  printf("|PR_MTE_TCF_NONE");  break;
	case PR_MTE_TCF_SYNC:  printf("|PR_MTE_TCF_SYNC");  break;
	case PR_MTE_TCF_ASYNC: printf("|PR_MTE_TCF_ASYNC"); break;
	case PR_MTE_TCF_MASK:  printf("|PR_MTE_TCF_MASK");  break;
	}
	arg &= ~val;
	val = arg & PR_MTE_TAG_MASK;
	if (val) {
		printf("|%#llx<<PR_MTE_TAG_SHIFT",
		       (unsigned long long) val >> PR_MTE_TAG_SHIFT);
	}
	arg &= ~val;
	if (arg)
		printf("|%#llx", (unsigned long long) arg);
}
#endif /* !XLAT_RAW */

#ifdef INJECT_RETVAL
# define INJ_STR " (INJECTED)"
#else
# define INJ_STR ""
#endif

#if SIZEOF_KERNEL_LONG_T == 8
# define HIBITS(a) a
#else
# define HIBITS(a)
#endif

int
main(int argc, char *argv[])
{
	prctl_marker();

#ifdef INJECT_RETVAL
	unsigned long num_skip;
	long inject_retval;
	bool locked = false;

	if (argc < 3)
		error_msg_and_fail("Usage: %s NUM_SKIP INJECT_RETVAL", argv[0]);

	num_skip = strtoul(argv[1], NULL, 0);
	inject_retval = strtol(argv[2], NULL, 0);

	for (size_t i = 0; i < num_skip; i++) {
		if (prctl_marker() != inject_retval)
			continue;

		locked = true;
		break;
	}

	if (!locked)
		error_msg_and_fail("Have not locked on prctl(-1, -2, -3, -4"
				   ", -5) returning %ld", inject_retval);
#endif /* INJECT_RETVAL */

	static const struct {
		kernel_ulong_t val;
		const char *str;
	} args[] = {
		{ 0, "!PR_TAGGED_ADDR_ENABLE|PR_MTE_TCF_NONE" },
		{ /* 1 */ ARG_STR(PR_TAGGED_ADDR_ENABLE|PR_MTE_TCF_NONE) },
		{ 2, "!PR_TAGGED_ADDR_ENABLE|PR_MTE_TCF_SYNC" },
		{ /* 5 */ ARG_STR(PR_TAGGED_ADDR_ENABLE|PR_MTE_TCF_ASYNC) },
		{ 6, "!PR_TAGGED_ADDR_ENABLE|PR_MTE_TCF_MASK" },
		{ 8, "!PR_TAGGED_ADDR_ENABLE|PR_MTE_TCF_NONE|0x1<<PR_MTE_TAG_SHIFT" },
		{ /* 13 */ ARG_STR(PR_TAGGED_ADDR_ENABLE|PR_MTE_TCF_ASYNC|0x1<<PR_MTE_TAG_SHIFT) },
		{ 0x57ae57f4, "!PR_TAGGED_ADDR_ENABLE|PR_MTE_TCF_ASYNC"
			      "|0xcafe<<PR_MTE_TAG_SHIFT|0x57a80000" },
		{ (kernel_ulong_t) -1LLU, "PR_TAGGED_ADDR_ENABLE"
					  "|PR_MTE_TCF_MASK"
					  "|0xffff<<PR_MTE_TAG_SHIFT"
					  "|0x" HIBITS("ffffffff") "fff80000" },
	};
	long rc;
	size_t i = 0;

	for (i = 0; i < ARRAY_SIZE(args); i++) {
		rc = syscall(__NR_prctl, PR_SET_TAGGED_ADDR_CTRL,
			     args[i].val, 1, 2, 3);
		printf("prctl(" XLAT_KNOWN(0x37, "PR_SET_TAGGED_ADDR_CTRL") ", "
		       XLAT_FMT_LL ", 0x1, 0x2, 0x3) = %s" INJ_STR "\n",
		       XLAT_SEL((unsigned long long) args[i].val, args[i].str),
		       sprintrc(rc));
	}

	rc = syscall(__NR_prctl, PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
	const char *errstr = sprintrc(rc);
	printf("prctl(" XLAT_KNOWN(0x38, "PR_GET_TAGGED_ADDR_CTRL")
	       ", 0, 0, 0, 0) = ");
	if (rc >= 0) {
		printf("%#lx", rc);
#if !XLAT_RAW
		for (i = 0; i < ARRAY_SIZE(args); i++) {
			if (args[i].val == (unsigned long) rc) {
				printf(" (%s)", args[i].str);
				break;
			}
		}

		if (i == ARRAY_SIZE(args)) {
			printf(" (");
			print_tagged_addr_arg(rc);
			printf(")");
		}
#endif /* !XLAT_RAW */
		puts(INJ_STR);
	} else {
		printf("%s" INJ_STR "\n", errstr);
	}

	puts("+++ exited with 0 +++");
	return 0;
}