/*
 * Copyright (c) 2016-2017 Dmitry V. Levin <ldv@strace.io>
 * Copyright (c) 2017-2023 The strace developers.
 * All rights reserved.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#ifndef STRACE_PRINT_FIELDS_H
# define STRACE_PRINT_FIELDS_H

# include "static_assert.h"

# ifdef IN_STRACE

#  define STRACE_PRINTS(s_) tprints_string(s_)

/*
 * The printf-like function to use in header files
 * shared between strace and its tests.
 */
#  define STRACE_PRINTF tprintf_string

# else /* !IN_STRACE */

#  include <stdio.h>

#  define STRACE_PRINTS(s_) fputs((s_), stdout)

/*
 * The printf-like function to use in header files
 * shared between strace and its tests.
 */
#  define STRACE_PRINTF printf

# endif /* !IN_STRACE */


static inline void
tprint_struct_begin(void)
{
	STRACE_PRINTS("{");
}

static inline void
tprint_struct_next(void)
{
	STRACE_PRINTS(", ");
}

static inline void
tprint_struct_end(void)
{
	STRACE_PRINTS("}");
}

static inline void
tprint_union_begin(void)
{
	STRACE_PRINTS("{");
}

static inline void
tprint_union_next(void)
{
	STRACE_PRINTS(", ");
}

static inline void
tprint_union_end(void)
{
	STRACE_PRINTS("}");
}

static inline void
tprint_array_begin(void)
{
	STRACE_PRINTS("[");
}

static inline void
tprint_array_next(void)
{
	STRACE_PRINTS(", ");
}

static inline void
tprint_array_end(void)
{
	STRACE_PRINTS("]");
}

static inline void
tprint_array_index_begin(void)
{
	STRACE_PRINTS("[");
}

static inline void
tprint_array_index_equal(void)
{
	STRACE_PRINTS("]=");
}

static inline void
tprint_array_index_end(void)
{
}

static inline void
tprint_arg_next(void)
{
	STRACE_PRINTS(", ");
}

static inline void
tprint_arg_end(void)
{
	STRACE_PRINTS(")");
}

static inline void
tprint_bitset_begin(void)
{
	STRACE_PRINTS("[");
}

static inline void
tprint_bitset_next(void)
{
	STRACE_PRINTS(" ");
}

static inline void
tprint_bitset_end(void)
{
	STRACE_PRINTS("]");
}

static inline void
tprint_comment_begin(void)
{
	STRACE_PRINTS(" /* ");
}

static inline void
tprint_comment_end(void)
{
	STRACE_PRINTS(" */");
}

static inline void
tprint_indirect_begin(void)
{
	STRACE_PRINTS("[");
}

static inline void
tprint_indirect_end(void)
{
	STRACE_PRINTS("]");
}

static inline void
tprint_attribute_begin(void)
{
	STRACE_PRINTS("[");
}

static inline void
tprint_attribute_end(void)
{
	STRACE_PRINTS("]");
}

static inline void
tprint_associated_info_begin(void)
{
	STRACE_PRINTS("<");
}

static inline void
tprint_associated_info_end(void)
{
	STRACE_PRINTS(">");
}

static inline void
tprint_more_data_follows(void)
{
	STRACE_PRINTS("...");
}

static inline void
tprint_value_changed(void)
{
	STRACE_PRINTS(" => ");
}

static inline void
tprint_alternative_value(void)
{
	STRACE_PRINTS(" or ");
}

static inline void
tprint_unavailable(void)
{
	STRACE_PRINTS("???");
}

static inline void
tprint_shift_begin(void)
{
}

static inline void
tprint_shift_end(void)
{
}

static inline void
tprint_shift(void)
{
	STRACE_PRINTS("<<");
}

static inline void
tprint_flags_begin(void)
{
}

static inline void
tprint_flags_or(void)
{
	STRACE_PRINTS("|");
}

static inline void
tprint_flags_end(void)
{
}

static inline void
tprint_plus(void)
{
	STRACE_PRINTS("+");
}

static inline void
tprint_space(void)
{
	STRACE_PRINTS(" ");
}

static inline void
tprint_null(void)
{
	STRACE_PRINTS("NULL");
}

static inline void
tprint_newline(void)
{
	STRACE_PRINTS("\n");
}

static inline void
tprints_field_name(const char *name)
{
	STRACE_PRINTF("%s=", name);
}

static inline void
tprints_arg_name_begin(const char *name)
{
	STRACE_PRINTF("%s=", name);
}

static inline void
tprint_arg_name_end(void)
{
}

static inline void
tprints_arg_begin(const char *name)
{
	STRACE_PRINTF("%s(", name);
}

static inline void
tprint_sysret_begin(void)
{
	STRACE_PRINTS("=");
}

static inline void
tprints_sysret_next(const char *name)
{
	tprint_space();
}

static inline void
tprints_sysret_string(const char *name, const char *str)
{
	tprints_sysret_next(name);
	STRACE_PRINTF("(%s)", str);
}

static inline void
tprint_sysret_pseudo_rval(void)
{
	STRACE_PRINTS("?");
}

static inline void
tprint_sysret_end(void)
{
}

# define PRINT_VAL_D(val_)	\
	STRACE_PRINTF("%lld", sign_extend_unsigned_to_ll(val_))

# define PRINT_VAL_U(val_)	\
	STRACE_PRINTF("%llu", zero_extend_signed_to_ull(val_))

# define PRINT_VAL_X(val_)	\
	STRACE_PRINTF("%#llx", zero_extend_signed_to_ull(val_))

# define PRINT_VAL_03O(val_)	\
	STRACE_PRINTF("%#03llo", zero_extend_signed_to_ull(val_))

# define PRINT_VAL_0X(val_)						\
	STRACE_PRINTF("%#0*llx", (int) sizeof(val_) * 2,		\
		      zero_extend_signed_to_ull(val_))

# define PRINT_VAL_ID(val_)						\
	do {								\
		if (sign_extend_unsigned_to_ll(val_) == -1LL)		\
			PRINT_VAL_D(-1);				\
		else							\
			PRINT_VAL_U(val_);				\
	} while (0)

# define PRINT_FIELD_D(where_, field_)					\
	do {								\
		tprints_field_name(#field_);				\
		PRINT_VAL_D((where_).field_);				\
	} while (0)

# define PRINT_FIELD_U(where_, field_)					\
	do {								\
		tprints_field_name(#field_);				\
		PRINT_VAL_U((where_).field_);				\
	} while (0)

# define PRINT_FIELD_U_CAST(where_, field_, type_)			\
	do {								\
		tprints_field_name(#field_);				\
		PRINT_VAL_U((type_)((where_).field_));			\
	} while (0)

# define PRINT_FIELD_X(where_, field_)					\
	do {								\
		tprints_field_name(#field_);				\
		PRINT_VAL_X((where_).field_);				\
	} while (0)

# define PRINT_FIELD_X_CAST(where_, field_, type_)			\
	do {								\
		tprints_field_name(#field_);				\
		PRINT_VAL_X((type_)((where_).field_));			\
	} while (0)

# define PRINT_FIELD_ADDR64(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
		printaddr64((where_).field_);				\
	} while (0)

# define PRINT_FIELD_0X(where_, field_)					\
	do {								\
		tprints_field_name(#field_);				\
		PRINT_VAL_0X((where_).field_);				\
	} while (0)

# define PRINT_FIELD_VAL_ARRAY(where_, field_, print_val_)		\
	do {								\
		tprints_field_name(#field_);				\
		for (size_t i_ = 0;					\
		     i_ < ARRAY_SIZE((where_).field_);			\
		     ++i_) {						\
			if (i_)						\
				tprint_array_next();			\
			else						\
				tprint_array_begin();			\
			print_val_((where_).field_[i_]);		\
		}							\
		tprint_array_end();					\
	} while (0)

# define PRINT_FIELD_D_ARRAY(where_, field_)				\
	PRINT_FIELD_VAL_ARRAY((where_), field_, PRINT_VAL_D)

# define PRINT_FIELD_U_ARRAY(where_, field_)				\
	PRINT_FIELD_VAL_ARRAY((where_), field_, PRINT_VAL_U)

# define PRINT_FIELD_X_ARRAY(where_, field_)				\
	PRINT_FIELD_VAL_ARRAY((where_), field_, PRINT_VAL_X)

# define PRINT_FIELD_VAL_ARRAY2D(where_, field_, print_val_)		\
	do {								\
		tprints_field_name(#field_);				\
		for (size_t i_ = 0;					\
		     i_ < ARRAY_SIZE((where_).field_);			\
		     ++i_) {						\
			if (i_)						\
				tprint_array_next();			\
			else						\
				tprint_array_begin();			\
			for (size_t j_ = 0;				\
			     j_ < ARRAY_SIZE((where_).field_[i_]);	\
			     ++j_) {					\
				if (j_)					\
					tprint_array_next();		\
				else					\
					tprint_array_begin();		\
				print_val_((where_).field_[i_][j_]);	\
			}						\
			tprint_array_end();				\
		}							\
		tprint_array_end();					\
	} while (0)

# define PRINT_FIELD_X_ARRAY2D(where_, field_)				\
	PRINT_FIELD_VAL_ARRAY2D((where_), field_, PRINT_VAL_X)

# define PRINT_FIELD_COOKIE(where_, field_)				\
	do {								\
		static_assert(ARRAY_SIZE((where_).field_) == 2,		\
			      "unexpected array size");			\
		PRINT_FIELD_U_ARRAY((where_), field_);			\
	} while (0)

# define PRINT_FIELD_FLAGS(where_, field_, xlat_, dflt_)		\
	do {								\
		tprints_field_name(#field_);				\
		printflags64((xlat_),					\
			     zero_extend_signed_to_ull((where_).field_),\
			     (dflt_));					\
	} while (0)

# define PRINT_FIELD_FLAGS_VERBOSE(where_, field_, xlat_, dflt_)	\
	do {								\
		tprints_field_name(#field_);				\
		tprint_flags_begin();					\
		printflags_ex(zero_extend_signed_to_ull((where_).field_), \
			      (dflt_),					\
			      xlat_verbose(xlat_verbosity) == XLAT_STYLE_RAW \
				? XLAT_STYLE_RAW : XLAT_STYLE_VERBOSE,	\
			      (xlat_), NULL);				\
		tprint_flags_end();					\
	} while (0)

# define PRINT_FIELD_XVAL(where_, field_, xlat_, dflt_)			\
	do {								\
		tprints_field_name(#field_);				\
		printxval64((xlat_),					\
			    zero_extend_signed_to_ull((where_).field_),	\
			    (dflt_));					\
	} while (0)

# define PRINT_FIELD_XVAL_VERBOSE(where_, field_, xlat_, dflt_)		\
	do {								\
		tprints_field_name(#field_);				\
		printxval_ex((xlat_),					\
			     zero_extend_signed_to_ull((where_).field_), \
			     (dflt_),					\
			     xlat_verbose(xlat_verbosity) == XLAT_STYLE_RAW \
				? XLAT_STYLE_RAW : XLAT_STYLE_VERBOSE);	\
	} while (0)

# define PRINT_FIELD_XVAL_D(where_, field_, xlat_, dflt_)		\
	do {								\
		tprints_field_name(#field_);				\
		printxval64_d((xlat_),					\
			      sign_extend_unsigned_to_ll((where_).field_), \
			      (dflt_));					\
	} while (0)

# define PRINT_FIELD_XVAL_U(where_, field_, xlat_, dflt_)		\
	do {								\
		tprints_field_name(#field_);				\
		printxval64_u((xlat_),					\
			      zero_extend_signed_to_ull((where_).field_), \
			      (dflt_));					\
	} while (0)

# define PRINT_FIELD_XVAL_U_VERBOSE(where_, field_, xlat_, dflt_)	\
	do {								\
		tprints_field_name(#field_);				\
		printxval_ex((xlat_),					\
			     zero_extend_signed_to_ull((where_).field_), \
			     (dflt_),					\
			     (xlat_verbose(xlat_verbosity) == XLAT_STYLE_RAW \
				? XLAT_STYLE_RAW : XLAT_STYLE_VERBOSE)	\
			     | XLAT_STYLE_FMT_U);			\
	} while (0)

# define PRINT_FIELD_ERR_D(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
		print_err(sign_extend_unsigned_to_ll((where_).field_),	\
			  true);					\
	} while (0)

# define PRINT_FIELD_ERR_U(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
		print_err(zero_extend_signed_to_ull((where_).field_),	\
			  false);					\
	} while (0)

/*
 * Generic "ID" printing. ID is considered unsigned except for the special value
 * of -1.
 */
# define PRINT_FIELD_ID(where_, field_)					\
	do {								\
		tprints_field_name(#field_);				\
		PRINT_VAL_ID((where_).field_);				\
	} while (0)

# define PRINT_FIELD_UUID(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
		print_uuid((const unsigned char *) ((where_).field_));	\
	} while (0)

# define PRINT_FIELD_U64(where_, field_)					\
	do {									\
		tprints_field_name(#field_);					\
		if (zero_extend_signed_to_ull((where_).field_) == UINT64_MAX)	\
			print_xlat_u(UINT64_MAX);				\
		else								\
			PRINT_VAL_U((where_).field_);				\
	} while (0)

# define PRINT_FIELD_TICKS(where_, field_, freq_, precision_)		\
	do {								\
		tprints_field_name(#field_);				\
		print_ticks((where_).field_, (freq_), (precision_));	\
	} while (0)

# define PRINT_FIELD_TICKS_D(where_, field_, freq_, precision_)		\
	do {								\
		tprints_field_name(#field_);				\
		print_ticks_d((where_).field_, (freq_), (precision_));	\
	} while (0)

# define PRINT_FIELD_CLOCK_T(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
		print_clock_t((where_).field_);				\
	} while (0)

# define PRINT_FIELD_STRING(where_, field_, len_, style_)		\
	do {								\
		tprints_field_name(#field_);				\
		print_quoted_string((const char *)(where_).field_,	\
				    (len_), (style_));			\
	} while (0)

# define PRINT_FIELD_CSTRING(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
		print_quoted_cstring((const char *) (where_).field_,	\
				     sizeof((where_).field_) +		\
				     MUST_BE_ARRAY((where_).field_));	\
	} while (0)

# define PRINT_FIELD_CSTRING_SZ(where_, field_, size_)			\
	do {								\
		tprints_field_name(#field_);				\
		print_quoted_cstring((const char *) (where_).field_,	\
				     (size_));				\
	} while (0)

# define PRINT_FIELD_ARRAY(where_, field_, tcp_, print_func_)		\
	do {								\
		tprints_field_name(#field_);				\
		print_local_array((tcp_), (where_).field_,		\
				  (print_func_));			\
	} while (0)

# define PRINT_FIELD_ARRAY_INDEXED(where_, field_, tcp_, print_func_,	\
				   ind_xlat_, ind_dflt_)		\
	do {								\
		tprints_field_name(#field_);				\
		print_local_array_ex((tcp_), (where_).field_,		\
				  ARRAY_SIZE((where_).field_),		\
				  sizeof(((where_).field_)[0]),		\
				  (print_func_),			\
				  NULL, PAF_PRINT_INDICES | XLAT_STYLE_FMT_U, \
				  (ind_xlat_), (ind_dflt_));		\
	} while (0)

# define PRINT_FIELD_ARRAY_UPTO(where_, field_,				\
				upto_, tcp_, print_func_)		\
	do {								\
		tprints_field_name(#field_);				\
		print_local_array_upto((tcp_), (where_).field_,		\
				       (upto_), (print_func_));		\
	} while (0)

# define PRINT_FIELD_HEX_ARRAY(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
		print_quoted_string((const char *)(where_).field_,	\
				    sizeof((where_).field_) +		\
					    MUST_BE_ARRAY((where_).field_), \
				    QUOTE_FORCE_HEX); \
	} while (0)

# define PRINT_FIELD_HEX_ARRAY_UPTO(where_, field_, upto_)		\
	do {								\
		tprints_field_name(#field_);				\
		print_quoted_string((const char *)(where_).field_,	\
				    (upto_), QUOTE_FORCE_HEX);		\
	} while (0)

# define PRINT_FIELD_INET_ADDR(where_, field_, af_)			\
	print_inet_addr((af_), &(where_).field_,			\
			sizeof((where_).field_), #field_)

# define PRINT_FIELD_NET_PORT(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
									\
		if (xlat_verbose(xlat_verbosity) != XLAT_STYLE_ABBREV)	\
			print_quoted_string((const char *)		\
					&(where_).field_,		\
					sizeof((where_).field_),	\
					QUOTE_FORCE_HEX);		\
									\
		if (xlat_verbose(xlat_verbosity) == XLAT_STYLE_RAW)	\
			break;						\
									\
		if (xlat_verbose(xlat_verbosity)			\
				== XLAT_STYLE_VERBOSE)			\
			tprint_comment_begin();				\
									\
		tprints_arg_begin("htons");				\
		unsigned short us_ = ntohs((where_).field_);		\
		PRINT_VAL_U(us_);					\
		tprint_arg_end();					\
									\
		if (xlat_verbose(xlat_verbosity)			\
				== XLAT_STYLE_VERBOSE)			\
			tprint_comment_end();				\
	} while (0)

# define PRINT_FIELD_IFINDEX(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
		print_ifindex((where_).field_);				\
	} while (0)

# define PRINT_FIELD_SOCKADDR(where_, field_, tcp_)			\
	do {								\
		tprints_field_name(#field_);				\
		print_sockaddr(tcp_, &(where_).field_,			\
			       sizeof((where_).field_));		\
	} while (0)

# define PRINT_FIELD_DEV(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
		print_dev_t((where_).field_);				\
	} while (0)

# define PRINT_FIELD_PTR(where_, field_)				\
	do {								\
		tprints_field_name(#field_);				\
		printaddr((mpers_ptr_t) (where_).field_);		\
	} while (0)

# define PRINT_FIELD_FD(where_, field_, tcp_)				\
	do {								\
		tprints_field_name(#field_);				\
		printfd((tcp_), (where_).field_);			\
	} while (0)

# define PRINT_FIELD_DIRFD(where_, field_, tcp_)			\
	do {								\
		tprints_field_name(#field_);				\
		print_dirfd((tcp_), (where_).field_);			\
	} while (0)

# define PRINT_FIELD_CHAR(where_, field_, flags_)		\
	do {								\
		tprints_field_name(#field_);				\
		print_char((where_).field_, (flags_));			\
	} while (0)

# define PRINT_FIELD_TGID(where_, field_, tcp_)				\
	do {								\
		tprints_field_name(#field_);				\
		printpid((tcp_), (where_).field_, PT_TGID);		\
	} while (0)

# define PRINT_FIELD_SYSCALL_NAME(where_, field_, audit_arch_)		\
	do {								\
		tprints_field_name(#field_);				\
		const char *nr_prefix_ = NULL;				\
		const char *name = syscall_name_arch((where_).field_,	\
					(audit_arch_), &nr_prefix_);	\
		if (xlat_verbose(xlat_verbosity) != XLAT_STYLE_ABBREV	\
		    || !nr_prefix_)					\
			PRINT_VAL_U((where_).field_);			\
		if (xlat_verbose(xlat_verbosity) == XLAT_STYLE_RAW	\
		    || !name)						\
			break;						\
		if (!nr_prefix_ ||					\
		    xlat_verbose(xlat_verbosity) == XLAT_STYLE_VERBOSE)	\
			tprint_comment_begin();				\
		if (nr_prefix_)						\
			tprints_string(nr_prefix_);			\
		tprints_string(name);					\
		if (!nr_prefix_ ||					\
		    xlat_verbose(xlat_verbosity) == XLAT_STYLE_VERBOSE)	\
			tprint_comment_end();				\
	} while (0)

# define PRINT_FIELD_MAC(where_, field_)				\
	PRINT_FIELD_MAC_SZ((where_), field_, ARRAY_SIZE((where_).field_))

# define PRINT_FIELD_MAC_SZ(where_, field_, size_)			\
	do {								\
		static_assert(sizeof(((where_).field_)[0]) == 1,	\
			      "MAC address is not a byte array");	\
		tprints_field_name(#field_);				\
		print_mac_addr("", (const uint8_t *) ((where_).field_),	\
			MIN((size_), ARRAY_SIZE((where_).field_)));	\
	} while (0)

# define PRINT_FIELD_HWADDR_SZ(where_, field_, size_, hwtype_)		\
	do {								\
		static_assert(sizeof(((where_).field_)[0]) == 1,	\
			      "hwaddress is not a byte array");		\
		tprints_field_name(#field_);				\
		print_hwaddr("", (const uint8_t *) ((where_).field_),	\
			       (size_), (hwtype_));			\
	} while (0)


# define PRINT_FIELD_OBJ_PTR(where_, field_, print_func_, ...)		\
	do {								\
		tprints_field_name(#field_);				\
		(print_func_)(&((where_).field_), ##__VA_ARGS__);	\
	} while (0)

# define PRINT_FIELD_OBJ_TCB_PTR(where_, field_,			\
				 tcp_, print_func_, ...)		\
	do {								\
		tprints_field_name(#field_);				\
		(print_func_)((tcp_), &((where_).field_),		\
			      ##__VA_ARGS__);				\
	} while (0)

# define PRINT_FIELD_OBJ_VAL(where_, field_, print_func_, ...)		\
	do {								\
		tprints_field_name(#field_);				\
		(print_func_)((where_).field_, ##__VA_ARGS__);		\
	} while (0)

# define PRINT_FIELD_OBJ_U(where_, field_, print_func_, ...)		\
	do {								\
		tprints_field_name(#field_);				\
		(print_func_)(zero_extend_signed_to_ull((where_).field_),\
			      ##__VA_ARGS__);				\
	} while (0)

# define PRINT_FIELD_OBJ_TCB_VAL(where_, field_,			\
			     tcp_, print_func_, ...)			\
	do {								\
		tprints_field_name(#field_);				\
		(print_func_)((tcp_), (where_).field_, ##__VA_ARGS__);	\
	} while (0)


# define MAYBE_PRINT_FIELD_LEN(print_prefix_, where_, field_,		\
			len_, print_func_, ...)				\
	do {								\
		unsigned int start = offsetof(typeof(where_), field_);	\
		unsigned int end = start + sizeof((where_).field_);	\
		if (len_ > start) {					\
			print_prefix_;					\
			if (len_ >= end) {				\
				print_func_((where_), field_,		\
					    ##__VA_ARGS__);		\
			} else {					\
				tprints_field_name(#field_);		\
				print_quoted_string(			\
					(void *)&(where_).field_,	\
					len_ - start, QUOTE_FORCE_HEX);	\
			}						\
		}							\
	} while (0)

#endif /* !STRACE_PRINT_FIELDS_H */
