/trace/nd-trace.c (158d116f28fbb5d3198a39aecb975d8194f447c6) (123171 bytes) (mode 100644) (type blob)

#define _XOPEN_SOURCE 500
#define _GNU_SOURCE

#include <asm/types.h>

#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/un.h>

#include <linux/netlink.h>
#include <linux/rtnetlink.h>

#include <arpa/inet.h>
#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <poll.h>
#include <semaphore.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include <sf_tools.h>

#include "ids.h"
#include "params.h"
#include "shared.h"
#include "tools.h"
#include "decode_text.h"

struct theme
{
	char		*name;
	unsigned int	colors;
	unsigned int	color_default;

	unsigned int	color_func_i_c, color_func_i_r, color_func_i_m;
	unsigned int	color_func_n_c, color_func_n_r, color_func_n_m;
	unsigned int	color_ts_s, color_ts_e, color_ts_bad;
	unsigned int	color_ret_ok, color_ret_error, color_flags, color_file;
	unsigned int	pad;
};

static const struct theme themes[] =
{
	{ .name = "def",
		.colors = 256, .color_default = 3,
		.color_func_i_c = 196,	.color_func_i_r = 202,	.color_func_i_m = 207,
		.color_func_n_c = 46,	.color_func_n_r = 70,	.color_func_n_m = 90,
		.color_ts_s = 246,	.color_ts_e = 255,	.color_ts_bad = 196,
		.color_ret_ok = 48,	.color_ret_error = 197,	.color_flags = 46,
		.color_file = 226
		},
	{ .name = NULL }
};
static const char *theme_name = "def";
static const char *theme_s1 = "", *theme_s2 = "", *theme_e = "";
static const struct theme *theme = NULL;

static const struct option options[] =
{
	{"pid",			required_argument,	NULL,	'p'},
	{"output",		required_argument,	NULL,	'o'},
	{"only-high-level",	no_argument,	 	NULL,	'H'},
	{"theme",		required_argument, 	NULL,	't'},
	{"time-format",		required_argument,	NULL,	'T'},
	{"hide-pids",		no_argument,		NULL,	'P'},
	{NULL,			0,			NULL,	0}
};

static void usage(void)
{
	fprintf(stderr,
		"Usage: nd-trace [options]\n"
		"	--pid			-p	pid to trace (can be added multiple times)\n"
		"	--output		-o	where to store the output\n"
		"	--only-high-level	-H	log only high level, if possible\n"
		"	--theme			-t	do not show output with colors ['def']\n"
		"	--time-format		-T	none, ts [def], human\n"
		"	--hide-pids		-P	do not show pids\n"
		"					If output is not a tty, no theme is used\n"
		"					Use 'none' for no theme\n"
		"\n");
	exit(1);
}

static FILE		*outf;
static unsigned int	do_exit;
static pid_t		pids[256];
static unsigned int	no_pids;
static char		only_high_level;
static char		*time_format = "ts";
static char		hide_pids = 0;
static uint64_t		last_ts;

/*
 * Set the theme
 */
static void nd_trace_color_set_theme(void)
{
	unsigned theme_colors;

	if (strcmp(theme_name, "none") == 0)
		return;

	if (!isatty(fileno(outf)))
		return;

	const char *term = getenv("TERM");
	//fprintf(outf, "TERM=[%s]\n", term); fflush(outf);
	if (term == NULL)
		return;

	if (strcmp(term, "xterm-256color") == 0) {
		theme_colors = 256;
	} else if (strcmp(term, "xterm") == 0) {
		theme_colors = 16;
	} else {
		return;
	}

	//fprintf(outf, "Searching for theme [%s] and %u colors...\n",
	//	theme_name, theme_colors); fflush(outf);
	for (int i = 0; themes[i].name != NULL; i++) {
		if (strcmp(themes[i].name, theme_name) != 0)
			continue;
		if (themes[i].colors != theme_colors)
			continue;
		theme = &themes[i];
		break;
	}
	if (!theme) {
		fprintf(outf, "  Theme [%s/%u] not found, using no theme.\n",
			theme_name, theme_colors);
		return;
	}

	if (theme->colors == 16) {
		theme_s1 = "\x1b["; theme_s2 = "m";
		theme_e = "\x1b[0m";
	} else if (theme->colors == 256) {
		theme_s1 = "\x1b[38;5;"; theme_s2 = "m";
		theme_e = "\x1b[0m";
	}

	fprintf(outf, "Theme set to [%s] based on term [%s]; colors=%u\n",
		theme->name, term, theme->colors);
}

/*
 * Color output
 */
static void nd_trace_color(char *out, const size_t out_size,
	const char *str, const char *category, const char type,
	const char *flags, const uint64_t u64)
{
	char scolor[4] = { 0 };
	int r;

	//fprintf(outf, "%s: str=[%s] category=[%s] u64=%lu\n",
	//	__func__, str, category, u64); fflush(outf);

	if (!theme)
		goto no_theme;

	unsigned color = theme->color_default;
	if (strcmp(category, "func") == 0) {
		if (strstr("i", flags)) {
			if ((type == 'c') || (type == 'R'))
				color = theme->color_func_i_c;
			else if (type == 'r')
				color = theme->color_func_i_r;
			else if (type == 'm')
				color = theme->color_func_i_m;
		} else {
			if ((type == 'c') || (type == 'R'))
				color = theme->color_func_n_c;
			else if (type == 'r')
				color = theme->color_func_n_r;
			else if (type == 'm')
				color = theme->color_func_n_m;
		}
	} else if (strcmp(category, "ts") == 0) {  // 'u64' contains milliseconds
		if (u64 >= 2000)
			color = theme->color_ts_bad;
		else if (u64 >= 1000)
			color = theme->color_ts_e;
		else
			color = theme->color_ts_s + u64 / 100;
	} else if (strcmp(category, "error") == 0) {
		if (u64 == 0)
			color = theme->color_ret_ok;
		else
			color = theme->color_ret_error;
	} else if (strcmp(category, "flags") == 0) {
		color = theme->color_flags;
	} else if (strcmp(category, "file") == 0) {
		color = theme->color_file;
	}
	snprintf(scolor, sizeof(scolor), "%u", color);

	no_theme:
	r = snprintf(out, out_size, "%s%s%s%s%s",
		theme_s1, scolor, theme_s2, str, theme_e);
	// we will not color things if we do not have enough space
	if ((unsigned) r > out_size)
		snprintf(out, out_size, "%s", str);
}


static uint8_t decode8(unsigned char *d, unsigned int *i)
{
	uint8_t u;
	u = d[*i];
	*i = *i + 1;
	return u;
}

static uint16_t decode16(unsigned char *d, unsigned int *i)
{
	uint16_t u;
	memcpy(&u, d + *i, 2);
	*i = *i + 2;
	return be16toh(u);
}

static uint32_t decode32(unsigned char *d, unsigned int *i)
{
	uint32_t u;
	memcpy(&u, d + *i, 4);
	*i = *i + 4;
	return be32toh(u);
}

static uint64_t decode64(unsigned char *d, unsigned int *i)
{
	uint64_t u;
	memcpy(&u, d + *i, 8);
	*i = *i + 8;
	return be64toh(u);
}

static uint8_t decode_bool(const char *prefix, char *out0, size_t out_size,
	unsigned char *d, unsigned int *i)
{
	char out[out_size];

	uint8_t v = d[*i]; *i = *i + 1;
	snprintf(out, out_size, "%s%s", prefix, v == 0 ? "nok" : "ok");
	nd_trace_color(out0, out_size, out, "error", '-', "", 0);
	return v;
}

// TODO: more to add from /usr/include/asm-generic/errno.h
// TODO: move this in common
static void decode_errno(char *out0, unsigned out_max, const int e)
{
	char out[out_max];

	switch (e) {
	case EPERM: snprintf(out, out_max, "EPERM"); break;
	case ENOENT: snprintf(out, out_max, "ENOENT"); break;
	case ESRCH: snprintf(out, out_max, "ESRCH"); break;
	case EINTR: snprintf(out, out_max, "EINTR"); break;
	case EIO: snprintf(out, out_max, "EIO"); break;
	case ENXIO: snprintf(out, out_max, "ENXIO"); break;
	case E2BIG: snprintf(out, out_max, "E2BIG"); break;
	case ENOEXEC: snprintf(out, out_max, "NOEXEC"); break;
	case EBADF: snprintf(out, out_max, "BADF"); break;
	case ECHILD: snprintf(out, out_max, "ECHILD"); break;
	case EAGAIN: snprintf(out, out_max, "EAGAIN"); break;
	case ENOMEM: snprintf(out, out_max, "ENOMEM"); break;
	case EACCES: snprintf(out, out_max, "EACCES"); break;
	case EFAULT: snprintf(out, out_max, "EFAULT"); break;
	case ENOTBLK: snprintf(out, out_max, "ENOTBLK"); break;
	case EBUSY: snprintf(out, out_max, "EBUSY"); break;
	case EEXIST: snprintf(out, out_max, "EEXIST"); break;
	case EXDEV: snprintf(out, out_max, "EXDEV"); break;
	case ENODEV: snprintf(out, out_max, "ENODEV"); break;
	case ENOTDIR: snprintf(out, out_max, "ENOTDIR"); break;
	case EISDIR: snprintf(out, out_max, "EISDIR"); break;
	case EINVAL: snprintf(out, out_max, "EINVAL"); break;
	case ENFILE: snprintf(out, out_max, "ENFILE"); break;
	case ENOTTY: snprintf(out, out_max, "ENOTTY"); break;
	case ETXTBSY: snprintf(out, out_max, "ETXTBSY"); break;
	case EFBIG: snprintf(out, out_max, "EFBIG"); break;
	case ENOSPC: snprintf(out, out_max, "ENOSPC"); break;
	case ESPIPE: snprintf(out, out_max, "ESPIPE"); break;
	case EROFS: snprintf(out, out_max, "EROFS"); break;
	case EMLINK: snprintf(out, out_max, "EMLINK"); break;
	case EPIPE: snprintf(out, out_max, "EPIPE"); break;
	case EDOM: snprintf(out, out_max, "EDOM"); break;
	case ERANGE: snprintf(out, out_max, "ERANGE"); break;
	case EDEADLK: snprintf(out, out_max, "EDEADLK"); break;
	case ENAMETOOLONG: snprintf(out, out_max, "ENAMETOOLONG"); break;
	case ENOLCK: snprintf(out, out_max, "ENOLCK"); break;

	case EINPROGRESS: snprintf(out, out_max, "EINPROGRESS"); break;

	default: snprintf(out, out_max, "%d", e); break;
	}

	nd_trace_color(out0, out_max, out, "error", '-', "", 0);
}

static int decode_ret_int(char *out, unsigned out_max,
	unsigned char *d, unsigned int *i)
{
	int ret = decode32(d, i);
	if (ret != -1) {
		snprintf(out, out_max, " = %d", ret);
		return ret;
	}

	int xerrno = decode32(d, i);
	char err[64];
	decode_errno(err, sizeof(err), xerrno);
	snprintf(out, out_max, " = %d (%s)", ret, err);

	return ret;
}

static ssize_t decode_ret_int64(char *out, unsigned out_max,
	unsigned char *d, unsigned int *i)
{
	ssize_t ret = decode64(d, i);
	if (ret != -1) {
		snprintf(out, out_max, " = %zd", ret);
		return ret;
	}

	int xerrno = decode32(d, i);
	char err[64];
	decode_errno(err, sizeof(err), xerrno);
	snprintf(out, out_max, " = %zd (%s)", ret, err);

	return ret;
}

static void *decode_ret_pointer(char *out, unsigned out_max,
	unsigned char *d, unsigned int *i)
{
	void *ret = (void *) decode64(d, i);
	if (ret) {
		snprintf(out, out_max, " = %p", ret);
		return ret;
	}

	char out2[32];
	nd_trace_color(out2, sizeof(out2), "NULL", "error", '-', "", 0);
	snprintf(out, out_max, " = %s", out2);

	return ret;
}

static void *decode_ret_pointer_errno(char *out, unsigned out_max,
	unsigned char *d, unsigned int *i)
{
	void *ret = (void *) decode64(d, i);
	if (ret) {
		snprintf(out, out_max, " = %p", ret);
		return ret;
	}

	int xerrno = decode32(d, i);
	char err[64];
	decode_errno(err, sizeof(err), xerrno);
	char out2[64];
	nd_trace_color(out2, sizeof(out2), err, "error", '-', "", 0);
	snprintf(out, out_max, " = %p (%s)", ret, out2);

	return ret;
}

static void decode_dirfd(char *out0, unsigned out_size,
	unsigned char *d, unsigned int *i, const char *postfix)
{
	int v = decode32(d, i);
	char out[out_size];
	switch (v) {
	case AT_FDCWD: snprintf(out, out_size, "AT_FDCWD%s", postfix); break;
	default: sprintf(out, "%d%s", v, postfix); return;
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
}

static socklen_t decode_sockaddr(char *out, unsigned char *d, unsigned int *i)
{
	struct sockaddr_storage ss;
	socklen_t sl;

	sl = decode16(d, i);

	if (sl > sizeof(ss)) {
		char dump[65];
		sf_tools_bin2hex(dump, d + *i, 32);
		sprintf(out, "[sl is too big: %u: %s]", sl, dump);
		return sl;
	}

	memcpy(&ss, d + *i, sl); *i = *i + sl;

	nd_decode_sockaddr(out, &ss, sl);

	return sl;
}

// TODO: move to 'common'
static int decode_sock_level(char *out0, unsigned out_size,
	unsigned char *d, unsigned int *i)
{
	char out[out_size];

	int level = decode32(d, i);

	switch (level) {
	case SOL_SOCKET: snprintf(out, out_size, "SOL_SOCKET"); break;
	case SOL_IP: snprintf(out, out_size, "SOL_IP"); break;
	case SOL_RAW: snprintf(out, out_size, "SOL_RAW"); break;
	case SOL_PACKET: snprintf(out, out_size, "SOL_PACKET"); break;
	case SOL_NETLINK: snprintf(out, out_size, "SOL_NETLINK"); break;
	case SOL_IPV6: snprintf(out, out_size, "SOL_IPV6"); break;
	case SOL_ICMPV6: snprintf(out, out_size, "SOL_ICMPV6"); break;
	case IPPROTO_UDP: snprintf(out, out_size, "IPPROTO_UDP"); break;
	case IPPROTO_TCP: snprintf(out, out_size, "IPPROTO_TCP"); break;
	default: snprintf(out, out_size, "todo(%d)", level); break;
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
	return level;
}

static void decode_sock_optname_socket(char *out0, unsigned out_size,
	const int optname)
{
	char out[out_size];

	switch (optname) {
	case SO_DEBUG: snprintf(out, out_size, "SO_DEBUG"); break;
	case SO_REUSEADDR: snprintf(out, out_size, "SO_REUSEADDR"); break;
	case SO_TYPE: snprintf(out, out_size, "SO_TYPE"); break;
	case SO_ERROR: snprintf(out, out_size, "SO_ERROR"); break;
	case SO_DONTROUTE: snprintf(out, out_size, "SO_DONTROUTE"); break;
	case SO_BROADCAST: snprintf(out, out_size, "SO_BROADCAST"); break;
	case SO_SNDBUF: snprintf(out, out_size, "SO_SNDBUF"); break;
	case SO_RCVBUF: snprintf(out, out_size, "SO_RCVBUF"); break;
	case SO_SNDBUFFORCE: snprintf(out, out_size, "SO_SNDBUFFORCE"); break;
	case SO_RCVBUFFORCE: snprintf(out, out_size, "SO_RCVBUFFORCE"); break;
	case SO_KEEPALIVE: snprintf(out, out_size, "SO_KEEPALIVE"); break;
	case SO_OOBINLINE: snprintf(out, out_size, "SO_OOBINLINE"); break;
	case SO_NO_CHECK: snprintf(out, out_size, "SO_NO_CHECK"); break;
	case SO_PRIORITY: snprintf(out, out_size, "SO_PRIORITY"); break;
	case SO_LINGER: snprintf(out, out_size, "SO_LINGER"); break;
	case SO_BSDCOMPAT: snprintf(out, out_size, "SO_BSDCOMPAT"); break;
	case SO_REUSEPORT: snprintf(out, out_size, "SO_REUSEPORT"); break; // 15
	case SO_PASSCRED: snprintf(out, out_size, "SO_PASSCRED"); break; // 16
	case SO_PEERCRED: snprintf(out, out_size, "SO_PEECRED"); break; // 17
	case SO_RCVLOWAT: snprintf(out, out_size, "SO_RCVLOWAT"); break;
	case SO_SNDLOWAT: snprintf(out, out_size, "SO_SNDLOWAT"); break;
	case SO_RCVTIMEO_OLD: snprintf(out, out_size, "SO_RCVTIMEO_OLD"); break;
	case SO_SNDTIMEO_OLD: snprintf(out, out_size, "SO_SNDTIMEO_OLD"); break;
	case SO_BINDTODEVICE: snprintf(out, out_size, "SO_BINDTODEVICE"); break;
	case SO_ATTACH_FILTER: snprintf(out, out_size, "SO_ATTACH_FILTER"); break;
	case SO_DETACH_FILTER: snprintf(out, out_size, "SO_DETACH_FILTER"); break;
	case SO_ACCEPTCONN: snprintf(out, out_size, "SO_ACCEPTCONN"); break;
	case SO_PEERSEC: snprintf(out, out_size, "SO_PEERSEC"); break;
	case SO_PASSSEC: snprintf(out, out_size, "SO_PASSSEC"); break;
	case SO_MARK: snprintf(out, out_size, "SO_MARK"); break;
	case SO_PROTOCOL: snprintf(out, out_size, "SO_PROTOCOL"); break;
	case SO_DOMAIN: snprintf(out, out_size, "SO_DOMAIN"); break;
	case SO_RXQ_OVFL: snprintf(out, out_size, "SO_RXQ_OVFL"); break;
	case SO_WIFI_STATUS: snprintf(out, out_size, "SO_WIFI_STATUS"); break;
	case SO_PEEK_OFF: snprintf(out, out_size, "SO_PEEK_OFF"); break;
	case SO_NOFCS: snprintf(out, out_size, "SO_NOFCS"); break;
	case SO_LOCK_FILTER: snprintf(out, out_size, "SO_LOCK_FILTER"); break;
	case SO_SELECT_ERR_QUEUE: snprintf(out, out_size, "SO_SELECT_ERR_QUEUE"); break;
	case SO_BUSY_POLL: snprintf(out, out_size, "SO_BUSY_POLL"); break;
	case SO_MAX_PACING_RATE: snprintf(out, out_size, "SO_PACING_RATE"); break;
	case SO_BPF_EXTENSIONS: snprintf(out, out_size, "SO_BPF_EXTENSIONS"); break;
	case SO_INCOMING_CPU: snprintf(out, out_size, "SO_INCOMING_CPU"); break;
	case SO_ATTACH_BPF: snprintf(out, out_size, "SO_ATTACH_BPF"); break;
	case SO_ATTACH_REUSEPORT_CBPF: snprintf(out, out_size, "SO_ATTACH_REUSEPORT_CBPF"); break;
	case SO_ATTACH_REUSEPORT_EBPF: snprintf(out, out_size, "SO_ATTACH_REUSEPORT_EBPF"); break;
	case SO_CNX_ADVICE: snprintf(out, out_size, "SO_CNX_ADVICE"); break;
	case SCM_TIMESTAMPING_OPT_STATS: snprintf(out, out_size, "SCM_TIMESTAMPING_OPT_STATS"); break;
	case SO_MEMINFO: snprintf(out, out_size, "SO_MEMINFO"); break;
	case SO_INCOMING_NAPI_ID: snprintf(out, out_size, "SO_INCOMING_NAPI_ID"); break;
	case SO_COOKIE: snprintf(out, out_size, "SO_COOKIE"); break;
	case SCM_TIMESTAMPING_PKTINFO: snprintf(out, out_size, "SCM_TIMESTAMPING_PKTINFO"); break;
	case SO_PEERGROUPS: snprintf(out, out_size, "SO_PEERGROUPS"); break;
	case SO_ZEROCOPY: snprintf(out, out_size, "SO_ZEROCOPY"); break;
	case SO_TXTIME: snprintf(out, out_size, "SO_TXTIME"); break;
	case SO_BINDTOIFINDEX: snprintf(out, out_size, "SO_BINDTOIFINDEX"); break;
	case SO_TIMESTAMP_OLD: snprintf(out, out_size, "SO_TIMESTAMP_OLD"); break;
	case SO_TIMESTAMPNS_OLD: snprintf(out, out_size, "SO_TIMESTAMPNS_OLD"); break;
	case SO_TIMESTAMPING_OLD: snprintf(out, out_size, "SO_TIMESTAMPING_OLD"); break;
	case SO_TIMESTAMP_NEW: snprintf(out, out_size, "SO_TIMESTAMP_NEW"); break;
	case SO_TIMESTAMPNS_NEW: snprintf(out, out_size, "SO_TIMESTAMPNS_NEW"); break;
	case SO_TIMESTAMPING_NEW: snprintf(out, out_size, "SO_TIMESTAMPING_NEW"); break;
	case SO_RCVTIMEO_NEW: snprintf(out, out_size, "SO_RCVTIMEO_NEW"); break;
	case SO_SNDTIMEO_NEW: snprintf(out, out_size, "SO_SNDTIMEO_NEW"); break;
	case SO_DETACH_REUSEPORT_BPF: snprintf(out, out_size, "SO_DETACH_REUSEPORT_BPF"); break;
	case SO_PREFER_BUSY_POLL: snprintf(out, out_size, "SO_PREFER_BUSY_POLL"); break;
	case SO_BUSY_POLL_BUDGET: snprintf(out, out_size, "SO_BUSY_POLL_BUDGET"); break;
	case SO_NETNS_COOKIE: snprintf(out, out_size, "SO_NETNS_COOKIE"); break;
	case SO_BUF_LOCK: snprintf(out, out_size, "SO_BUF_LOCK"); break;
	case SO_RESERVE_MEM: snprintf(out, out_size, "SO_RESERVE_MEM"); break;
	case SO_TXREHASH: snprintf(out, out_size, "SO_TXREHASH"); break;
	case SO_RCVMARK: snprintf(out, out_size, "SO_RCVMARK"); break; // 75
	default: snprintf(out, out_size, "todo(%d)", optname); break;
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
}

#ifndef IP_RECVERR_RFC4884
	#define IP_RECVERR_RFC4884 26
#endif
static void decode_sock_optname_ip(char *out0, unsigned out_size,
	const int optname)
{
	char out[out_size];

	switch (optname) {
	case IP_OPTIONS: snprintf(out, out_size, "IP_OPTIONS"); break;
	case IP_HDRINCL: snprintf(out, out_size, "IP_HDRINCL"); break;
	case IP_TOS: snprintf(out, out_size, "IP_TOS"); break;
	case IP_TTL: snprintf(out, out_size, "IP_TTL"); break;
	case IP_RECVOPTS: snprintf(out, out_size, "IP_RECVOPTS"); break;
	case IP_RETOPTS: snprintf(out, out_size, "IP_RETOPTS"); break;
	case IP_MULTICAST_IF: snprintf(out, out_size, "IP_MULTICAST_IF"); break;
	case IP_MULTICAST_TTL: snprintf(out, out_size, "IP_MULTICAST_TTL"); break;
	case IP_MULTICAST_LOOP: snprintf(out, out_size, "IP_MULTICAST_LOOP"); break;
	case IP_ADD_MEMBERSHIP: snprintf(out, out_size, "IP_ADD_MEMBERSHIP"); break;
	case IP_DROP_MEMBERSHIP: snprintf(out, out_size, "IP_DROP_MEMBERSHIP"); break;
	case IP_UNBLOCK_SOURCE: snprintf(out, out_size, "IP_UNBLOCK_SOURCE"); break;
	case IP_BLOCK_SOURCE: snprintf(out, out_size, "IP_BLOCK_SOURCE"); break;
	case IP_ADD_SOURCE_MEMBERSHIP: snprintf(out, out_size, "IP_ADD_SOURCE_MEMBERSHIP"); break;
	case IP_DROP_SOURCE_MEMBERSHIP: snprintf(out, out_size, "IP_DROP_SOURCE_MEMBERSHIP"); break;
	case IP_MSFILTER: snprintf(out, out_size, "IP_MSFILTER"); break;
	case MCAST_JOIN_GROUP: snprintf(out, out_size, "MCAST_JOIN_GROUP"); break;
	case MCAST_BLOCK_SOURCE: snprintf(out, out_size, "MCAST_BLOCK_SOURCE"); break;
	case MCAST_UNBLOCK_SOURCE: snprintf(out, out_size, "MCAST_UNBLOCK_SOURCE"); break;
	case MCAST_LEAVE_GROUP: snprintf(out, out_size, "MCAST_LEAVE_GROUP"); break;
	case MCAST_JOIN_SOURCE_GROUP: snprintf(out, out_size, "MCAST_JOIN_SOURCE_GROUP"); break;
	case MCAST_LEAVE_SOURCE_GROUP: snprintf(out, out_size, "MCAST_LEAVE_SOURCE_GROUP"); break;
	case MCAST_MSFILTER: snprintf(out, out_size, "MCAST_MSFILTER"); break;
	case IP_MULTICAST_ALL: snprintf(out, out_size, "IP_MULTICAST_ALL"); break;
	case IP_UNICAST_IF: snprintf(out, out_size, "IP_UNICAST_IF"); break;
	case IP_ROUTER_ALERT: snprintf(out, out_size, "IP_ROUTER_ALERT"); break;
	case IP_PKTINFO: snprintf(out, out_size, "IP_PKTINFO"); break;
	case IP_PKTOPTIONS: snprintf(out, out_size, "IP_PKTOPTIONS"); break;
	case IP_MTU_DISCOVER: snprintf(out, out_size, "IP_MTU_DISCOVER"); break;
	// TODO: these are for optname IP_MTU_DISCOVER
	//case IP_PMTUDISC_DONT: snprintf(out, out_size, "IP_PMTUDISC_DONT"); break;
	//case IP_PMTUDISC_WANT: snprintf(out, out_size, "IP_PMTUDISC_WANT"); break;
	//case IP_PMTUDISC_DO: snprintf(out, out_size, "IP_PMTUDISC_DO"); break;
	//case IP_PMTUDISC_PROBE: snprintf(out, out_size, "IP_PMTUDISC_PROBE"); break;
	//case IP_PMTUDISC_INTERFACE: snprintf(out, out_size, "IP_PMTUDISC_INTERFACE"); break;
	//case IP_PMTUDISC_OMIT: snprintf(out, out_size, "IP_PMTUDISC_OMIT"); break;
	case IP_RECVERR: snprintf(out, out_size, "IP_RECVERR"); break;
	case IP_RECVTTL: snprintf(out, out_size, "IP_RECVTTL"); break;
	case IP_RECVTOS: snprintf(out, out_size, "IP_RECVTOS"); break;
	case IP_MTU: snprintf(out, out_size, "IP_MTU"); break;
	case IP_FREEBIND: snprintf(out, out_size, "IP_FREEBIND"); break;
	case IP_IPSEC_POLICY: snprintf(out, out_size, "IP_IPSEC_POLICY"); break;
	case IP_XFRM_POLICY: snprintf(out, out_size, "IP_XFRM_POLICY"); break;
	case IP_PASSSEC: snprintf(out, out_size, "IP_PASSSEC"); break;
	case IP_TRANSPARENT: snprintf(out, out_size, "IP_TRANSPARENT"); break;
	case IP_ORIGDSTADDR: snprintf(out, out_size, "IP_ORIGDSTADDR"); break;
	case IP_MINTTL: snprintf(out, out_size, "IP_MINTTL"); break;
	case IP_NODEFRAG: snprintf(out, out_size, "IP_NODEFRAG"); break;
	case IP_CHECKSUM: snprintf(out, out_size, "IP_CHECKSUM"); break;
	case IP_BIND_ADDRESS_NO_PORT: snprintf(out, out_size, "IP_BIND_ADDRESS_NO_PORT"); break;
	case IP_RECVFRAGSIZE: snprintf(out, out_size, "IP_RECVFRAGSIZE"); break;
	case IP_RECVERR_RFC4884: snprintf(out, out_size, "IP_RECVERR_RFC4884"); break;
	// TODO case IP_MAX_MEMBERSHIPS: snprintf(out, out_size, "IP_MAX_MEMBERSHIPS"); break;
	default: snprintf(out, out_size, "todo(%d)", optname); break;
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
}

#ifndef IPV6_MULTICAST_ALL
	#define IPV6_MULTICAST_ALL 29
#endif
#ifndef IPV6_ROUTER_ALERT_ISOLATE
	#define IPV6_ROUTER_ALERT_ISOLATE 30
#endif
#ifndef IPV6_RECVERR_RFC4884
	#define IPV6_RECVERR_RFC4884 31
#endif
static void decode_sock_optname_ipv6(char *out0, unsigned out_size,
	const int optname)
{
	char out[out_size];

	switch (optname) {
	case IPV6_ADDRFORM: snprintf(out, out_size, "IPV6_ADDRFORM"); break;
	case IPV6_2292PKTINFO: snprintf(out, out_size, "IPV6_2292PKTINFO"); break;
	case IPV6_2292HOPOPTS: snprintf(out, out_size, "IPV6_2292HOPOPTS"); break;
	case IPV6_2292DSTOPTS: snprintf(out, out_size, "IPV6_2292DSTOPTS"); break;
	case IPV6_2292RTHDR: snprintf(out, out_size, "IPV6_2292RTHDR"); break;
	case IPV6_2292PKTOPTIONS: snprintf(out, out_size, "IPV6_2292PKTOPTIONS"); break;
	case IPV6_CHECKSUM: snprintf(out, out_size, "IPV6_CHECKSUM"); break;
	case IPV6_2292HOPLIMIT: snprintf(out, out_size, "IPV6_2292HOPLIMIT"); break;
	case IPV6_NEXTHOP: snprintf(out, out_size, "IPV6_NEXTHOP"); break;
	case IPV6_AUTHHDR: snprintf(out, out_size, "IPV6_AUTHHDR"); break;
	case IPV6_UNICAST_HOPS: snprintf(out, out_size, "IPV6_UNICAST_HOPS"); break;
	case IPV6_MULTICAST_IF: snprintf(out, out_size, "IPV6_MULTICAST_IF"); break;
	case IPV6_MULTICAST_HOPS: snprintf(out, out_size, "IPV6_MULTICAST_HOPS"); break;
	case IPV6_MULTICAST_LOOP: snprintf(out, out_size, "IPV6_MULTICAST_LOOP"); break;
	case IPV6_JOIN_GROUP: snprintf(out, out_size, "IPV6_JOIN_GROUP"); break;
	case IPV6_LEAVE_GROUP: snprintf(out, out_size, "IPV6_LEAVE_GROUP"); break;
	case IPV6_ROUTER_ALERT: snprintf(out, out_size, "IPV6_ROUTER_ALERT"); break;
	case IPV6_MTU_DISCOVER: snprintf(out, out_size, "IPV6_MTU_DISCOVER"); break;
	case IPV6_MTU: snprintf(out, out_size, "IPV6_MTU"); break;
	case IPV6_RECVERR: snprintf(out, out_size, "IPV6_RECVERR"); break;
	case IPV6_V6ONLY: snprintf(out, out_size, "IPV6_V6ONLY"); break;
	case IPV6_JOIN_ANYCAST: snprintf(out, out_size, "IPV6_JOIN_ANYCAST"); break;
	case IPV6_LEAVE_ANYCAST: snprintf(out, out_size, "IPV6_LEAVE_ANYCAST"); break;
	case IPV6_MULTICAST_ALL: snprintf(out, out_size, "IPV6_MULTICAST_ALL"); break;
	case IPV6_ROUTER_ALERT_ISOLATE: snprintf(out, out_size, "IPV6_ROUTER_ALERT_ISOLATE"); break;
	case IPV6_RECVERR_RFC4884: snprintf(out, out_size, "IPV6_RECVERR_RFC4884"); break;
	case IPV6_IPSEC_POLICY: snprintf(out, out_size, "IPV6_IPSEC_POLICY"); break;
	case IPV6_XFRM_POLICY: snprintf(out, out_size, "IPV6_XFRM_POLICY"); break;
	case IPV6_HDRINCL: snprintf(out, out_size, "IPV6_HDRINCL"); break;
	case IPV6_RECVPKTINFO: snprintf(out, out_size, "IPV6_RECVPKTINFO"); break;
	case IPV6_PKTINFO: snprintf(out, out_size, "IPV6_PKTINFO"); break;
	case IPV6_RECVHOPLIMIT: snprintf(out, out_size, "IPV6_RECVHOPLIMIT"); break;
	case IPV6_HOPLIMIT: snprintf(out, out_size, "IPV6_HOPLIMIT"); break;
	case IPV6_RECVHOPOPTS: snprintf(out, out_size, "IPV6_RECVHOPOPTS"); break;
	case IPV6_HOPOPTS: snprintf(out, out_size, "IPV6_HOPOPTS"); break;
	case IPV6_RTHDRDSTOPTS: snprintf(out, out_size, "IPV6_RTHDRDSTOPTS"); break;
	case IPV6_RECVRTHDR: snprintf(out, out_size, "IPV6_RECVRTHDR"); break;
	case IPV6_RTHDR: snprintf(out, out_size, "IPV6_RTHDR"); break;
	case IPV6_RECVDSTOPTS: snprintf(out, out_size, "IPV6_RECVDSTOPTS"); break;
	case IPV6_DSTOPTS: snprintf(out, out_size, "IPV6_DSTOPTS"); break;
	case IPV6_RECVPATHMTU: snprintf(out, out_size, "IPV6_RECVPATHMTU"); break;
	case IPV6_PATHMTU: snprintf(out, out_size, "IPV6_PATHMTU"); break;
	case IPV6_DONTFRAG: snprintf(out, out_size, "IPV6_DONTFRAG"); break;
	case IPV6_RECVTCLASS: snprintf(out, out_size, "IPV6_RECVTCLASS"); break;
	case IPV6_TCLASS: snprintf(out, out_size, "IPV6_TCLASS"); break;
	case IPV6_AUTOFLOWLABEL: snprintf(out, out_size, "IPV6_AUTOFLOWLABEL"); break;
	case IPV6_ADDR_PREFERENCES: snprintf(out, out_size, "IPV6_ADDR_PREFERENCES"); break;
	case IPV6_MINHOPCOUNT: snprintf(out, out_size, "IPV6_MINHOPCOUNT"); break;
	case IPV6_ORIGDSTADDR: snprintf(out, out_size, "IPV6_ORIGDSTADDR"); break;
	case IPV6_TRANSPARENT: snprintf(out, out_size, "IPV6_TRANSPARENT"); break;
	case IPV6_UNICAST_IF: snprintf(out, out_size, "IPV6_UNICAST_IF"); break;
	case IPV6_RECVFRAGSIZE: snprintf(out, out_size, "IPV6_RECVFRAGSIZE"); break;
	case IPV6_FREEBIND: snprintf(out, out_size, "IPV6_FREEBIND"); break;
	//case : snprintf(out, out_size, ""); break;
	default: snprintf(out, out_size, "todo(%d)", optname); break;
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
}

#ifndef TCP_ZEROCOPY_RECEIVE
	#define TCP_ZEROCOPY_RECEIVE 35
#endif
#ifndef TCP_INQ
	#define TCP_INQ 36
#endif
#ifndef TCP_TX_DELAY
	#define TCP_TX_DELAY 37
#endif
#ifndef UDP_GRO
	#define UDP_GRO 104
#endif
static void decode_sock_optname_tcp(char *out0, unsigned out_size,
	const int optname)
{
	char out[out_size];

	switch (optname) {
	case TCP_NODELAY: snprintf(out, out_size, "TCP_NODELAY"); break;
	case TCP_MAXSEG: snprintf(out, out_size, "TCP_MAXSEG"); break;
	case TCP_CORK: snprintf(out, out_size, "TCP_CORK"); break;
	case TCP_KEEPIDLE: snprintf(out, out_size, "TCP_KEEPIDLE"); break;
	case TCP_KEEPINTVL: snprintf(out, out_size, "TCP_KEEPINTVL"); break;
	case TCP_KEEPCNT: snprintf(out, out_size, "TCP_KEEPCNT"); break;
	case TCP_SYNCNT: snprintf(out, out_size, "TCP_SYNCNT"); break;
	case TCP_LINGER2: snprintf(out, out_size, "TCP_LINGER2"); break;
	case TCP_DEFER_ACCEPT: snprintf(out, out_size, "TCP_DEFER_ACCEPT"); break;
	case TCP_WINDOW_CLAMP: snprintf(out, out_size, "TCP_WINDOW_CLAMP"); break;
	case TCP_INFO: snprintf(out, out_size, "TCP_INFO"); break;
	case TCP_QUICKACK: snprintf(out, out_size, "TCP_QUICKACK"); break;
	case TCP_CONGESTION: snprintf(out, out_size, "TCP_CONGESTION"); break;
	case TCP_MD5SIG: snprintf(out, out_size, "TCP_MD5SIG"); break;
	case TCP_COOKIE_TRANSACTIONS: snprintf(out, out_size, "TCP_COOKIE_TRANSACTIONS"); break;
	case TCP_THIN_LINEAR_TIMEOUTS: snprintf(out, out_size, "TCP_THIN_LINEAR_TIMEOUTS"); break;
	case TCP_THIN_DUPACK: snprintf(out, out_size, "TCP_THIN_DUPACK"); break;
	case TCP_USER_TIMEOUT: snprintf(out, out_size, "TCP_USER_TIMEOUT"); break;
	case TCP_REPAIR: snprintf(out, out_size, "TCP_REPAIR"); break;
	case TCP_REPAIR_QUEUE: snprintf(out, out_size, "TCP_REPAIR_QUEUE"); break;
	case TCP_QUEUE_SEQ: snprintf(out, out_size, "TCP_QUEUE_SEQ"); break;
	case TCP_REPAIR_OPTIONS: snprintf(out, out_size, "TCP_REPAIR_OPTIONS"); break;
	case TCP_FASTOPEN: snprintf(out, out_size, "TCP_FASTOPEN"); break;
	case TCP_TIMESTAMP: snprintf(out, out_size, "TCP_TIMESTAMP"); break;
	case TCP_NOTSENT_LOWAT: snprintf(out, out_size, "TCP_NOTSENT_LOWAT"); break;
	case TCP_CC_INFO: snprintf(out, out_size, "TCP_CC_INFO"); break;
	case TCP_SAVE_SYN: snprintf(out, out_size, "TCP_SAVE_SYN"); break;
	case TCP_SAVED_SYN: snprintf(out, out_size, "TCP_SAVED_SYN"); break;
	case TCP_REPAIR_WINDOW: snprintf(out, out_size, "TCP_REPAIR_WINDOW"); break;
	case TCP_FASTOPEN_CONNECT: snprintf(out, out_size, "TCP_FASTOPEN_CONNECT"); break;
	case TCP_ULP: snprintf(out, out_size, "TCP_ULP"); break;
	case TCP_MD5SIG_EXT: snprintf(out, out_size, "TCP_MD5SIG_EXT"); break;
	case TCP_FASTOPEN_KEY: snprintf(out, out_size, "TCP_FASTOPEN_KEY"); break;
	case TCP_FASTOPEN_NO_COOKIE: snprintf(out, out_size, "TCP_FASTOPEN_NO_COOKIE"); break;
	case TCP_ZEROCOPY_RECEIVE: snprintf(out, out_size, "TCP_ZEROCOPY_RECEIVE"); break;
	case TCP_INQ: snprintf(out, out_size, "TCP_INQ"); break;
	case TCP_TX_DELAY: snprintf(out, out_size, "TCP_TX_DELAY"); break;
	//case : snprintf(out, out_size, ""); break;
	default: snprintf(out, out_size, "todo(%d)", optname); break;
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
}

#ifndef UDP_SEGMENT
	#define UDP_SEGMENT 103
#endif
static void decode_sock_optname_udp(char *out0, unsigned out_size,
	const int optname)
{
	char out[out_size];

	switch (optname) {
	case UDP_CORK: snprintf(out, out_size, "UDP_CORK"); break;
	case UDP_ENCAP: snprintf(out, out_size, "UDP_ENCAP"); break;
	case UDP_NO_CHECK6_TX: snprintf(out, out_size, "UDP_NO_CHECK6_TX"); break;
	case UDP_NO_CHECK6_RX: snprintf(out, out_size, "UDP_NO_CHECK6_RX"); break;
	case UDP_SEGMENT: snprintf(out, out_size, "UDP_SEGMENT"); break;
	case UDP_GRO: snprintf(out, out_size, "UDP_GRO"); break;
	//case : snprintf(out, out_size, ""); break;
	default: snprintf(out, out_size, "todo(%d)", optname); break;
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
}

static void decode_sock_optname_nl(char *out0, unsigned out_size,
	const int optname)
{
	char out[out_size];

	switch (optname) {
	case NETLINK_ADD_MEMBERSHIP:	snprintf(out, out_size, "ADD_MEMBERSHIP"); break;
	case NETLINK_DROP_MEMBERSHIP:	snprintf(out, out_size, "DROP_MEMBERSHIP"); break;
	case NETLINK_PKTINFO:		snprintf(out, out_size, "PKTINFO"); break;
	case NETLINK_BROADCAST_ERROR:	snprintf(out, out_size, "BROADCAST_ERROR"); break;
	case NETLINK_NO_ENOBUFS:	snprintf(out, out_size, "NO_ENOBUFS"); break;
	case NETLINK_RX_RING:		snprintf(out, out_size, "RX_RING"); break;
	case NETLINK_TX_RING:		snprintf(out, out_size, "TX_RING"); break;
	case NETLINK_LISTEN_ALL_NSID:	snprintf(out, out_size, "LISTEN_ALL_NSID"); break;
	case NETLINK_LIST_MEMBERSHIPS:	snprintf(out, out_size, "LIST_MEMBERSHIPS"); break;
	case NETLINK_CAP_ACK:		snprintf(out, out_size, "CAP_ACK"); break;
	case NETLINK_EXT_ACK:		snprintf(out, out_size, "EXT_ACK"); break;
	case NETLINK_GET_STRICT_CHK:	snprintf(out, out_size, "GET_STRICT_CHK"); break;
	default: snprintf(out, out_size, "todo(%d)", optname); break;
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
}

static void decode_sock_optname(char *out, unsigned out_size,
	const int level,
	unsigned char *d, unsigned int *i)
{
	int v = decode32(d, i);

	if (level == SOL_SOCKET)
		decode_sock_optname_socket(out, out_size, v);
	else if (level == SOL_IP)
		decode_sock_optname_ip(out, out_size, v);
	else if (level == SOL_IPV6)
		decode_sock_optname_ipv6(out, out_size, v);
	else if (level == SOL_TCP)
		decode_sock_optname_tcp(out, out_size, v);
	else if (level == SOL_UDP)
		decode_sock_optname_udp(out, out_size, v);
	else if (level == SOL_NETLINK)
		decode_sock_optname_nl(out, out_size, v);
	else
		snprintf(out, out_size, "%d", v);
}

static void decode_syslog_prio(char *out0, unsigned out_size,
	unsigned char *d, unsigned int *i)
{
	int v = decode32(d, i);
	int facility = v >> 3;
	char sfac[16];

	if (facility)
		snprintf(sfac, sizeof(sfac), "/fac=%d", facility);
	else
		sfac[0] = '\0';

	char out[out_size];
	switch (v & 7) {
	case LOG_EMERG: snprintf(out, out_size, "LOG_EMERG%s", sfac); break;
	case LOG_ALERT: snprintf(out, out_size, "LOG_ALERT%s", sfac); break;
	case LOG_CRIT: snprintf(out, out_size, "LOG_CRIT%s", sfac); break;
	case LOG_ERR: snprintf(out, out_size, "LOG_ERR%s", sfac); break;
	case LOG_WARNING: snprintf(out, out_size, "LOG_WARNING%s", sfac); break;
	case LOG_NOTICE: snprintf(out, out_size, "LOG_NOTICE%s", sfac); break;
	case LOG_INFO: snprintf(out, out_size, "LOG_INFO%s", sfac); break;
	case LOG_DEBUG: snprintf(out, out_size, "LOG_DEBUG%s", sfac); break;
	default: snprintf(out, out_size, "todo(%d/%s)", v & 7, sfac);
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
}

static void decode_stat(char *out, unsigned out_size,
	unsigned char *d, unsigned int *i)
{
	struct stat s;

	int ret = decode_ret_int(out, out_size, d, i);
	if (ret != 0)
		return;

	s.st_dev = decode64(d, i);
	s.st_ino = decode64(d, i);
	s.st_mode = decode32(d, i);
	s.st_nlink = decode64(d, i);
	s.st_uid = decode32(d, i);
	s.st_gid = decode32(d, i);
	s.st_rdev = decode64(d, i);
	s.st_size = decode64(d, i);
	s.st_blksize = decode64(d, i);
	s.st_blocks = decode64(d, i);
	s.st_atim.tv_sec = decode64(d, i);
	s.st_atim.tv_nsec = decode32(d, i);
	s.st_mtim.tv_sec = decode64(d, i);
	s.st_mtim.tv_nsec = decode32(d, i);
	s.st_ctim.tv_sec = decode64(d, i);
	s.st_ctim.tv_nsec = decode32(d, i);

	snprintf(out, out_size,
		" => {dev=%u:%u ino=%lu mode=0x%u nlink=%lu uid=%u gid=%u rdev=%lu"
		" size=%lu blksize=%lu blocks=%lu atime=%ld.%010ld"
		" mtime=%ld.%010ld ctime=%ld.%010ld}",
		major(s.st_dev), minor(s.st_dev),
		s.st_ino, s.st_mode, s.st_nlink, s.st_uid, s.st_gid, s.st_rdev,
		s.st_size, s.st_blksize, s.st_blocks, s.st_atim.tv_sec, s.st_atim.tv_nsec,
		s.st_mtim.tv_sec, s.st_mtim.tv_nsec, s.st_ctim.tv_sec, s.st_ctim.tv_nsec);
}

static void decode_statx_dirfd(char *out0, const size_t out_size,
	unsigned char *d, unsigned int *i)
{
	int v = decode32(d, i);
	char out[out_size];
	switch (v) {
	case AT_FDCWD: snprintf(out, out_size, "AT_FDCWD"); break;
	default: snprintf(out, out_size, "%d", v); break;
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
}

static void decode_statx_timestamp(char *out, const size_t out_size,
	const char *prefix, unsigned char *d, unsigned int *i)
{
	struct statx_timestamp ts;

	ts.tv_sec = decode64(d, i);
	ts.tv_nsec = decode32(d, i);

	snprintf(out, out_size, "%s%llu.%06u", prefix, ts.tv_sec, ts.tv_nsec);
}

static void decode_statx_attr(char *out, const size_t out_size,
	const uint64_t attr, const uint64_t attr_mask)
{
	char sattr[128], color_sattr[128];
	nd_decode_statx_attr(sattr, sizeof(sattr), attr);
	nd_trace_color(color_sattr, sizeof(color_sattr), sattr, "flags", '-', "", 0);
	//char sattr_mask[128];
	//nd_decode_statx_attr(sattr_mask, sizeof(sattr_mask), attr_mask);
	//snprintf(out, out_size, " attr=%s/%s", sattr, sattr_mask);
	snprintf(out, out_size, " attr=%s", color_sattr);
}

// TODO: proper decoding of file type
static void decode_statx(char *out, unsigned out_size,
	unsigned char *d, unsigned int *i)
{
	struct statx s;
	char blksize[16] = { 0 };
	char nlink[16] = { 0 };
	char uid[16] = { 0 };
	char gid[16] = { 0 };
	char mode[16] = { 0 };
	char ino[32] = { 0 };
	char size[32] = { 0 };
	char blocks[32] = { 0 };
	char atime[32] = { 0 };
	char btime[32] = { 0 };
	char ctime[32] = { 0 };
	char mtime[32] = { 0 };
	char rdev[32] = { 0 };
	char dev[32] = { 0 };
	char mnt[32] = { 0 };

	int ret = decode_ret_int(out, out_size, d, i);
	if (ret != 0)
		return;

	s.stx_mask = decode32(d, i);
	s.stx_blksize = decode32(d, i);
	snprintf(blksize, sizeof(blksize), "blksize=%u", s.stx_blksize);
	s.stx_attributes = decode64(d, i);

	if (s.stx_mask & STATX_NLINK) {
		s.stx_nlink = decode32(d, i);
		snprintf(nlink, sizeof(nlink), " nlink=%u", s.stx_nlink);
	}
	if (s.stx_mask & STATX_UID) {
		s.stx_uid = decode32(d, i);
		snprintf(uid, sizeof(uid), " uid=%u", s.stx_uid);
	}
	if (s.stx_mask & STATX_GID) {
		s.stx_gid = decode32(d, i);
		snprintf(gid, sizeof(gid), " gid=%u", s.stx_gid);
	}
	if (s.stx_mask & (STATX_TYPE | STATX_MODE)) {
		s.stx_mode = decode16(d, i);
		snprintf(mode, sizeof(mode), " mode=%hu", s.stx_mode);
	}
	if (s.stx_mask & STATX_INO) {
		s.stx_ino = decode64(d, i);
		snprintf(ino, sizeof(ino), " ino=%llu", s.stx_ino);
	}
	if (s.stx_mask & STATX_SIZE) {
		s.stx_size = decode64(d, i);
		snprintf(size, sizeof(size), " size=%llu", s.stx_size);
	}
	if (s.stx_mask & STATX_BLOCKS) {
		s.stx_blocks = decode64(d, i);
		snprintf(blocks, sizeof(blocks), " blocks=%llu", s.stx_blocks);
	}

	s.stx_attributes_mask = decode64(d, i);

	if (s.stx_mask & STATX_ATIME)
		decode_statx_timestamp(atime, sizeof(atime), " atime=", d, i);
	if (s.stx_mask & STATX_BTIME)
		decode_statx_timestamp(btime, sizeof(btime), " btime=", d, i);
	if (s.stx_mask & STATX_CTIME)
		decode_statx_timestamp(ctime, sizeof(ctime), " ctime=", d, i);
	if (s.stx_mask & STATX_MTIME)
		decode_statx_timestamp(mtime, sizeof(mtime), " mtime=", d, i);

	s.stx_rdev_major = decode32(d, i);
	s.stx_rdev_minor = decode32(d, i);
	snprintf(rdev, sizeof(rdev), " rdev=%u:%u", s.stx_rdev_major, s.stx_rdev_minor);

	s.stx_dev_major = decode32(d, i);
	s.stx_dev_minor = decode32(d, i);
	snprintf(dev, sizeof(dev), " dev=%u:%u", s.stx_dev_major, s.stx_dev_minor);

	if (s.stx_mask & STATX_MNT_ID) {
		s.stx_mnt_id = decode64(d, i);
		snprintf(mnt, sizeof(mnt), " mnt_id=%llu", s.stx_mnt_id);
	}

	char attr[128];
	decode_statx_attr(attr, sizeof(attr), s.stx_attributes, s.stx_attributes_mask);

	snprintf(out, out_size,
		" => {%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s}",
		blksize, attr, nlink, uid, gid, mode, ino, size, blocks,
		atime, btime, ctime, mtime, rdev, dev);
}

static void decode_string_array(char *out, size_t out_size,
	unsigned char *d, unsigned int *i)
{
	size_t rest = out_size;
	char *add = "";

	if (out_size < 1)
		return;

	if (out_size < 3) {
		strcpy(out, "");
		return;
	}

	strcpy(out, "{");

	while (1) {
		unsigned short len = decode16(d, i);
		if (len == 0)
			break;

		char e[len * 4 + 1];
		sf_tools_bin2hex_ascii(e, d + *i, len); *i = *i + len;

		unsigned int e_len = strlen(e);
		if (2 + e_len + 2 > rest) // 2 for 2x"'" and 2 for ', ' or '}'
			break;

		strcat(out, add);

		strcat(out, "'");
		strcat(out, e);
		strcat(out, "'");

		add = ", ";
		rest -= e_len + 2;
	}

	strcat(out, "}");
}

static int decode_sqlite3_ret(char *out0, unsigned out_size,
	unsigned char *d, unsigned int *i)
{
	unsigned int ret = decode32(d, i);

	char out[out_size];
	switch (ret) {
	case 0: snprintf(out, out_size, "ok"); break;
	case 1: snprintf(out, out_size, "ERROR"); break;
	case 2: snprintf(out, out_size, "INTERNAL"); break;
	case 3: snprintf(out, out_size, "PERM"); break;
	case 4: snprintf(out, out_size, "ABORT"); break;
	case 5: snprintf(out, out_size, "BUSY"); break;
	case 6: snprintf(out, out_size, "LOCKED"); break;
	case 7: snprintf(out, out_size, "NOMEM"); break;
	case 8: snprintf(out, out_size, "READONLY"); break;
	case 9: snprintf(out, out_size, "INTERRUPT"); break;
	case 10: snprintf(out, out_size, "IOERR"); break;
	case 11: snprintf(out, out_size, "CORRUPT"); break;
	case 12: snprintf(out, out_size, "NOTFOUND"); break;
	case 13: snprintf(out, out_size, "FULL"); break;
	case 14: snprintf(out, out_size, "CANTOPEN"); break;
	case 15: snprintf(out, out_size, "PROTOCOL"); break;
	case 16: snprintf(out, out_size, "EMPTY"); break;
	case 17: snprintf(out, out_size, "SCHEMA"); break;
	case 18: snprintf(out, out_size, "TOOBIG"); break;
	case 19: snprintf(out, out_size, "CONSTRAINT"); break;
	case 20: snprintf(out, out_size, "MISMATCH"); break;
	case 21: snprintf(out, out_size, "MISUSE"); break;
	case 22: snprintf(out, out_size, "NOLFS"); break;
	case 23: snprintf(out, out_size, "AUTH"); break;
	case 24: snprintf(out, out_size, "FORMAT"); break;
	case 25: snprintf(out, out_size, "RANGE"); break;
	case 26: snprintf(out, out_size, "NOTADB"); break;
	case 27: snprintf(out, out_size, "NOTICE"); break;
	case 28: snprintf(out, out_size, "WARNING"); break;
	case 100: snprintf(out, out_size, "ROW"); break;
	case 101: snprintf(out, out_size, "DONE"); break;
	default: snprintf(out, out_size, "todo(%u)", ret);
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
	return ret;
}

static int decode_sqlite3_open_flags(char *out, unsigned out_size,
	unsigned char *d, unsigned int *i)
{
	unsigned int ret = decode32(d, i);

	char *a = ""; if (ret & 0x00000001) a = "|READONLY";
	char *b = ""; if (ret & 0x00000002) b = "|READWRITE";
	char *c = ""; if (ret & 0x00000004) c = "|CREATE";
	char *e = ""; if (ret & 0x00000008) e = "|DELETEONCLOSE";
	char *f = ""; if (ret & 0x00000010) f = "|EXCLUSIVE";
	char *g = ""; if (ret & 0x00000020) g = "|AUTOPROXY";
	char *h = ""; if (ret & 0x00000040) h = "|URI";
	char *j = ""; if (ret & 0x00000080) j = "|MEMORY";
	char *k = ""; if (ret & 0x00000100) k = "|MAIN_DB";
	char *l = ""; if (ret & 0x00000200) l = "|TEMP_DB";
	char *m = ""; if (ret & 0x00000400) m = "|TRANSIENT_DB";
	char *n = ""; if (ret & 0x00000800) n = "|MAIN_JOURNAL";
	char *o = ""; if (ret & 0x00001000) o = "|TEMP_JOURNAL";
	char *p = ""; if (ret & 0x00002000) p = "|SUBJOURNAL";
	char *q = ""; if (ret & 0x00004000) q = "|SUPER_JOURNAL";
	char *r = ""; if (ret & 0x00008000) r = "|NOMUTEX";
	char *s = ""; if (ret & 0x00010000) s = "|FULLMUTEX";
	char *t = ""; if (ret & 0x00020000) t = "|SHAREDCACHE";
	char *u = ""; if (ret & 0x00040000) u = "|PRIVATECACHE";
	char *v = ""; if (ret & 0x00080000) v = "|WAL";
	char *w = ""; if (ret & 0x00100000) w = "|NOFOLLOW";

	snprintf(out, out_size, "0x%x=%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
		ret, a, b, c, e, f, g, h, j, k, l, m, n, o, p, q, r, s, t, u, v, w);

	return ret;
}

static int decode_mysql_real_connect_flags(char *out, unsigned out_size,
	unsigned char *d, unsigned int *i)
{
	int ret = decode32(d, i);

	char *f1 = ""; if (ret & 2)	f1 = "|FOUND_ROWS";
	char *f2 = ""; if (ret & 32)	f2 = "|COMPRESS";
	char *f3 = ""; if (ret & 64)	f3 = "|SSL_DONT_VERIFY_SERVER_CERT";
	char *f4 = ""; if (ret & 256)	f4 = "|IGNORE_SPACE";
	char *f5 = ""; if (ret & 1024)	f5 = "|INTERACTIVE";
	char *f6 = ""; if (ret & 2048)	f6 = "|SSL";

	snprintf(out, out_size, "0x%x=%s%s%s%s%s%s", ret, f1, f2, f3, f4, f5, f6);

	return ret;
}

static int decode_dlopen_flags(char *out, size_t out_size,
	unsigned char *d, unsigned int *i)
{
	int ret = decode32(d, i);

	char *lazy = ""; if (ret & RTLD_LAZY) lazy = "|LAZY";
	char *now = ""; if (ret & RTLD_NOW) lazy = "|NOW";
	char *global = ""; if (ret & RTLD_GLOBAL) global = "|GLOBAL";
	char *local = ""; if (ret & RTLD_LOCAL) local = "|LOCAL";
	char *nodel = ""; if (ret & RTLD_NODELETE) nodel = "|NODELETE";
	char *noload = ""; if (ret & RTLD_NOLOAD) nodel = "|NOLOAD";
	char *deep = ""; if (ret & RTLD_DEEPBIND) deep = "|DEEPBIND";

	snprintf(out, out_size, "0x%x=%s%s%s%s%s%s%s",
		ret, lazy, now, global, local, nodel, noload, deep);

	return ret;
}

static int decode_mysqli_mode(char *out, size_t out_size,
	unsigned char *d, unsigned int *i)
{
	int mode = decode32(d, i);

	if (mode == 1)
		snprintf(out, out_size, "MYSQLI_ASSOC");
	else if (mode == 2)
		snprintf(out, out_size, "MYSQLI_NUM");
	else if (mode == 3)
		snprintf(out, out_size, "MYSQLI_BOTH");

	return mode;
}

static void decode_query_params(char *out, size_t out_size,
	unsigned char filter_bind_way, unsigned char *d, unsigned int *i)
{
	unsigned short j;
	size_t len, rest = out_size - 1 - 1; // -1 for \0 and -1 for '}'

	//fprintf(outf, "%s: filter_bind_way=%hhu\n",
	//	__func__, filter_bind_way);

	if (rest >= 2) {
		strcpy(out, " {");
		rest -= 2;
	}

	char *add = "", do_break = 0;
	j = 1;
	while (1) {
		char value[64];
		uint8_t type = decode8(d, i);
		//fprintf(outf, "  type=%hhu\n", type);
		if (type == ND_TYPE_LAST)
			break;

		uint8_t bind_info = decode8(d, i); // (o->bind_way << 2) | o->bind_type
		uint8_t bind_type = bind_info & 3;
		uint8_t bind_way = (bind_info >> 2) & 3;
		char head[128];

		const char *way = "";
		if (bind_way == ND_PARAMS_BIND_WAY_IN)
			way = "IN:";
		else if (bind_way == ND_PARAMS_BIND_WAY_OUT)
			way = "OUT:";
		else if (bind_way == ND_PARAMS_BIND_WAY_BOTH)
			way = "IN/OUT:";

		//fprintf(outf, "  type=%hhu bind_type=%hhu bind_way=%hhu\n",
		//	type, bind_type, bind_way);

		if (bind_type == ND_PARAMS_BIND_TYPE_NAME) {
			uint8_t name_len = decode8(d, i);
			char name[name_len * 4 + 1];
			sf_tools_bin2hex_ascii(name, d + *i, name_len); *i = *i + name_len;
			snprintf(head, sizeof(head), "%s'%s'", way, name);
		} else if (bind_type == ND_PARAMS_BIND_TYPE_POS) {
			snprintf(head, sizeof(head), "%s%hu", way, decode16(d, i));
		} else {
			snprintf(head, sizeof(head), "%s%hu", way, j);
		}

		if (type == ND_TYPE_NULL) { // NULL
			snprintf(value, sizeof(value),
				"%s:NULL", head);
		} else if (type == ND_TYPE_INT) {
			snprintf(value, sizeof(value),
				"%s:int:%d", head, decode32(d, i));
		} else if (type == ND_TYPE_LONG) {
			snprintf(value, sizeof(value),
				"%s:long:%ld", head, decode64(d, i));
		} else if (type == ND_TYPE_DOUBLE) {
			double dd;
			uint64_t u = decode64(d, i);
			memcpy(&dd, &u, 8);
			snprintf(value, sizeof(value),
				"%s:double:%f", head, dd);
		} else if ((type == ND_TYPE_STRING) || (type == ND_TYPE_UNK)) {
			uint16_t len = decode16(d, i);
			char s[len * 4 + 1];
			sf_tools_bin2hex_ascii(s, d + *i, len); *i = *i + len;
			snprintf(value, sizeof(value),
				"%s:str:'%s'", head, s);
		} else {
			snprintf(value, sizeof(value),
				"%s:unk, ...", head);
			do_break = 1; // we cannot continue
		}

		len = 2 + strlen(value);
		if (len > rest)
			break;

		if (filter_bind_way & bind_way) {
			strcat(out, add);
			strcat(out, value);
			add = ", ";
			rest -= len;
		}

		if (do_break)
			break;

		j++;
	}

	strcat(out, "}");
}

static int decode_h_errno(char *out, const size_t out_size, unsigned char *d, unsigned int *i)
{
	int ret = decode32(d, i);

	switch (ret) {
	case HOST_NOT_FOUND: snprintf(out, out_size, "HOST_NOT_FOUND"); break;
	case NO_DATA: snprintf(out, out_size, "NO_DATA"); break;
	case NO_RECOVERY: snprintf(out, out_size, "NO_RECOVERY"); break;
	case TRY_AGAIN: snprintf(out, out_size, "TRY_AGAIN"); break;
	default: snprintf(out, out_size, "todo(%d)", ret);
	}

	return ret;
}

static void decode_hostent(char *out, const size_t out_size, unsigned char *d, unsigned int *i)
{
	uint8_t len = decode8(d, i);
	char name[len * 4 + 1];
	sf_tools_bin2hex_ascii(name, d + *i, len); *i = *i + len;

	char aliases[512] = {0}; char *add = "";
	for (int j = 0; ; j++) {
		uint8_t alen = decode8(d, i);
		if (alen == 0)
			break;

		char alias[alen * 4 + 1];
		sf_tools_bin2hex_ascii(alias, d + *i, alen); *i = *i + alen;
		strcat(aliases, add); add = ",";
		strcat(aliases, alias);
	}

	uint8_t addrtype = decode8(d, i);
	uint8_t h_length = decode8(d, i);
	uint8_t count = decode8(d, i);
	char addrs[512] = {0}; add = "";
	for (int j = 0; j < count; j++) {
		char addr[40];
		const char *r = inet_ntop(addrtype, d + *i, addr, sizeof(addr));
		if (!r)
			sf_tools_bin2hex(addr, d + *i, h_length);
		*i = *i + h_length;
		strcat(addrs, add); add = ", ";
		strcat(addrs, addr);
	}

	snprintf(out, out_size, " => name=[%s] aliases={%s} addrs={%s}",
		name, aliases, addrs);
}

static int decode_whence(char *out0, const size_t out_size, unsigned char *d, unsigned int *i)
{
	int ret = decode32(d, i);

	char out[out_size];
	switch (ret) {
	case SEEK_SET:	snprintf(out, out_size, "SEEK_SET"); break;
	case SEEK_CUR:	snprintf(out, out_size, "SEEK_CUR"); break;
	case SEEK_END:	snprintf(out, out_size, "SEEK_END"); break;
	case SEEK_DATA:	snprintf(out, out_size, "SEEK_DATA"); break;
	case SEEK_HOLE:	snprintf(out, out_size, "SEEK_HOLE"); break;
	default: snprintf(out, out_size, "todo(%d)", ret);
	}

	nd_trace_color(out0, out_size, out, "flags", '-', "", 0);
	return ret;
}

// TODO: we need to check if out is large enough
static void decode_types(char *out, const size_t out_size,
	unsigned char *buf, unsigned int *i)
{
	unsigned int off = 0;
	uint16_t u16;
	int64_t d64;
	uint64_t u64;
	char *add = "";
	unsigned char depth = 0;
	char tmp[4096];

	do {
		unsigned char type = buf[*i]; *i = *i + 1;
		switch (type) {
		case ND_TYPE_ARRAY_START:
			strcat(out + off, add); off += strlen(add);
			out[off++] = '{';
			depth++;
			add = "";
			break;

		case ND_TYPE_ARRAY_END:
			out[off++] = '}';
			depth--;
			add = ", ";
			break;

		case ND_TYPE_STRING:
			memcpy(&u16, buf + *i, 2); *i = *i + 2;
			u16 = be16toh(u16);
			sf_tools_bin2hex_ascii(tmp, buf + *i, u16); *i = *i + u16;
			off += sprintf(out + off, "%s'%s'", add, tmp);
			add = ", ";
			break;

		case ND_TYPE_TRUE:
			off += sprintf(out + off, "%strue", add);
			add = ", ";
			break;

		case ND_TYPE_FALSE:
			off += sprintf(out + off, "%sfalse", add);
			add = ", ";
			break;

		case ND_TYPE_NULL:
			off += sprintf(out + off, "%snull", add);
			add = ", ";
			break;

		case ND_TYPE_UNK:
			off += sprintf(out + off, "%s?", add);
			add = ", ";
			break;

		case ND_TYPE_LONG:
			memcpy(&d64, buf + *i, 8); *i = *i + 8;
			d64 = be64toh(d64);
			off += sprintf(out + off, "%s%ld", add, d64);
			add = ", ";
			break;

		case ND_TYPE_DOUBLE:
			memcpy(&d64, buf + *i, 8); *i = *i + 8;
			d64 = be64toh(d64);
			double d;
			memcpy(&d, &d64, 8);
			off += sprintf(out + off, "%s%f", add, d);
			add = ", ";
			break;

		case ND_TYPE_POINTER:
			memcpy(&u64, buf + *i, 8); *i = *i + 8;
			u64 = be64toh(u64);
			off += sprintf(out + off, "%s0x%lx", add, u64);
			add = ", ";
			break;

		}
	} while (depth > 0);
}

static uint8_t decode_string8(unsigned char *out, unsigned char *d, unsigned int *i)
{
	uint8_t len = decode8(d, i);
	memcpy(out, d + *i, len); *i = *i + len;
	return len;
}

// TODO: use this!
static void decode_ascii_string8(char *out, unsigned char *d, unsigned int *i)
{
	uint8_t len = decode8(d, i);
	sf_tools_bin2hex_ascii(out, d + *i, len); *i = *i + len;
}

static void decode_filename(char *out, unsigned char *d, unsigned int *i)
{
	char tmp[512];
	decode_ascii_string8(tmp, d, i);
	nd_trace_color(out, 512, tmp, "file", '-', "", 0);
}

// TODO: decode type
// TODO: add color for the file name
static void decode_dirent(char *out, const size_t out_size,
	unsigned char *d, unsigned int *i)
{
	uint8_t we_have_data = decode8(d, i);
	if (we_have_data == 0) {
		int xerrno = decode32(d, i);
		char err[64];
		decode_errno(err, sizeof(err), xerrno);
		if (xerrno == 0)
			snprintf(out, out_size, " - no more objects");
		else
			snprintf(out, out_size, " = %d (%s)", xerrno, err);
		return;
	}

	struct dirent e;
	e.d_ino = decode64(d, i);
	e.d_off = decode64(d, i);
	e.d_type = decode8(d, i);
	char name[512];
	decode_filename(name, d, i);
	snprintf(out, out_size, " => %s {ino=%lu off=%lu type=%hhu}",
		name, e.d_ino, e.d_off, e.d_type);
}

// 2400000018000106443322118877665502200012fa00ff01000000000800010008000500
static void decode_msg_nl_rt(char *out, const size_t out_size,
	unsigned char *buf, const unsigned int buf_size)
{
	struct rtmsg *r = (struct rtmsg *) buf;
	unsigned int rest;
	size_t off = 0;

	char dom[32], protocol[32], scope[32], type[32], flags[128];
	nd_decode_socket_domain(dom, sizeof(dom), r->rtm_family);
	nd_decode_route_protocol(protocol, sizeof(protocol), r->rtm_protocol);
	nd_decode_route_scope(scope, sizeof(scope), r->rtm_scope);
	nd_decode_route_type(type, sizeof(type), r->rtm_type);
	nd_decode_route_flags(flags, sizeof(flags), r->rtm_flags);

	nd_str_append(out, out_size, &off,
		"family=%s dst_len=%hhu src_len=%hhu tos=0x%hhx"
		" table=%hhu protocol=%s scope=%s type=%s flags=%s",
		dom, r->rtm_dst_len, r->rtm_src_len, r->rtm_tos,
		r->rtm_table, protocol, scope, type, flags);

	struct rtattr *rta = (struct rtattr *) (buf + NLMSG_ALIGN(sizeof(struct rtmsg)));
	rest = buf_size - NLMSG_ALIGN(sizeof(struct rtmsg));
	while (1) {
		if (!RTA_OK(rta, rest))
			break;

		char dump[512];
		nd_decode_rta(dump, sizeof(dump), rta->rta_type, r->rtm_family,
			RTA_DATA(rta), RTA_PAYLOAD(rta));
		nd_str_append(out, out_size, &off, "%s", dump);
		rta = RTA_NEXT(rta, rest);
	}

	if (rest != 0) {
		char dump[rest * 4 + 1];
		sf_tools_bin2hex(dump, rta, rest);
		nd_str_append(out, out_size, &off, " junk=%s", dump);
	}
}

static void decode_msg_nl(char *out, const size_t out_size,
	unsigned char *buf, const unsigned int buf_size)
{
	struct nlmsghdr *n = (struct nlmsghdr *) buf;
	char stype[32], sflags[128];

	uint8_t req_type;
	nd_decode_nlmsg_type(stype, sizeof(stype), n->nlmsg_type, &req_type);
	nd_decode_nlmsg_flags(sflags, sizeof(sflags), n->nlmsg_flags, req_type);

	char decoded[buf_size * 10 + 1];
	unsigned char *buf2 = buf + NLMSG_LENGTH(0);
	const unsigned int buf2_size = buf_size - NLMSG_LENGTH(0);
	if (n->nlmsg_type == RTM_NEWROUTE)
		decode_msg_nl_rt(decoded, sizeof(decoded), buf2, buf2_size);
	else
		sf_tools_bin2hex(decoded, buf2, buf2_size);

	snprintf(out, out_size, "nl={len=%u, type=%s flags=%s seq=%u pid=%u [%s]}",
		n->nlmsg_len, stype, sflags, n->nlmsg_seq, n->nlmsg_pid, decoded);
}

static void decode_msg(char *out, const size_t out_size, const uint16_t family,
	unsigned char *buf, const unsigned int buf_size)
{
	if (family == AF_NETLINK) {
		decode_msg_nl(out, out_size, buf, buf_size);
	} else {
		char dump[buf_size * 4 + 1];
		sf_tools_bin2hex(dump, buf, buf_size);
		snprintf(out, out_size, "?%hu?={%s}", family, dump);
	}
}

static void decode_msghdr(char *out, const size_t out_size,
	unsigned char *d, unsigned int *i)
{
	char *add = "";
	size_t len = 0;
	uint16_t family = 0;

	nd_str_append(out, out_size, &len, "{");

	unsigned char name[256];
	uint8_t name_len = decode_string8(name, d, i);
	if (name_len) {
		struct sockaddr *s = (struct sockaddr *) name;
		family = s->sa_family;
		char sname[name_len * 4 + 1];
		sf_tools_bin2hex_ascii(sname, name, name_len);
		nd_str_append(out, out_size, &len, "name=[%s] ", sname);
	}

	unsigned char control[256];
	uint8_t control_len = decode_string8(control, d, i);
	if (control_len) {
		char scontrol[control_len * 4 + 1];
		sf_tools_bin2hex_ascii(scontrol, control, control_len);
		nd_str_append(out, out_size, &len, "control=[%s] ", control);
	}

	int msg_flags = decode32(d, i);

	size_t iovlen = decode64(d, i);
	nd_str_append(out, out_size, &len, "flags=0x%x iovlen=%zu",
		msg_flags, iovlen);

	uint16_t real_iovlen = decode16(d, i);

	if (iovlen > 0) {
		nd_str_append(out, out_size, &len, ": ");
		for (uint16_t j = 0; j < real_iovlen; j++) {
			size_t msg_iov_len = decode64(d, i);
			uint16_t max = decode16(d, i);
			char decoded[4096] = { 0 };
			if (max > 0) {
				decode_msg(decoded, sizeof(decoded), family, d + *i, max);
				*i = *i + max;
			}
			nd_str_append(out, out_size, &len,
				"%s" "{iov %hu: len=%zu: %s}",
				add, j, msg_iov_len, decoded);
			add = ", ";
		}
	}

	nd_str_append(out, out_size, &len, "}");
}

static void decode_func(const uint32_t parent, unsigned char *d)
{
	unsigned char nothing_to_log = 0;
	unsigned int i = 0;
	unsigned short func_len, trace_depth;
	char type, line[64000], line2[64000], rest[4000];
	uint64_t t;

	line[0] = '\0';
	line2[0] = '\0';
	rest[0] = '\0';

	t = decode64(d, &i);
	uint8_t tf_len = decode8(d, &i);
	char tf[tf_len + 1];
	memcpy(tf, d + i, tf_len); i += tf_len;
	tf[tf_len] = '\0';
	trace_depth = decode16(d, &i);
	func_len = decode16(d, &i);
	char func[func_len * 4 + 1];
	sf_tools_bin2hex_ascii(func, d + i, func_len); i += func_len;
	type = d[i++];
	uint32_t pid = decode32(d, &i);
	uint32_t tid = decode32(d, &i);
	//fprintf(outf, "%s: t=%lu func_len=%hu func=[%s] tf=[%s] type=%c pid=%u tid=%u\n",
	//	__func__, t, func_len, func, tf, type, pid, tid);

	if (only_high_level && !strstr(tf, "i"))
		return;

	switch (func[0]) {
	case '-': // project specific
		if (strcmp(func, "-stop") == 0) {
			int ret = decode32(d, &i);
			sprintf(rest, "exit code = %d", ret);
			char color[64];
			nd_trace_color(color, sizeof(color), rest, "error", '-', "", ret);
			sprintf(line, ": %s", color);
			if (pid == parent)
				do_exit++;
		} else if (strcmp(func, "-segv") == 0) {
			uint8_t regs = decode8(d, &i);
			strcat(line, ": regs:");
			for (uint8_t j = 0; j < regs; j++) {
				char l[64], name[32];
				nd_decode_register(name, sizeof(name), j);
				snprintf(l, sizeof(l), " %s=0x%016lx",
					name, decode64(d, &i));
				strcat(line, l);
			}
			int r = decode32(d, &i);
			strcat(line, "; bt:");
			for (int j = 0; j < r; j++) {
				unsigned char len = decode8(d, &i);
				char l[len * 4 + 1];
				sf_tools_bin2hex_ascii(l, d + i, len); i += len;
				strcat(line, " ");
				strcat(line, l);
			}
			if (pid == parent)
				do_exit++;
		} else if (strcmp(func, "-msgs_lost") == 0) {
			sprintf(rest, "ninedogs: messages lost: %u", decode32(d, &i));
			nd_trace_color(line, sizeof(line), rest, "error", '-', "", 1);
		} break;

	case 'a':
		if (strcmp(func, "accept") == 0) {
			int fd = decode32(d, &i);
			int addrlen = decode32(d, &i);
			if (type == 'c') {
				sprintf(line, "(%d, sockaddr, %u)%s",
					fd, addrlen, rest);
			} else {
				char sa[256];
				nd_decode_sockaddr(sa, d + i, addrlen); i += addrlen;
				decode_ret_int(rest, sizeof(rest), d, &i);
				sprintf(line, "(%d, %s, %u)%s",
					fd, sa, addrlen, rest);
			}
		} else if (strcmp(func, "accept4") == 0) {
			int fd = decode32(d, &i);
			int addrlen = decode32(d, &i);
			int flags = decode32(d, &i);
			if (type == 'c') {
				sprintf(line, "(%d, sockaddr, %u, 0x%x)",
					fd, addrlen, flags);
			} else {
				char sa[256];
				nd_decode_sockaddr(sa, d + i, addrlen); i += addrlen;
				decode_ret_int(rest, sizeof(rest), d, &i);
				sprintf(line, "(%d, %s, %u, 0x%x)%s",
					fd, sa, addrlen, flags, rest);
			}
		} break;

	case 'b':
		if (strcmp(func, "bind") == 0) {
			int sock = decode32(d, &i);
			char dump[128];
			socklen_t sl = decode_sockaddr(dump, d, &i);
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%d, %s, %d)%s", sock, dump, sl, rest);
		} break;

	case 'c':
		if (strcmp(func, "close") == 0) {
			int fd = decode32(d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%d)%s", fd, rest);
		} else if (strcmp(func, "closedir") == 0) {
			uint64_t dir = decode64(d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(0x%lx)%s", dir, rest);
		} else if (strcmp(func, "connect") == 0) {
			int sock = decode32(d, &i);
			if (type == 'c') {
				char dump[128];
				socklen_t sl = decode_sockaddr(dump, d, &i);
				sprintf(line, "(%d, %s, %d)", sock, dump, sl);
			} else if (type == 'r') {
				decode_ret_int(rest, sizeof(rest), d, &i);
				sprintf(line, "(%d)%s", sock, rest);
			}
		} else if (strcmp(func, "curl_easy_cleanup") == 0) {
			uint64_t handle = decode64(d, &i);
			sprintf(line, "(0x%lx)", handle);
		} else if (strcmp(func, "curl_init") == 0) { // php
			uint64_t handle = decode64(d, &i);
			uint16_t len = decode16(d, &i);
			if (len == 0) {
				sprintf(line, "() = 0x%lx", handle);
			} else {
				char url[len * 4 + 1];
				sf_tools_bin2hex_ascii(url, d + i, len); i += len;
				sprintf(line, "('%s') = 0x%lx", url, handle);
			}
		} else if (strcmp(func, "curl_setopt") == 0) { // php
			uint64_t handle = decode64(d, &i);
			uint8_t oplen = decode8(d, &i);
			char op[oplen * 4 + 1];
			sf_tools_bin2hex_ascii(op, d + i, oplen); i += oplen;
			char types[4096], types2[4096];
			decode_types(types, sizeof(types), d, &i);
			nd_trace_color(types2, sizeof(types2), types, "flags", type, tf, 0);
			if (type == 'r') {
				uint8_t ret = decode8(d, &i);   // TODO: decode error
				snprintf(rest, sizeof(rest), " = %s", ret == 1 ? "ok" : "nok");
			}
			sprintf(line, "(0x%lx, %s, %s)%s", handle, op, types2, rest);
		} else if (strcmp(func, "curl_exec") == 0) { // php
			uint64_t handle = decode64(d, &i);
			if (type == 'r') {
				uint8_t ret = decode8(d, &i);
				if (ret == 2) {
					uint16_t sret_len = decode16(d, &i);
					char sret[sret_len * 4 + 1];
					sf_tools_bin2hex_ascii(sret, d + i, sret_len); i += sret_len;
					snprintf(rest, sizeof(rest), " = '%s'", sret);
				} else if (ret == 0) {
					snprintf(rest, sizeof(rest), " = nok");
				} else {
					snprintf(rest, sizeof(rest), " = ok");
				}
			}
			sprintf(line, "(0x%lx)%s", handle, rest);
		} else if (strcmp(func, "curl_easy_init") == 0) {
			uint64_t handle = decode64(d, &i);
			sprintf(line, "() = 0x%lx", handle);
		} else if (strcmp(func, "curl_easy_perform") == 0) {
			if (type == 'm') {
				uint8_t len = decode8(d, &i);
				char info[len * 4 + 1], info2[len * 4 + 1];
				sf_tools_bin2hex_ascii(info, d + i, len); i += len;
				nd_trace_color(info2, sizeof(info2), info, "flags", type, tf, 0);
				uint64_t handle = decode64(d, &i);
				uint16_t post_len = decode16(d, &i);
				char post[post_len * 4 + 1];
				sf_tools_bin2hex_ascii(post, d + i, post_len); i += post_len;
				sprintf(line, "(0x%lx): meta: %s: %s", handle, info2, post);
			} else {
				uint64_t handle = decode64(d, &i);
				if (type == 'r') {
					int ret = decode32(d, &i);
					nd_decode_curl_code(rest, sizeof(rest), ret);
					char sret[sizeof(rest)];
					nd_trace_color(sret, sizeof(sret), rest,
						"error", type, tf, ret == 0 ? 0 : 1);
					sprintf(line, "(0x%lx) = %s", handle, sret);
				} else {
					sprintf(line, "(0x%lx)", handle);
				}
			}
		} else if (strcmp(func, "curl_easy_setopt") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t handle = decode64(d, &i);
			uint8_t type = decode8(d, &i);
			uint8_t option_len = decode8(d, &i);
			char option[option_len * 4 + 1], option2[option_len * 4 + 32];
			sf_tools_bin2hex_ascii(option, d + i, option_len); i += option_len;
			nd_trace_color(option2, sizeof(option2), option, "flags", type, tf, 0);
			if ((type == 0) || (type == 2)) { // long or off_t
				snprintf(rest, sizeof(rest), "%ld", decode64(d, &i));
			} else if ((type == 3) || (type == 4)) { // object or string
				long len = decode64(d, &i);
				if (len >= 0) {
					uint16_t s_len = decode16(d, &i);
					char s[s_len * 4 + 1];
					sf_tools_bin2hex_ascii(s, d + i, len); i += s_len;
					snprintf(rest, sizeof(rest), "'%s'", s);
				} else {
					snprintf(rest, sizeof(rest),
						"[cannot dump because size was not set yet]");
				}
			} else if (type == 5) { // slist
				decode_string_array(rest, sizeof(rest), d, &i);
			} else {
				snprintf(rest, sizeof(rest), "%p", (void *) decode64(d, &i));
			}
			int ret = decode32(d, &i);
			char sret[32], sret2[64];
			nd_decode_curl_code(sret, sizeof(sret), ret);
			nd_trace_color(sret2, sizeof(sret2), sret,
				"error", type, tf, ret == 0 ? 0 : 1);
			sprintf(line, "(0x%lx, %s, %s) = %s",
				handle, option2, rest, sret2);
		} break;

	case 'd':
		if (strcmp(func, "dlopen") == 0) {
			char filename[512];
			decode_filename(filename, d, &i);
			char flags[128];
			decode_dlopen_flags(flags, sizeof(flags), d, &i);
			if (type == 'r')
				decode_ret_pointer_errno(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s', '%s')%s", filename, flags, rest);
		} else if (strcmp(func, "dlclose") == 0) {
			void *h = (void *) decode64(d, &i);
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%p)%s", h, rest);
		} break;

	case 'e':
		if (strcmp(func, "execve") == 0) {
			char argv[4096], envp[4096];
			char pathname[512];
			decode_filename(pathname, d, &i);
			decode_string_array(argv, sizeof(argv), d, &i);
			decode_string_array(envp, sizeof(envp), d, &i);
			if (type == 'r')
				decode_ret_pointer_errno(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s', %s, %s)%s",
				pathname, argv, envp, rest);
			// special action here. We need to switch to the new pid
			// TODO: close previous one?
			if (no_pids < 256) {
				pids[no_pids] = pid;
				no_pids++;
			}
		} break;

	case 'f':
		if (strcmp(func, "fork") == 0) {
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "()%s", rest);
		} else if ((strcmp(func, "fstatat") == 0)
			|| (strcmp(func, "fstat64") == 0)) {
			char dirfd[32], sstat[512];
			decode_dirfd(dirfd, sizeof(dirfd), d, &i, "");
			char pathname[512];
			decode_filename(pathname, d, &i);
			decode_stat(sstat, sizeof(sstat), d, &i);
			int flags = decode32(d, &i);
			if (type == 'r')
				decode_ret_pointer_errno(rest, sizeof(rest), d, &i);
			sprintf(line, "(%s, '%s', {%s}, 0x%x)%s",
				dirfd, pathname, sstat, flags, rest);
		} else if ((strcmp(func, "fsync") == 0)
			|| (strcmp(func, "fdatasync") == 0)) {
			int fd = decode32(d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%d)%s", fd, rest);
		} else if (strcmp(func, "ftruncate") == 0) {
			int fd = decode32(d, &i);
			uint64_t len = decode64(d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(fd=%d, len=%lu)%s", fd, len, rest);
		} break;

	case 'g':
		if (strcmp(func, "getaddrinfo") == 0) {
			uint16_t len;
			char node[256];
			char service[256];

			len = decode16(d, &i);
			if (len == 0xFFFF) {
				sprintf(node, "NULL");
			} else {
				memcpy(node, d + i, len); i += len;
				node[len] = '\0';
			}

			len = decode16(d, &i);
			if (len == 0xFFFF) {
				sprintf(service, "NULL");
			} else {
				memcpy(service, d + i, len); i += len;
				service[0] = '\0';
			}

			if (type == 'r') {
				int ret = decode32(d, &i);
				sprintf(rest, " = %d%s%s%s",
					ret, ret > 0 ? " (" : "",
					ret > 0 ? gai_strerror(ret) : "",
					ret > 0 ? ")" : "");
			}
			sprintf(line, "('%s', '%s')%s",
				node, service, rest);
		} else if (strcmp(func, "gethostbyname") == 0) {
			uint16_t len = decode16(d, &i);
			char host[len * 4 + 1];
			sf_tools_bin2hex_ascii(host, d + i, len); i += len;
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s')%s", host, rest);
		} else if (strcmp(func, "gethostbyname_r") == 0) {
			uint16_t len = decode16(d, &i);
			char host[len * 4 + 1];
			sf_tools_bin2hex_ascii(host, d + i, len); i += len;
			if (type == 'r') {
				int ret = decode32(d, &i);
				if (ret == -1)
					decode_h_errno(rest, sizeof(rest), d, &i);
				else
					decode_hostent(rest, sizeof(rest), d, &i);
			}
			sprintf(line, "('%s')%s", host, rest);
		} else if (strcmp(func, "getrandom") == 0) {
			size_t buflen = decode64(d, &i);
			unsigned int flags = decode32(d, &i);
 			if (type == 'c') {
				sprintf(line, "(buf, %zu, 0x%x)", buflen, flags);
			} else {
				ssize_t ret = decode_ret_int64(rest, sizeof(rest), d, &i);
				if (ret != -1) {
					unsigned short max = decode16(d, &i);
					char dump[max * 2 + 1];
					sf_tools_bin2hex(dump, d + i, max); i += max;
					sprintf(line, "('%s'%s, %zu, 0x%x)%s",
						dump, max < buflen ? "..." : "",
						buflen, flags, rest);
				} else {
					sprintf(line, "(buf, %zu, 0x%x)%s",
						buflen, flags, rest);
				}
			}
		} else if (strcmp(func, "getsockopt") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			int sock = decode32(d, &i);
			char level[32], optname[32];
			int ilevel = decode_sock_level(level, sizeof(level), d, &i);
			decode_sock_optname(optname, sizeof(optname), ilevel, d, &i);
			socklen_t optlen = decode32(d, &i);
			char optval[optlen * 2 + 1];
			sf_tools_bin2hex(optval, d + i, optlen); i+= optlen;
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%d, %s, %s, 0x%s, %u)%s",
				sock, level, optname, optval, optlen, rest);
		} break;

	case 'i':
		if ((strcmp(func, "imagecreate") == 0)
			|| (strcmp(func, "imagecreatetruecolor") == 0)) {
			uint64_t w = decode64(d, &i);
			uint64_t h = decode64(d, &i);
			decode_ret_pointer(rest, sizeof(rest), d, &i);
			sprintf(line, "(w=%lu, h=%lu)%s", w, h, rest);
		} else if (strcmp(func, "imagedestroy") == 0) {
			uint64_t im = decode64(d, &i);
			uint64_t elap_us = decode64(d, &i);
			decode_bool("", rest, sizeof(rest), d, &i);
			char elap[64], elap2[64];
			snprintf(elap, sizeof(elap), "[image generation time: %luus]", elap_us);
			nd_trace_color(elap2, sizeof(elap2), elap, "ts", type, tf, elap_us / 1000);
			sprintf(line, "(0x%lx) = %s %s", im, rest, elap2);
		}
		break;

	case 'j':
		if (strcmp(func, "java/db/connect") == 0) {
			char db_type = decode8(d, &i);
			uint16_t cs_len = decode16(d, &i);
			char cs[cs_len * 4 + 1];
			sf_tools_bin2hex_ascii(cs, d + i, cs_len); i += cs_len;
			if (type == 'r') {
				uint64_t dbh = decode64(d, &i);
				if (dbh != 0) {
					snprintf(rest, sizeof(rest), " = 0x%lx", dbh);
				} else {
					uint16_t ex_len = decode16(d, &i);
					char ex[ex_len * 4 + 1];
					sf_tools_bin2hex_ascii(ex, d + i, ex_len); i += ex_len;
					snprintf(rest, sizeof(rest), " = nok [%s]", ex);
				}
			}
			sprintf(line, "/%c('%s')%s", db_type, cs, rest);
		} else if (strcmp(func, "java/db/close") == 0) {
			char db_type = decode8(d, &i);
			uint64_t dbh = decode64(d, &i);
			sprintf(line, "/%c(0x%lx) = ok", db_type, dbh);
		} else if ((strcmp(func, "java/db/prepared/execute") == 0)
			|| (strcmp(func, "java/db/stmt/execute") == 0)) {
			char db_type = decode8(d, &i);
			uint64_t stmt = decode64(d, &i);
			if (type == 'r') {
				uint64_t res = decode64(d, &i);
				if (res != 0) {
					snprintf(rest, sizeof(rest), " = ok");
				} else {
					uint16_t ex_len = decode16(d, &i);
					char ex[ex_len * 4 + 1];
					sf_tools_bin2hex_ascii(ex, d + i, ex_len); i += ex_len;
					snprintf(rest, sizeof(rest), " = nok [%s]", ex);
				}
			}
			sprintf(line, "/%c(0x%lx)%s", db_type, stmt, rest);
		} else if (strcmp(func, "java/db/prepare") == 0) {
			char db_type = decode8(d, &i);
			uint64_t dbh = decode64(d, &i);
			uint16_t q_len = decode16(d, &i);
			char q[q_len * 4 + 1];
			sf_tools_bin2hex_ascii(q, d + i, q_len); i += q_len;
			if (type == 'r') {
				uint64_t stmt = decode64(d, &i);
				if (stmt != 0) {
					snprintf(rest, sizeof(rest), " = 0x%lx", stmt);
				} else {
					uint16_t ex_len = decode16(d, &i);
					char ex[ex_len * 4 + 1];
					sf_tools_bin2hex_ascii(ex, d + i, ex_len); i += ex_len;
					snprintf(rest, sizeof(rest), " = nok [%s]", ex);
				}
			}
			sprintf(line, "/%c(0x%lx, '%s')%s", db_type, dbh, q, rest);
		} else if (strcmp(func, "java/db/stmt/create") == 0) {
			char db_type = decode8(d, &i);
			uint64_t dbh = decode64(d, &i);
			uint64_t stmt = decode64(d, &i);
			if (stmt != 0) {
				snprintf(rest, sizeof(rest), " = 0x%lx", stmt);
			} else {
				uint16_t ex_len = decode16(d, &i);
				char ex[ex_len * 4 + 1];
				sf_tools_bin2hex_ascii(ex, d + i, ex_len); i += ex_len;
				snprintf(rest, sizeof(rest), " = nok [%s]", ex);
			}
			sprintf(line, "/%c(0x%lx)%s", db_type, dbh, rest);
		} else if (strcmp(func, "java/db/stmt/close") == 0) {
			char db_type = decode8(d, &i);
			uint64_t stmt = decode64(d, &i);
			sprintf(line, "/%c(0x%lx) = ok", db_type, stmt);
		} else if (strcmp(func, "java/db/stmt/execute/sql") == 0) {
			char db_type = decode8(d, &i);
			uint64_t stmt = decode64(d, &i);
			uint16_t q_len = decode16(d, &i);
			char q[q_len * 4 + 1];
			sf_tools_bin2hex_ascii(q, d + i, q_len); i += q_len;
			if (type == 'r') {
				uint64_t res = decode64(d, &i);
				if (res != 0) {
					snprintf(rest, sizeof(rest), " = 0x%lx", res);
				} else {
					uint16_t ex_len = decode16(d, &i);
					char ex[ex_len * 4 + 1];
					sf_tools_bin2hex_ascii(ex, d + i, ex_len); i += ex_len;
					snprintf(rest, sizeof(rest), " = nok [%s]", ex);
				}
			}
			sprintf(line, "/%c(0x%lx, '%s')%s", db_type, stmt, q, rest);
		} else if (strcmp(func, "java/db/result/get") == 0) {
			char db_type = decode8(d, &i);
			uint64_t stmt = decode64(d, &i);
			if (type == 'r') {
				uint64_t res = decode64(d, &i);
				if (res != 0) {
					snprintf(rest, sizeof(rest), " = 0x%lx", res);
				} else {
					uint16_t ex_len = decode16(d, &i);
					char ex[ex_len * 4 + 1];
					sf_tools_bin2hex_ascii(ex, d + i, ex_len); i += ex_len;
					snprintf(rest, sizeof(rest), " = nok [%s]", ex);
				}
			}
			sprintf(line, "/%c(0x%lx)%s", db_type, stmt, rest);
		} else if (strcmp(func, "java/db/stmt/setInt") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			char db_type = decode8(d, &i);
			uint64_t stmt = decode64(d, &i);
			uint8_t index = decode8(d, &i);
			int32_t value = decode32(d, &i);
			uint64_t res = decode64(d, &i);
			if (res != 0) {
				snprintf(rest, sizeof(rest), " = ok");
			} else {
				uint16_t ex_len = decode16(d, &i);
				char ex[ex_len * 4 + 1];
				sf_tools_bin2hex_ascii(ex, d + i, ex_len); i += ex_len;
				snprintf(rest, sizeof(rest), " = nok [%s]", ex);
			}
			sprintf(line, "/%c(0x%lx, %hhu, %d)%s",
				db_type, stmt, index, value, rest);
		} else if (strcmp(func, "java/db/stmt/setString") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			char db_type = decode8(d, &i);
			uint64_t stmt = decode64(d, &i);
			uint8_t index = decode8(d, &i);
			uint16_t vlen = decode16(d, &i);
			char value[vlen * 4 + 1];
			sf_tools_bin2hex_ascii(value, d + i, vlen); i += vlen;
			uint64_t res = decode64(d, &i);
			if (res != 0) {
				snprintf(rest, sizeof(rest), " = ok");
			} else {
				uint16_t ex_len = decode16(d, &i);
				char ex[ex_len * 4 + 1];
				sf_tools_bin2hex_ascii(ex, d + i, ex_len); i += ex_len;
				snprintf(rest, sizeof(rest), " = nok [%s]", ex);
			}
			sprintf(line, "/%c(0x%lx, %hhu, '%s')%s",
				db_type, stmt, index, value, rest);
		} break;

	case 'l':
		if (strcmp(func, "link") == 0) {
			char oldpath[512], newpath[512];
			decode_filename(oldpath, d, &i);
			decode_filename(newpath, d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s', '%s')%s", oldpath, newpath, rest);
		} else if (strcmp(func, "listen") == 0) {
			int sock = decode32(d, &i);
			int backlog = decode32(d, &i);
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%d, %d)%s", sock, backlog, rest);
		} else if (strcmp(func, "lseek") == 0) {
			int fd = decode32(d, &i);
			int64_t off = decode64(d, &i);
			char whence[32];
			decode_whence(whence, sizeof(whence), d, &i);
			if (type == 'r')
				decode_ret_int64(rest, sizeof(rest), d, &i);
			sprintf(line, "(%d, %zd, %s)%s", fd, off, whence, rest);
		} break;

	case 'm':
		if (strcmp(func, "memfd_create") == 0) {
			char name[512];
			decode_filename(name, d, &i);
			uint32_t flags = decode32(d, &i);
			char sflags[64];
			nd_decode_memfd_flags(sflags, sizeof(sflags), flags);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s', %s)%s", name, sflags, rest);
		} else if (strcmp(func, "mysqli_autocommit") == 0) {
			uint64_t link = decode64(d, &i);
			uint8_t value = decode8(d, &i);
			if (type == 'r')
				decode_bool(" = ", rest, sizeof(rest), d, &i);
			sprintf(line, "(link=0x%lx, %s)%s",
				link, value == 0 ? "false" : "true", rest);
		} else if (strcmp(func, "mysqli_close") == 0) {
			uint64_t link = decode64(d, &i);
			if (type == 'r')
				decode_bool(" = ", rest, sizeof(rest), d, &i);
			sprintf(line, "(link=0x%lx)%s", link, rest);
		} else if (strcmp(func, "mysqli_connect") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint32_t cs_len = decode32(d, &i);
			char cs[cs_len * 4 + 1];
			sf_tools_bin2hex_ascii(cs, d + i, cs_len); i += cs_len;
			if (type == 'r')
				decode_bool(" = ", rest, sizeof(rest), d, &i);
			sprintf(line, "('%s')%s", cs, rest);
		} else if (strcmp(func, "mysqli_fetch_all") == 0) {
			uint64_t res = decode64(d, &i);
			char mode[64];
			decode_mysqli_mode(mode, sizeof(mode), d ,&i);
			if (type == 'r')
				snprintf(rest, sizeof(rest), " [%lu row(s)]", decode64(d, &i));
			sprintf(line, "(0x%lx, %s)%s", res, mode, rest);
		} else if (strcmp(func, "mysqli_fetch_array") == 0) {
			uint64_t res = decode64(d, &i);
			char mode[64];
			decode_mysqli_mode(mode, sizeof(mode), d ,&i);
			if (type == 'r') {
				uint8_t ret = decode8(d, &i);
				if (ret == 0)
					snprintf(rest, sizeof(rest), " = no row");
				else if (ret == 1)
					snprintf(rest, sizeof(rest), " = nok");
				else if (ret == 2)
					snprintf(rest, sizeof(rest), " = ok");
			}
			sprintf(line, "(0x%lx, %s)%s", res, mode, rest);
		} else if (strcmp(func, "mysqli_free_result") == 0) {
			uint64_t res = decode64(d, &i);
			sprintf(line, "(0x%lx)", res);
		} else if (strcmp(func, "mysqli_prepare") == 0) {
			uint64_t link = decode64(d, &i);
			uint16_t q_len = decode16(d, &i);
			char q[q_len * 4 + 1];
			sf_tools_bin2hex_ascii(q, d + i, q_len); i += q_len;
			if (type == 'r') {
				uint64_t stmt = decode64(d, &i);
				snprintf(rest, sizeof(rest), " = 0x%lx", stmt);
			}
			sprintf(line, "(link=0x%lx, '%s')%s", link, q, rest);
		} else if (strcmp(func, "mysqli_query") == 0) {
			uint64_t link = decode64(d, &i);
			uint16_t q_len = decode16(d, &i);
			char q[q_len * 4 + 1];
			sf_tools_bin2hex_ascii(q, d + i, q_len); i += q_len;
			if (type == 'r') {
				uint64_t res = decode64(d, &i);
				uint64_t rows = decode64(d, &i);
				uint64_t aff = decode64(d, &i);
				if (res == 0)
					snprintf(rest, sizeof(rest), " = nok");
				else if (res == 1)
					snprintf(rest, sizeof(rest), " = ok [%lu rows, %ld aff]",
						rows, aff);
				else
					snprintf(rest, sizeof(rest),
						" = 0x%lx [%lu rows, %ld aff]",
						res, rows, aff);
			}
			sprintf(line, "(link=0x%lx, '%s')%s", link, q, rest);
		} else if (strcmp(func, "mysqli_real_connect") == 0) {
			uint64_t link = decode64(d, &i);
			uint32_t cs_len = decode32(d, &i);
			char cs[cs_len * 4 + 1];
			sf_tools_bin2hex_ascii(cs, d + i, cs_len); i += cs_len;
			char flags[512];
			decode_mysql_real_connect_flags(flags, sizeof(flags), d, &i);
			if (type == 'r')
				decode_bool(" = ", rest, sizeof(rest), d, &i);
			sprintf(line, "(link=0x%lx, '%s', flags=%s)%s", link, cs, flags, rest);
		} else if (strcmp(func, "mysqli_stmt_bind_param") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t stmt = decode64(d, &i);
			unsigned short types_len = decode16(d, &i);
			char types[types_len * 4 + 1];
			sf_tools_bin2hex_ascii(types, d + i, types_len); i += types_len;
			if (type == 'r')
				decode_bool(" = ", rest, sizeof(rest), d, &i);
			sprintf(line, "(stmt=0x%lx, types='%s', ...)%s",
				stmt, types, rest);
		} else if (strcmp(func, "mysqli_stmt_bind_result") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t stmt = decode64(d, &i);
			if (type == 'r')
				decode_bool(" = ", rest, sizeof(rest), d, &i);
			sprintf(line, "(stmt=0x%lx, ...)%s", stmt, rest);
		} else if (strcmp(func, "mysqli_stmt_close") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t stmt = decode64(d, &i);
			if (type == 'r') {
				uint8_t ret = decode8(d, &i);
				snprintf(rest, sizeof(rest), " = %s", ret == 1 ? "ok" : "nok");
			}
			sprintf(line, "(stmt=0x%lx)%s", stmt, rest);
		} else if ((strcmp(func, "mysqli_stmt_execute") == 0)
			|| (strcmp(func, "mysqli_execute") == 0)) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t stmt = decode64(d, &i);
			if (type == 'm') {
				nothing_to_log = 1;
				unsigned char bind_way = decode8(d, &i);
				decode_query_params(rest, sizeof(rest), bind_way, d, &i);
				if (bind_way == ND_PARAMS_BIND_WAY_IN) {
					uint16_t q_len = decode16(d, &i);
					char q[q_len * 4 + 1];
					sf_tools_bin2hex_ascii(q, d + i, q_len); i += q_len;
					sprintf(line, "(stmt=0x%lx): meta: sql: %s", stmt, q);
					if (strcmp(rest, " {}") != 0)
						sprintf(line2, "(stmt=0x%lx): meta: binds:%s", stmt, rest);
				} else {
					if (strcmp(rest, " {}") != 0)
						sprintf(line, "(stmt=0x%lx): meta: binds:%s", stmt, rest);
				}
			} else {
				if (type == 'c') {
					decode_query_params(rest, sizeof(rest),
						ND_PARAMS_BIND_WAY_IN, d, &i);
					sprintf(line, "(stmt=0x%lx%s%s)",
						stmt, rest[0] ? "," : "", rest);
				} else if (type == 'r') {
					uint8_t ret = decode8(d, &i);
					if (ret == 1) {
						uint64_t rows = decode64(d, &i);
						uint64_t aff = decode64(d, &i);
						snprintf(rest, sizeof(rest),
							" = ok [%lu rows, %ld aff]", rows, aff);
					} else {
						snprintf(rest, sizeof(rest), " = nok");
					}
					sprintf(line, "(stmt=0x%lx)%s", stmt, rest);
				}
			}
		} else if (strcmp(func, "mysqli_stmt_fetch") == 0) {
			uint64_t stmt = decode64(d, &i);
			if (type == 'm') {
				nothing_to_log = 1;
				uint8_t bind_way = decode8(d, &i);
				decode_query_params(rest, sizeof(rest), bind_way, d, &i);
				if (strcmp(rest, " {}") != 0)
					sprintf(line, "(stmt=0x%lx): meta: binds:%s", stmt, rest);
			} else {
				if (type == 'r') {
					uint8_t ret = decode8(d, &i);
					if (ret == 0)
						snprintf(rest, sizeof(rest), " = nok");
					else if (ret == 1)
						snprintf(rest, sizeof(rest), " = ok");
					else
						snprintf(rest, sizeof(rest), " = ok [no more rows available]");
				}
				sprintf(line, "(stmt=0x%lx)%s", stmt, rest);
			}
		} else if (strcmp(func, "mysqli_stmt_init") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t dbh = decode64(d, &i);
			if (type == 'r') {
				uint64_t stmt = decode64(d, &i);
				snprintf(rest, sizeof(rest), " = 0x%lx", stmt);
			}
			sprintf(line, "(dbh=0x%lx)%s", dbh, rest);
		} else if (strcmp(func, "mysqli_stmt_prepare") == 0) {
			uint64_t stmt = decode64(d, &i);
			uint16_t q_len = decode16(d, &i);
			char q[q_len * 4 + 1];
			sf_tools_bin2hex_ascii(q, d + i, q_len); i += q_len;
			if (type == 'r')
				decode_bool(" = ", rest, sizeof(rest), d, &i);
			sprintf(line, "(stmt=0x%lx, '%s')%s", stmt, q, rest);
		} else if (strcmp(func, "mount") == 0) {
			char source[512], target[512];
			decode_filename(source, d, &i);
			decode_filename(target, d, &i);
			uint8_t len = decode16(d, &i);
			char fstype[len * 4 + 1];
			sf_tools_bin2hex_ascii(fstype, d + i, len); i += len;
			long flags = decode64(d, &i);
			char sflags[128];
			nd_decode_mount_flags(sflags, sizeof(sflags), flags);
			len = decode16(d, &i);
			char data[len * 4 + 1];
			sf_tools_bin2hex_ascii(data, d + i, len); i += len;
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s', '%s', '%s', %s, '%s')%s",
				source, target, fstype, sflags, data, rest);
		} break;

	case 'n':
		if (strcmp(func, "nanosleep") == 0) {
			struct timespec req;
			req.tv_sec = decode64(d, &i);
			req.tv_nsec = decode64(d, &i);
			if (type == 'c') {
				sprintf(line, "(%lu.%09ld)",
					req.tv_sec, req.tv_nsec);
			} else {
				struct timespec rem;
				rem.tv_sec = decode64(d, &i);
				rem.tv_nsec = decode64(d, &i);
				decode_ret_int(rest, sizeof(rest), d, &i);
				sprintf(line, "(%lu.%09ld, %lu.%09ld)%s",
					req.tv_sec, req.tv_nsec, rem.tv_sec, rem.tv_nsec, rest);
			}
		} else if (strcmp(func, "nice") == 0) {
			int inc = decode32(d, &i);
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(inc=%d)%s", inc, rest);
		} break;

	case 'o':
		if (strcmp(func, "oci_bind_by_name") == 0) {
			uint64_t stmt = decode64(d, &i);
			sprintf(line, "(stmt=0x%lx)", stmt);
		} else if (strcmp(func, "oci_close") == 0) {
			uint64_t h = decode64(d, &i);
			sprintf(line, "(0x%lx)", h);
		} else if (strcmp(func, "oci_execute") == 0) {
			uint64_t stmt = decode64(d, &i);
			uint64_t mode = decode64(d, &i);
			if (type == 'r') {
				uint8_t ret = decode8(d, &i);
				if (ret == 1)
					snprintf(rest, sizeof(rest), " = ok");
				else
					snprintf(rest, sizeof(rest), " = nok");
			}
			char s_mode[128];
			nd_decode_oci_execute_mode(s_mode, sizeof(s_mode), mode);
			sprintf(line, "(stmt=0x%lx, %s)%s", stmt, s_mode, rest);
		} else if (strcmp(func, "oci_parse") == 0) {
			uint64_t dbh = decode64(d, &i);
			uint16_t q_len = decode16(d, &i);
			char q[q_len * 4 + 1];
			sf_tools_bin2hex_ascii(q, d + i, q_len); i += q_len;
			if (type == 'r') {
				uint64_t stmt = decode64(d, &i);
				snprintf(rest, sizeof(rest), " = 0x%lx", stmt);
			}
			sprintf(line, "(dbh=0x%lx, '%s')%s", dbh, q, rest);
		} else if (strcmp(func, "oci_pconnect") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint32_t cs_len = decode32(d, &i);
			char cs[cs_len * 4 + 1];
			sf_tools_bin2hex_ascii(cs, d + i, cs_len); i += cs_len;
			if (type == 'r') {
				uint64_t h = decode64(d, &i);
				snprintf(rest, sizeof(rest), " = 0x%lx", h);
			}
			sprintf(line, "('%s')%s", cs, rest);
		} else if ((strcmp(func, "open") == 0)
			|| (strcmp(func, "open64") == 0)
			|| (strcmp(func, "openat") == 0)) {
			char dirfd[32];
			if (strcmp(func, "openat") == 0)
				decode_dirfd(dirfd, sizeof(dirfd), d, &i, " ,");
			else
				dirfd[0] = '\0';
			char pathname[512];
			decode_filename(pathname, d, &i);
			int flags = decode32(d, &i);
			mode_t mode = decode32(d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%s'%s', 0x%x, 0x%x)%s",
				dirfd, pathname, flags, mode, rest);
		} else if (strcmp(func, "opendir") == 0) {
			char name[512];
			decode_filename(name, d, &i);
			if (type == 'r')
				decode_ret_pointer(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s')%s", name, rest);
		} break;

	case 'O':
		if (strcmp(func, "OCIAttrSet") == 0) {
			uint64_t h = decode64(d, &i);
			uint32_t htype = decode32(d, &i);
			unsigned int attr_size = decode32(d, &i);
			char attr[attr_size * 4 + 1];
			sf_tools_bin2hex_ascii(attr, d + i, attr_size); i += attr_size;
			unsigned int attr_type = decode32(d, &i);
			uint64_t err = decode64(d, &i);
			char s_htype[32];
			nd_decode_oci_handle_alloc_type(s_htype, sizeof(s_htype), htype);
			char s_attr_type[32];
			nd_decode_oci_attr_type(s_attr_type, sizeof(s_attr_type), attr_type);
			int32_t ret = decode32(d, &i);
			sprintf(line, "(h=0x%lx, htype=%s, attr='%s'"
				", attr_type=%s, err=0x%lx) = %d",
				h, s_htype, attr, s_attr_type, err, ret);
		} else if (strcmp(func, "OCIBindByName") == 0) {
			uint64_t stmt = decode64(d, &i);
			uint64_t bind = decode64(d, &i);
			uint64_t oci_error = decode64(d, &i);
			unsigned int ph_len = decode32(d, &i);
			char ph[ph_len * 4 + 1];
			sf_tools_bin2hex_ascii(ph, d + i, ph_len); i += ph_len;
			int value_size = decode32(d, &i);
			uint16_t dty = decode16(d, &i);
			uint64_t ind = decode64(d, &i);
			uint64_t alen = decode64(d, &i);
			uint64_t rcode = decode64(d, &i);
			uint32_t maxarr_len = decode32(d, &i);
			uint64_t curelep = decode64(d, &i);
			uint32_t mode = decode32(d, &i);
			int32_t ret = decode32(d, &i);
			char s_dty[32], /*s_ind[32],*/ s_mode[128], s_ret[32];
			nd_decode_oci_dty(s_dty, sizeof(s_dty), dty);
			//nd_decode_oci_ind(s_ind, sizeof(s_ind), ind);
			nd_decode_oci_bind_mode(s_mode, sizeof(s_mode), mode);
			nd_decode_oci_ret(s_ret, sizeof(s_ret), ret);
			sprintf(line, "(0x%lx, 0x%lx, 0x%lx, '%s', value, value_size=%d"
				", dty=%s, ind=0x%lx, alen=0x%lx, rcode=0x%lx, %u, curelep=0x%lx, mode=%s) = %s",
				stmt, bind, oci_error, ph, value_size,
				s_dty, ind, alen, rcode, maxarr_len, curelep, s_mode, s_ret);
		} else if (strcmp(func, "OCIDefineByPos") == 0) {
			uint64_t stmt = decode64(d, &i);
			uint64_t def = decode64(d, &i);
			uint64_t oci_error = decode64(d, &i);
			uint32_t pos = decode32(d, &i);
			int value_size = decode32(d, &i);
			uint16_t dty = decode16(d, &i);
			uint64_t ind = decode64(d, &i);
			uint64_t rlen = decode64(d, &i);
			uint64_t rcode = decode64(d, &i);
			uint32_t mode = decode32(d, &i);
			int32_t ret = decode32(d, &i);
			char s_dty[32], /*s_ind[32],*/ s_mode[128], s_ret[32];
			nd_decode_oci_dty(s_dty, sizeof(s_dty), dty);
			//nd_decode_oci_ind(s_ind, sizeof(s_ind), ind);
			nd_decode_oci_bind_mode(s_mode, sizeof(s_mode), mode);
			nd_decode_oci_ret(s_ret, sizeof(s_ret), ret);
			sprintf(line, "(0x%lx, 0x%lx, 0x%lx, pos=%u, value, value_size=%d"
				", %s, ind=0x%lx, rlen=0x%lx, rcode=0x%lx, mode=%s) = %s",
				stmt, def, oci_error, pos, value_size,
				s_dty, ind, rlen, rcode, s_mode, s_ret);
		} else if (strcmp(func, "OCIHandleAlloc") == 0) {
			uint64_t parent = decode64(d, &i);
			uint64_t dbh = decode64(d, &i);
			uint32_t type = decode32(d, &i);
			size_t extra_mem = decode64(d, &i);
			uint64_t user_mem = decode64(d, &i);
			char s_type[32];
			nd_decode_oci_handle_alloc_type(s_type, sizeof(s_type), type);
			sprintf(line, "(0x%lx, 0x%lx, %s, %zu, 0x%lx)",
				parent, dbh, s_type, extra_mem, user_mem);
		} else if (strcmp(func, "OCIHandleFree") == 0) {
			uint64_t hnd = decode64(d, &i);
			uint32_t type = decode32(d, &i);
			char s_type[32];
			nd_decode_oci_handle_alloc_type(s_type, sizeof(s_type), type);
			sprintf(line, "(0x%lx, %s)", hnd, s_type);
		} else if (strcmp(func, "OCIServerAttach") == 0) {
			uint64_t oci_server = decode64(d, &i);
			uint64_t oci_error = decode64(d, &i);
			int32_t db_link_len = decode32(d, &i), a;
			a = db_link_len < 0 ? 0 : db_link_len;
			char db_link[a * 4 + 1];
			sf_tools_bin2hex_ascii(db_link, d + i, a); i += a;
			uint32_t mode = decode32(d, &i);
			if (type == 'r')
				snprintf(rest, sizeof(rest), " = %d", decode32(d, &i));
			char s_mode[128];
			nd_decode_oci_mode(s_mode, sizeof(s_mode), mode);
			sprintf(line, "(0x%lx, 0x%lx, '%s', %d, mode=%s)%s",
				oci_server, oci_error, db_link, db_link_len, s_mode, rest);
		} else if (strcmp(func, "OCIServerDetach") == 0) {
			uint64_t oci_server = decode64(d, &i);
			uint64_t oci_error = decode64(d, &i);
			uint32_t mode = decode32(d, &i);
			if (type == 'r')
				snprintf(rest, sizeof(rest), " = %d", decode32(d, &i));
			char s_mode[128];
			nd_decode_oci_mode(s_mode, sizeof(s_mode), mode);
			sprintf(line, "(0x%lx, 0x%lx, mode=%s)%s",
				oci_server, oci_error, s_mode, rest);
		} else if (strcmp(func, "OCIStmtExecute") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			if (type == 'm') {
				nothing_to_log = 1;
				uint64_t stmt = decode64(d, &i);
				unsigned char bind_way = decode8(d, &i);
				decode_query_params(rest, sizeof(rest), bind_way, d, &i);
				if (bind_way == ND_PARAMS_BIND_WAY_IN) {
					uint16_t q_len = decode16(d, &i);
					char q[q_len * 4 + 1];
					sf_tools_bin2hex_ascii(q, d + i, q_len); i += q_len;
					sprintf(line, "(stmt=0x%lx): meta: sql: %s", stmt, q);
					if (strcmp(rest, " {}") != 0)
						sprintf(line2, "(stmt=0x%lx): meta: binds:%s", stmt, rest);
				} else {
					if (strcmp(rest, " {}") != 0)
						sprintf(line, "(stmt=0x%lx): meta: binds:%s", stmt, rest);
				}
			} else {
				uint64_t svc = decode64(d, &i);
				uint64_t stmt = decode64(d, &i);
				uint64_t err = decode64(d, &i);
				uint32_t iters = decode32(d, &i);
				uint32_t rowoff = decode32(d, &i);
				uint64_t snap_in = decode64(d, &i);
				uint64_t snap_out = decode64(d, &i);
				uint32_t mode = decode32(d, &i);
				if (type == 'r') {
					int ret = decode32(d, &i);
					char s_ret[32];
					nd_decode_oci_ret(s_ret, sizeof(s_ret), ret);
					snprintf(rest, sizeof(rest), " = %s", s_ret);
				}
				char s_mode[128];
				nd_decode_oci_execute_mode(s_mode, sizeof(s_mode), mode);
				sprintf(line, "(0x%lx, 0x%lx, 0x%lx iters=%u rowoff=%u, 0x%lx, 0x%lx, %s)%s",
					svc, stmt, err, iters, rowoff, snap_in, snap_out, s_mode, rest);
			}
		} else if (strcmp(func, "OCIStmtPrepare") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t stmt = decode64(d, &i);
			uint64_t oci_error = decode64(d, &i);
			uint16_t q_len = decode16(d, &i);
			char q[q_len * 4 + 1];
			sf_tools_bin2hex_ascii(q, d + i, q_len); i += q_len;
			uint32_t lang = decode32(d, &i);
			uint32_t mode = decode32(d, &i);
			int32_t ret = decode32(d, &i);
			char s_lang[32], s_mode[128];
			nd_decode_oci_lang(s_lang, sizeof(s_lang), lang);
			nd_decode_oci_mode(s_mode, sizeof(s_mode), mode);
			sprintf(line, "(0x%lx, 0x%lx, '%s', %s, mode=%s) = %d",
				stmt, oci_error, q, s_lang, s_mode, ret);
		} else if (strcmp(func, "OCITransCommit") == 0) {
			uint64_t svc = decode64(d, &i);
			uint64_t oci_error = decode64(d, &i);
			uint32_t flags = decode32(d, &i);
			if (type == 'r') {
				int ret = decode32(d, &i);
				char s_ret[32];
				nd_decode_oci_ret(s_ret, sizeof(s_ret), ret);
				snprintf(rest, sizeof(rest), " = %s", s_ret);
			}
			char s_flags[128];
			nd_decode_oci_trans_commit_flags(s_flags, sizeof(s_flags), flags);
			sprintf(line, "(0x%lx, 0x%lx, %s)%s",
				svc, oci_error, s_flags, rest);
		} else if (strcmp(func, "OCITransRollback") == 0) {
			uint64_t svc = decode64(d, &i);
			uint64_t oci_error = decode64(d, &i);
			uint32_t flags = decode32(d, &i);
			if (type == 'r') {
				int ret = decode32(d, &i);
				char s_ret[32];
				nd_decode_oci_ret(s_ret, sizeof(s_ret), ret);
				snprintf(rest, sizeof(rest), " = %s", s_ret);
			}
			char s_flags[128];
			nd_decode_oci_trans_rollback_flags(s_flags, sizeof(s_flags), flags);
			sprintf(line, "(0x%lx, 0x%lx, %s)%s",
				svc, oci_error, s_flags, rest);
		} else if (strcmp(func, "OCITransStart") == 0) {
			uint64_t svc = decode64(d, &i);
			uint64_t oci_error = decode64(d, &i);
			uint32_t timeout = decode32(d, &i);
			uint32_t flags = decode32(d, &i);
			int ret = decode32(d, &i);
			char s_flags[128], s_ret[32];
			nd_decode_oci_trans_start_flags(s_flags, sizeof(s_flags), flags);
			nd_decode_oci_ret(s_ret, sizeof(s_ret), ret);
			sprintf(line, "(0x%lx, 0x%lx, %us, %s) = %s",
				svc, oci_error, timeout, s_flags, s_ret);
		} break;

	case 'p':
		if (strcmp(func, "poll") == 0) {
			uint16_t nf = decode32(d, &i);
			char list[nf * 16 + 1], *add = "";
			list[0] = '\0';
			for (unsigned j = 0; j < nf; j++) {
				int fd = decode32(d, &i);
				short ev = decode16(d, &i);
				char tmp[16 + 1];
				snprintf(tmp, sizeof(tmp),
					"%s%d%s%s%s", add, fd,
					ev ? "/" : "",
					ev & POLLIN ? "i" : "",
					ev & POLLOUT ? "o" : "");
				strcat(list, tmp);
				add = ",";
			}
			int timeout = decode32(d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "([%s], %u, %d)%s",
				list, nf, timeout, rest);
		} else if ((strcmp(func, "pread") == 0)
			|| (strcmp(func, "pread64") == 0)) {
			int fd = decode32(d, &i);
			size_t count = decode64(d, &i);
			off_t offset = decode64(d, &i);
			if (type == 'c') {
				sprintf(line, "(%d, buf, %zu, off=%zu)", fd, count, offset);
			} else {
				ssize_t ret = decode_ret_int64(rest, sizeof(rest), d, &i);
				if (ret > 0) {
					unsigned short max = decode16(d, &i);
					char data[max * 4 + 1];
					sf_tools_bin2hex_ascii(data, d + i, max); i += max;
					sprintf(line, "(%d, '%s'%s, %zu, off=%zu)%s",
						fd, data,
						max < ret ? "..." : "", count, offset, rest);
				} else {
					sprintf(line, "(%d, buf, %zu, off=%zu)%s",
						fd, count, offset, rest);
				}
			}
		} else if ((strcmp(func, "pwrite") == 0)
			|| (strcmp(func, "pwrite64") == 0)) {
			int fd = decode32(d, &i);
			size_t count = decode64(d, &i);
			off_t offset = decode64(d, &i);
			if (type == 'c') {
				unsigned short max = decode16(d, &i);
				char data[max * 4 + 1];
				sf_tools_bin2hex_ascii(data, d + i, max); i += max;
				sprintf(line, "(%d, '%s'%s, %zu, off=%zu)",
					fd, data,
					max < count ? "..." : "", count, offset);
			} else {
				decode_ret_int64(rest, sizeof(rest), d, &i);
				sprintf(line, "(%d, buf, %zu, off=%zu)%s",
					fd, count, offset, rest);
			}
		} else if (strcmp(func, "pg_close") == 0) {
			uint64_t h = decode64(d, &i);
			if (h == 0)
				sprintf(line, "()");
			else
				sprintf(line, "(0x%lx)", h);
		} else if ((strcmp(func, "pg_connect") == 0)
			|| (strcmp(func, "pg_pconnect") == 0)) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint32_t cs_len = decode32(d, &i);
			char cs[cs_len * 4 + 1];
			sf_tools_bin2hex_ascii(cs, d + i, cs_len); i += cs_len;
			if (type == 'r') {
				uint64_t h = decode64(d, &i);
				snprintf(rest, sizeof(rest), " = 0x%lx", h);
			}
			sprintf(line, "('%s')%s", cs, rest);
		} else if (strcmp(func, "pg_free_result") == 0) {
			void *res = (void *) decode64(d, &i);
			uint8_t ret = decode8(d, &i);
			sprintf(line, "(%p) = %s", res, ret == 1 ? "ok" : "nok");
		} else if ((strcmp(func, "pg_query") == 0)
			|| (strcmp(func, "pg_query_params") == 0)
			|| (strcmp(func, "pg_send_query") == 0)
			|| (strcmp(func, "pg_send_query_params") == 0)) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t dbh = decode64(d, &i);
			uint16_t q_len = decode16(d, &i);
			char q[q_len * 4 + 1];
			sf_tools_bin2hex_ascii(q, d + i, q_len); i += q_len;
			if (type == 'c') {
				decode_query_params(rest, sizeof(rest),
					ND_PARAMS_BIND_WAY_IN, d, &i);
			} else if (type == 'r') {
				uint64_t res = decode64(d, &i);
				uint64_t rows = decode64(d, &i);
				uint64_t aff = decode64(d, &i);
				if (res == 1) // pg_send_query[_params]
					snprintf(rest, sizeof(rest),
						" = ok [%lu rows, %lu aff]",
						rows, aff);
				else if (res == 0) // pg_send_query[_params] TODO when this happens? pg_query returns false
					snprintf(rest, sizeof(rest), " = nok"); // TODO: error code
				else
					snprintf(rest, sizeof(rest),
						" = 0x%lx [%lu rows, %lu aff]",
						res, rows, aff);
			}
			sprintf(line, "(h=0x%lx, '%s')%s", dbh, q, rest);
		} else if (strcmp(func, "pg_get_result") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t dbh = decode64(d, &i);
			if (type == 'r') {
				uint64_t res = decode64(d, &i);
				uint64_t rows = decode64(d, &i);
				uint64_t aff = decode64(d, &i);
				snprintf(rest, sizeof(rest), " = 0x%lx [%lu rows, %lu aff]",
					res, rows, aff);
			}
			sprintf(line, "(h=0x%lx)%s", dbh, rest);
		} else if (strcmp(func, "pipe") == 0) {
			int fd0 = decode32(d, &i);
			int fd1 = decode32(d, &i);
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "()%s => {%d, %d}", rest, fd0, fd1);
		} else if (strcmp(func, "pipe2") == 0) {
			int fd0 = decode32(d, &i);
			int fd1 = decode32(d, &i);
			int flags = decode32(d, &i);
			char sflags[128];
			nd_decode_pipe_flags(sflags, sizeof(sflags), flags);
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(flags=%s)%s => {%d, %d}", sflags, rest, fd0, fd1);
		} else if (strcmp(func, "pthread_join") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t thread = decode64(d, &i);
			int ret = decode_ret_int(rest, sizeof(rest), d, &i);
			uint64_t retval = 0;
			if (ret != -1)
				retval = decode64(d, &i);
			sprintf(line, "(thread=0x%lx, retval=0x%lx)%s",
				thread, retval, rest);
		} else if (strcmp(func, "pthread_attr_setstacksize") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t attr = decode64(d, &i);
			size_t stack_size = decode64(d, &i);
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(attr=0x%lx, %zu)%s",
				attr, stack_size, rest);
		} else if (strcmp(func, "pthread_create") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t thread = decode64(d, &i);
			uint64_t attr = decode64(d, &i);
			uint64_t arg = decode64(d, &i);
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(thread=0x%lx, attr=0x%lx, arg=0x%lx)%s",
				thread, attr, arg, rest);
		} else if (strcmp(func, "pthread_setname_np") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t thread = decode64(d, &i);
			uint16_t len = decode16(d, &i);
			char name[len * 4 + 1];
			sf_tools_bin2hex_ascii(name, d + i, len); i += len;
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(thread=0x%lx, '%s')%s",
				thread, name, rest);
		} break;

	case 'r':
		if (strcmp(func, "read") == 0) {
			int fd = decode32(d, &i);
			size_t count = decode64(d, &i);
			sprintf(rest, ", %zu)", count);
			if (type == 'c') {
				sprintf(line, "(%d, buf%s", fd, rest);
			} else if (type == 'r') {
				ssize_t ret = decode_ret_int64(rest, sizeof(rest), d, &i);
				if (ret != -1) {
					unsigned short max = decode16(d, &i);
					char data[max * 4 + 1];
					sf_tools_bin2hex_ascii(data, d + i, max); i += max;
					sprintf(line, "(%d, '%s'%s)%s",
						fd, data,
						max < ret ? "..." : "", rest);
				} else {
					sprintf(line, "(%d)%s", fd, rest);
				}
			}
		} else if (strcmp(func, "readdir") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t dir = decode64(d, &i);
			if (type == 'r')
				decode_dirent(rest, sizeof(rest), d, &i);
			sprintf(line, "(0x%lx)%s", dir, rest);
		} else if (strcmp(func, "readahead") == 0) {
			int fd = decode32(d, &i);
			off_t off = decode64(d, &i);
			size_t count = decode64(d, &i);
			if (type == 'r')
				decode_ret_int64(rest, sizeof(rest), d, &i);
			sprintf(line, "(fd=%d, off=%zu, count=%zu)%s",
				fd, off, count, rest);
		} else if (strcmp(func, "recv") == 0) {
			int sock = decode32(d, &i);
			size_t len = decode64(d, &i);
			int flags = decode32(d, &i);
			sprintf(rest, ", %zu, 0x%x)", len, flags);
			if (type == 'c') {
				sprintf(line, "(%d, buf%s", sock, rest);
			} else if (type == 'r') {
				ssize_t ret = decode_ret_int64(rest, sizeof(rest), d, &i);
				if (ret != -1) {
					unsigned short max = decode16(d, &i);
					char data[max * 4 + 1];
					sf_tools_bin2hex_ascii(data, d + i, max); i += max;
					sprintf(line, "(%d, '%s'%s)%s",
						sock, data,
						max < ret ? "..." : "", rest);
				} else {
					sprintf(line, "(%d)%s", sock, rest);
				}
			}
		} else if (strcmp(func, "recvfrom") == 0) {
			int sock = decode32(d, &i);
			uint64_t len = decode64(d, &i);
			int addrlen = decode32(d, &i);
			int flags = decode32(d, &i);
			if (type == 'c')
				sprintf(line, "(%d, buf, %zu, 0x%x, addr, %u)",
					sock, len, flags, addrlen);
			while (type == 'r') {
				ssize_t ret = decode_ret_int64(rest, sizeof(rest), d, &i);
				if (ret <= 0)
					break;
				uint16_t max = decode16(d, &i);
				char buf[max * 4 + 1];
				sf_tools_bin2hex_ascii(buf, d + i, max); i += max;
				if (addrlen == 0)
					break;
				char addr[addrlen * 2 + 1];
				sf_tools_bin2hex(addr, d + i, addrlen); i += addrlen; // TODO: we need to decode this!
				sprintf(line, "(%d, '%s'%s, %zd, 0x%x, '%s')%s",
					sock, buf, len > max ? "..." : "",
					len, flags, addr, rest);
				break;
			}
		} else if (strcmp(func, "recvmsg") == 0) {
			char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			int sock = decode32(d, &i);
			int flags = decode32(d, &i);
			char sflags[128];
			nd_decode_msghdr_flags(sflags, sizeof(sflags), flags);
			if (type == 'c') {
				sprintf(line, "(%d, msg, flags=%s)", sock, sflags);
			} else if (type == 'r') {
				char sret[128];
				ssize_t r = decode_ret_int64(sret, sizeof(sret), d, &i);
				if (r != -1)
					decode_msghdr(rest, sizeof(rest), d, &i);
				else
					strcpy(rest, "msg");
				sprintf(line, "(%d, %s, flags=%s)%s",
					sock, rest, sflags, sret);
			}
		} else if (strcmp(func, "rename") == 0) {
			char oldpath[512], newpath[512];
			decode_filename(oldpath, d, &i);
			decode_filename(newpath, d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s', '%s')%s", oldpath, newpath, rest);
		} else if (strcmp(func, "rmdir") == 0) {
			char path[512];
			decode_filename(path, d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s')%s", path, rest);
		} break;

	case 's':
		if (strcmp(func, "send") == 0) {
			int sock = decode32(d, &i);
			size_t len = decode64(d, &i);
			int flags = decode32(d, &i);
			if (type == 'c') {
				unsigned short max = decode16(d, &i);
				char data[max * 4 + 1];
				sf_tools_bin2hex_ascii(data, d + i, max); i += max;
				sprintf(line, "(%d, '%s'%s, %zu, 0x%x)",
					sock, data,
					max < len ? "..." : "", len, flags);
			} else if (type == 'r') {
				decode_ret_int64(rest, sizeof(rest), d, &i);
				sprintf(line, "(%d, buf, %zu, 0x%x)%s",
					sock, len, flags, rest);
			}
		} else if (strcmp(func, "sendfile") == 0) {
			int out_fd = decode32(d, &i);
			int in_fd = decode32(d, &i);
			off_t off = decode64(d, &i);
			size_t count = decode64(d, &i);
			if (type == 'r')
				decode_ret_int64(rest, sizeof(rest), d, &i);
			sprintf(line, "(out_fd=%d, in_fd=%d, off=%zu, count=%zu)%s",
				out_fd, in_fd, off, count, rest);
		} else if (strcmp(func, "sendmsg") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			int sock = decode32(d, &i);
			if (type == 'c')
				decode_msghdr(rest, sizeof(rest), d, &i);
			int flags = decode32(d, &i);
			char sflags[128];
			nd_decode_msghdr_flags(sflags, sizeof(sflags), flags);
			if (type == 'c') {
				sprintf(line, "(%d, %s, flags=%s)", sock, rest, sflags);
				// TODO: I need a way to split on multiple lines
				// TODO: really needed?
			} else if (type == 'r') {
				decode_ret_int64(rest, sizeof(rest), d, &i);
				sprintf(line, "(%d, msg, flags=%s)%s",
					sock, sflags, rest);
			}
		} else if (strcmp(func, "setns") == 0) {
			int fd = decode32(d, &i);
			int nstype = decode32(d, &i);
			char snstype[32];
			nd_decode_clone_flags(snstype, sizeof(snstype), nstype);
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(fd=%d, nstype=%s)%s",
				fd, snstype, rest);
		} else if (strcmp(func, "setsockopt") == 0) {
			int sock = decode32(d, &i);
			char level[32], optname[32];
			int ilevel = decode_sock_level(level, sizeof(level), d, &i);
			decode_sock_optname(optname, sizeof(optname), ilevel, d, &i);
			socklen_t optlen = decode32(d, &i);
			char optval[optlen * 2 + 1];
			sf_tools_bin2hex(optval, d + i, optlen); i+= optlen;
			decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%d, %s, %s, 0x%s, %u)%s",
				sock, level, optname, optval, optlen, rest);
		} else if (strcmp(func, "socket") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			int xdomain = decode32(d, &i);
			int xtype = decode32(d, &i);
			int xprotocol = decode32(d, &i);
			decode_ret_int(rest, sizeof(rest), d, &i);
			char dom[32], type[32], proto[32];
			nd_decode_socket_domain(dom, sizeof(dom), xdomain);
			nd_decode_socket_type(type, sizeof(type), xtype);
			nd_decode_socket_protocol(proto, sizeof(proto),
				xdomain, xtype, xprotocol);
			sprintf(line, "(%s, %s, %s)%s", dom, type, proto, rest);
		} else if (strcmp(func, "sqlite3_bind_double") == 0) {
			uint64_t stmt = decode64(d, &i);
			int index = decode32(d, &i);
			double value = decode64(d, &i);
			decode_sqlite3_ret(rest, sizeof(rest), d, &i);
			sprintf(line, "(0x%lx, index=%d, value=%f) = %s",
				stmt, index, value, rest);
		} else if (strcmp(func, "sqlite3_bind_int") == 0) {
			uint64_t stmt = decode64(d, &i);
			int index = decode32(d, &i);
			int value = decode32(d, &i);
			decode_sqlite3_ret(rest, sizeof(rest), d, &i);
			sprintf(line, "(0x%lx, index=%d, value=%d) = %s",
				stmt, index, value, rest);
		} else if (strcmp(func, "sqlite3_bind_int64") == 0) {
			uint64_t stmt = decode64(d, &i);
			int index = decode32(d, &i);
			int64_t value = decode64(d, &i);
			decode_sqlite3_ret(rest, sizeof(rest), d, &i);
			sprintf(line, "(0x%lx, index=%d, value=%ld) = %s",
				stmt, index, value, rest);
		} else if (strcmp(func, "sqlite3_bind_text") == 0) {
			uint64_t stmt = decode64(d, &i);
			int index = decode32(d, &i);
			int16_t len = decode16(d, &i);
			char value[len * 4 + 1];
			sf_tools_bin2hex_ascii(value, d + i, len); i += len;
			decode_sqlite3_ret(rest, sizeof(rest), d, &i);
			sprintf(line, "(stmt=0x%lx, index=%d, '%s') = %s",
				stmt, index, value, rest);
		} else if (strcmp(func, "sqlite3_finalize") == 0) {
			uint64_t stmt = decode64(d, &i);
			decode_sqlite3_ret(rest, sizeof(rest), d, &i);
			sprintf(line, "(0x%lx)%s%s", stmt, rest[0] ? " = " : "", rest);
		} else if (strcmp(func, "sqlite3_open_v2") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			char filename[512];
			decode_filename(filename, d, &i);
			char flags[128];
			decode_sqlite3_open_flags(flags, sizeof(flags), d, &i);
			uint16_t vfs_len = decode16(d, &i);
			char vfs[vfs_len * 4 + 1];
			sf_tools_bin2hex_ascii(vfs, d + i, vfs_len); i += vfs_len;

			if (type == 'c') {
				sprintf(line, "('%s', %s, vfs='%s')",
					filename, flags, vfs);
			} else {
				char err[64];
				int ret = decode_sqlite3_ret(err, sizeof(err), d, &i);
				uint64_t pdb = 0;
				if (ret == 0)
					pdb = decode64(d, &i);
				sprintf(line, "('%s', 0x%lx, %s, vfs='%s') = %s",
					filename, pdb, flags, vfs, err);
			}
		} else if (strcmp(func, "sqlite3_prepare_v2") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t h = decode64(d, &i);
			uint32_t sql_len = decode32(d, &i);
			char sql[sql_len * 4 + 1];
			sf_tools_bin2hex_ascii(sql, d + i, sql_len); i += sql_len;
			int nByte = decode32(d, &i);
			if (type == 'c') {
				sprintf(line, "(0x%lx, '%s', %d, stmt, tail)",
					h, sql, nByte);
			} else {
				uint64_t stmt = decode64(d, &i);
				char err[64];
				decode_sqlite3_ret(err, sizeof(err), d, &i);
				sprintf(line, "(0x%lx, '%s', %d, 0x%lx, tail) = %s",
					h, sql, nByte, stmt, err);
			}
		} else if (strcmp(func, "sqlite3_step") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			uint64_t stmt = decode64(d, &i);
			if (type == 'c') {
				sprintf(line, "(0x%lx)", stmt);
			} else {
				char err[64];
				decode_sqlite3_ret(err, sizeof(err), d, &i);
				sprintf(line, "(0x%lx) = %s", stmt, err);
			}
		} else if (strcmp(func, "stat") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			char pathname[512];
			decode_filename(pathname, d, &i);
			if (type == 'r')
				decode_stat(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s')%s", pathname, rest);
		} else if (strcmp(func, "statx") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			char sdirfd[64];
			decode_statx_dirfd(sdirfd, sizeof(sdirfd), d, &i);
			char pathname[512];
			decode_filename(pathname, d, &i);
			int flags = decode32(d, &i);
			char sflags[128];
			nd_decode_statx_flags(sflags, sizeof(sflags), flags);
			unsigned int mask = decode32(d, &i);
			char smask[128];
			nd_decode_statx_mask(smask, sizeof(smask), mask);
			if (type == 'r')
				decode_statx(rest, sizeof(rest), d, &i);
			sprintf(line, "(%s, '%s', flags=%s, mask=%s)%s",
				sdirfd, pathname, sflags, smask, rest);
		} else if (strcmp(func, "symlink") == 0) {
			char target[512], linkpath[512];
			decode_filename(target, d, &i);
			decode_filename(linkpath, d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s', '%s')%s", target, linkpath, rest);
		} else if (strcmp(func, "symlinkat") == 0) {
			char target[512];
			decode_filename(target, d, &i);
			int newdirfd = decode32(d, &i);
			char snewdirfd[32], color_snewdirfd[64];
			nd_decode_symlink_newdirfd(snewdirfd, sizeof(snewdirfd), newdirfd);
			nd_trace_color(color_snewdirfd, sizeof(color_snewdirfd),
				snewdirfd, "flags", '-', "", 0);
			char linkpath[512];
			decode_filename(linkpath, d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s', %s, '%s')%s",
				target, color_snewdirfd, linkpath, rest);
		} else if (strcmp(func, "sync") == 0) {
			strcpy(line, "()");
		} else if (strcmp(func, "syncfs") == 0) {
			int fd = decode32(d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%d)%s", fd, rest);
		} else if (strcmp(func, "syslog") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP[%s][%c]: %s\n", func, type, dump);
			char sprio[32];
			decode_syslog_prio(sprio, sizeof(sprio), d, &i);
			int len = decode16(d, &i);
			char data[len * 4 + 1];
			sf_tools_bin2hex_ascii(data, d + i, len); i += len;
			sprintf(line, "(%s, '%s')", sprio, data);
		} break;

	case 't':
		if (strcmp(func, "timerfd_create") == 0) {
			int clockid = decode32(d, &i);
			int flags = decode32(d, &i);
			char sclock[32], sflags[64];
			nd_decode_clockid(sclock, sizeof(sclock), clockid);
			nd_decode_timerfd_flags(sflags, sizeof(sflags), flags);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%s, %s)%s", sclock, sflags, rest);
		} break;

	case 'u':
		if (strcmp(func, "umask") == 0) {
			mode_t mode = decode32(d, &i);
			mode_t ret = decode32(d, &i);
			sprintf(line, "(%03o) = %03o", mode, ret);
		} else if (strcmp(func, "unlink") == 0) {
			char pathname[512];
			decode_filename(pathname, d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s')%s", pathname, rest);
		} else if (strcmp(func, "umount") == 0) {
			char target[512];
			decode_filename(target, d, &i);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s')%s", target, rest);
		} else if (strcmp(func, "umount2") == 0) {
			char target[512];
			decode_filename(target, d, &i);
			int flags = decode32(d, &i);
			char sflags[128];
			nd_decode_umount_flags(sflags, sizeof(sflags), flags);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "('%s', %s)%s", target, sflags, rest);
		} else if (strcmp(func, "unshare") == 0) {
			int flags = decode32(d, &i);
			char sflags[128];
			nd_decode_clone_flags(sflags, sizeof(sflags), flags);
			if (type == 'r')
				decode_ret_int(rest, sizeof(rest), d, &i);
			sprintf(line, "(%s)%s", sflags, rest);
		} break;

	case 'w':
		if (strcmp(func, "write") == 0) {
			//char dump[4096]; sf_tools_bin2hex_ascii(dump, d + i, 128); fprintf(outf, "DUMP: %s\n", dump);
			int fd = decode32(d, &i);
			size_t count = decode64(d, &i);
			if (type == 'c') {
				unsigned short max = decode16(d, &i);
				char data[max * 4 + 1];
				sf_tools_bin2hex_ascii(data, d + i, max); i += max;
				sprintf(line, "(%d, '%s'%s, %zu)",
					fd, data,
					max < count ? "..." : "", count);
			} else if (type == 'r') {
				decode_ret_int64(rest, sizeof(rest), d, &i);
				sprintf(line, "(%d, buf, %zu)%s",
					fd, count, rest);
			}
		} break;
	}
	if (line[0] == '\0') {
		if (nothing_to_log == 0)
			fprintf(outf, "I do not know how to decode func [%s] type [%c]!\n",
				func, type);
		return;
	}

	char space[4096];
	unsigned short max = sizeof(space) - 1;
	if (max > trace_depth)
		max = trace_depth;
	memset(space, ' ', max);
	space[max] = '\0';

	char cfunc[128];
	if (theme)
		nd_trace_color(cfunc, sizeof(cfunc), func, "func", type, tf, 0);

	char sts[32], sts2[64];
	if (strcmp(time_format, "none") == 0) {
		sts2[0] = '\0';
	} else {
		if (strcmp(time_format, "human") == 0) {
			snprintf(sts, sizeof(sts), "%lu.%03lu ", t / 1000, t % 1000);
		} else {
			snprintf(sts, sizeof(sts), "%lu.%03lu ", t / 1000, t % 1000);
		}
		nd_trace_color(sts2, sizeof(sts2), sts, "ts", '-', "",
			last_ts == 0 ? 0 : t - last_ts);
	}

	if (hide_pids) {
		fprintf(outf, "%s%s %s%s\n",
			sts2, space, theme ? cfunc : func, line);
		if (line2[0] != '\0')
			fprintf(outf, "%s%s %s%s\n",
				sts2, space, theme ? cfunc : func, line2);
	} else if (pid == tid) {
		fprintf(outf, "%s%10u %s %s%s\n",
			sts2, pid, space, theme ? cfunc : func, line);
		if (line2[0] != '\0')
			fprintf(outf, "%s%10u %s %s%s\n",
				sts2, pid, space, theme ? cfunc : func, line2);
	} else {
		fprintf(outf, "%s%10u %10u %s %s%s\n",
			sts2, pid, tid, space, theme ? cfunc : func, line);
		if (line2[0] != '\0')
			fprintf(outf, "%s%10u %10u %s %s%s\n",
				sts2, pid, tid, space, theme ? cfunc : func, line2);
	}

	last_ts = t;
}

static void decode(const pid_t parent, unsigned char *d, size_t len)
{
	unsigned int i = 0;
	char type;

	type = d[i++];
	if (type == 'F') {
		decode_func(parent, d + i);
	} else {
		fprintf(outf, "I do not know how to decode type [%c]!\n", type);
		char dump[len * 4 + 1];
		sf_tools_bin2hex_ascii(dump, d, len);
		fprintf(outf, "Decode %s\n", dump);
	}
}

int main(int argc, char *argv[])
{
	int c, r;
	int options_index = 0;
	char *out_file = NULL;
	int sm[256];
	char version[256];
	struct shared *shared[256];
	pid_t child;

	// Disabling ninedogs.so for the tracer
	int fd = open("/dev/ninedogs", O_WRONLY);
	if (fd != -1) {
		ssize_t junk = write(fd, "disable", 7);
		(void) junk;
		close(fd);
	}

	for (unsigned i = 0; i < 256; i++) {
		sm[i] = -2;
		version[i] = 0;
	}

	setlinebuf(stderr);

	while ((c = getopt_long(argc, argv, "p:o:Ht:T:P", options, &options_index)) != -1) {
		switch (c) {
		case 'o': out_file = optarg; break;
		case 'p': if (no_pids < 256) pids[no_pids++] = strtoull(optarg, NULL, 10); break;
		case 'H': only_high_level = 1; break;
		case 't': theme_name = optarg; break;
		case 'T': time_format = optarg; break;
		case 'P': hide_pids = 1; break;
		default: usage();
		}
	}
	argc -= optind;
	argv += optind;
	//fprintf(stderr, "argc=%u [%s]\n", argc, argv[0]);

	if (argc > 0) {
		child = fork();
		if (child == -1) {
			fprintf(stderr, "Cannot execute: %m\n");
			exit(1);
		} else if (child == 0) {
			char *env[] = { "LD_PRELOAD=ninedogs.so",
				"NINEDOGS_DELAY=1", "NINEDOGS_VERBOSE=0", NULL };
			r = execvpe(argv[0], argv, env);
			if (r == -1)
				fprintf(stderr, "Error executing: %m\n");
			exit(1);
		}
		fprintf(stderr, "Adding pid %u to list\n", child);

		if (no_pids < 256)
			pids[no_pids++] = child;
	}

	if (no_pids == 0) {
		fprintf(stderr, "Error: no pids specified with -p!\n");
		return 1;
	}

	if (!out_file) {
		outf = stderr;
	} else {
		fprintf(stderr, "Saving output to [%s]\n", out_file);
		umask(S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH);
		outf = fopen(out_file, "w");
		if (!outf) {
			fprintf(stderr, "Error: cannot open [%s]: %m\n", out_file);
			return 1;
		}
	}
	setlinebuf(outf);

	//pid_t my_pid = getpid();

	nd_trace_color_set_theme();

	again:
	for (unsigned i = 0; i < no_pids; i++) {
		unsigned char *p;
		unsigned int ava, head, tail, used;
		unsigned char buf[64000];
		struct stat s;

		if (sm[i] < 0) {
			char name[32];
			snprintf(name, sizeof(name), "/ninedogs-%d", pids[i]);
			sm[i] = shm_open(name, O_RDWR, 0);
			if (sm[i] == -1) {
				//fprintf(stderr, "%u: Cannot do shm_open: %m\n", pids[i]);
				continue;
			}
			//fprintf(stderr, "%u: shm_open returned %d\n", pids[i], sm[i]);

			shared[i] = mmap(NULL, sizeof(struct shared),
				PROT_READ | PROT_WRITE, MAP_SHARED, sm[i], 0);
			if (shared[i] == MAP_FAILED) {
				fprintf(stderr, "%u: Cannot mmap: %m\n", pids[i]);
				close(sm[i]);
				sm[i] = -1;
				continue;
			}

			fprintf(stderr, "%u: attached\n", pids[i]);
			// we use '|' because we may have a concurrent tracer attached
			shared[i]->clients++;
			shared[i]->client_flags |= only_high_level == 1 ? ND_SHARED_ONLY_HIGH_LEVEL : 0;
		}

		if (do_exit == no_pids) {
			shared[i]->clients--;
			fprintf(stderr, "Bye!\n");
			break;
		}

		fstat(sm[i], &s);
		if (s.st_size == 0) {
			//fprintf(outf, "%u: Shared memory size is zero!\n", pids[i]);
			continue;
		}
		//fprintf(outf, "%u: Shared memory size: %ld\n", pids[i], s.st_size);

		r = sem_wait(&shared[i]->sem1);
		if (r == -1) {
			fprintf(stderr, "%u: Cannot wait for sem1: %m\n", pids[i]);
			return 1;
		}

		// Only here we can read 'head' and 'tail'
		head = shared[i]->head;
		tail = shared[i]->tail;

		r = sem_post(&shared[i]->sem1);
		if (r == -1) {
			fprintf(stderr, "%u: Cannot post sem1: %m\n", pids[i]);
			return 1;
		}

		if (version[i] == 0) {
			version[i] = shared[i]->version;
			fprintf(outf, "%u: version is %hhu\n", pids[i], version[i]);
		}

		// tail-1 points to last value available
		if (tail == head)
			continue;
		else if (head < tail) // 01h3t5 => ava = 2
			ava = tail - head;
		else // 01t345h78 => ava = 2 + (9 - 6) = 5
			ava = tail + (shared[i]->buf_size - head);

		//fprintf(outf, "RING[%u]: head=%u tail=%u ava=%u [after lock]\n",
		//	i, head, tail, ava);

		//char dump[ava * 4 + 1];
		//sf_tools_bin2hex_ascii(dump, shared[i]->buf + shared[i]->head, ava);
		//fprintf(outf, "DUMP0[%hu]: %s\n", ava, dump);

		used = 0;
		while (ava >= 2) {
			unsigned short plen;

			// TODO: we may have a byte at the end and the other at the beginning!
			if (shared[i]->buf_size - head == 1) { // ...h size=4 h=3 => max=1 or ..h. max=2
				// Split length!
				plen = shared[i]->buf[head] << 8;
				plen |= shared[i]->buf[0];
			} else {
				memcpy(&plen, shared[i]->buf + head, sizeof(plen));
				plen = be16toh(plen);
			}
			if (plen > ava) {
				fprintf(outf, "%u:  Too short packet: plen=%hu > ava[%u]!\n",
					pids[i], plen, ava);
				break;
			}

			if (head + plen <= shared[i]->buf_size) { //  dddt...hddd size=11, head=7, plen=4 => 10 <= 11
				p = shared[i]->buf + head;
			} else { // split data! dddt...hddd size=11, head=7, plen=8 => max=4
				unsigned int max = shared[i]->buf_size - head;
				if (max > plen) max = plen;
				//fprintf(outf, "  DEBUG: split data. max=%u\n", max);
				//fprintf(outf, "  DEBUG: copy to buf from %p+%u, %u bytes\n", shared[i]->buf, shared[i]->head, max);
				memcpy(buf, shared[i]->buf + head, max);
				//fprintf(outf, "  DEBUG: copy to buf+%u from %p, %u bytes\n", max, shared[i]->buf, plen - max);
				memcpy(buf + max, shared[i]->buf, plen - max);
				p = buf;
			}

			if (0) {
				char dump[plen * 4 + 1];
				sf_tools_bin2hex_ascii(dump, p, plen);
				fprintf(outf, "%u:  DUMP[%hu] head=%u: %s\n",
					pids[i], plen, head, dump);
			}

			decode(pids[i], p + 2, plen - 2);

			ava -= plen;
			head = (head + plen) % shared[i]->buf_size;
			used += plen;
			//fprintf(outf, "  DEBUG: ava[%u] after substracting plen[%hu]; no_pids=%u\n",
			//	ava, plen, no_pids);
		}

		r = sem_wait(&shared[i]->sem1);
		if (r == -1) {
			fprintf(stderr, "%u: Cannot wait for sem1: %m\n", pids[i]);
			return 1;
		}

		// Only here we can change 'head'
		shared[i]->head = head;
		shared[i]->junk++;
		//fprintf(outf, "  DEBUG: shared[%u]->head moved to %u\n", i, shared[i]->head);

		r = sem_post(&shared[i]->sem1);
		if (r == -1) {
			fprintf(stderr, "%u: Cannot post sem1: %m\n", pids[i]);
			return 1;
		}
	}

	if (!do_exit) {
		usleep(100 * 1000);
		goto again;
	}

	// TODO: print some stats
	return 0;
}



Mode Type Size Ref File
100644 blob 177 5c4689cc689fe8331a251f33913b0450a47545e1 .gitignore
100644 blob 95 7642b9826e1e2c9b5c5c7aa9c940f97939d2571a History.txt
100644 blob 34520 dba13ed2ddf783ee8118c6a581dbf75305f816a3 LICENSE
100644 blob 783 70c9e3f19e0023cd079edde5ef8eba3ef692ca0e Makefile.common.in
100644 blob 1159 c23934f03cfbf5b90a1e6fcad3a8938830d71243 Makefile.in
100644 blob 42 d7c52ebbfd48ca77062939fdbb338cd89ec8c432 README
100644 blob 2773 6e08b630e723416a98db74644e194593eeb44771 TODO
040000 tree - 75681f3480f2bcc00f4df83819104fc76e78ca21 agent
040000 tree - 745cb8edf2ce432ba86d19e84b9d9264675da9fa common
100755 blob 30 92c4bc48245c00408cd7e1fd89bc1a03058f4ce4 configure
040000 tree - 1e5106b94ebd01f707f0ef0e2f62786ca4a026d5 debian
040000 tree - f2fd66bde957d057a33dff00a5040166d4fb7a71 docs
100755 blob 18530 45f1710267130a4a4db542edd579f7c08be09839 duilder
040000 tree - 4195278236a631a17d3cbd541ab4aaa17eca8790 ingestd
040000 tree - c0540001659e312c01c650f510e92b1a1b07e214 misc
100644 blob 2276 e5b1512bd9c72e795882732ea82504110f268a96 ninedogs.spec
040000 tree - ed2ce950c8d76542b032c32b6414eb756393ae87 rocketgit
040000 tree - 4e32fc3d5249af07533fb4ea602e150ca737aee7 samples
040000 tree - 91094c4942b3f4205b586d8e934efa2006901fe2 test
040000 tree - 68c554b3cb25597390cbfd70c3ca9a16c7c265c8 tools
040000 tree - 4c377c29b3a8f5ebf62cfabf4563a1fec51e452b trace
040000 tree - fc1176d20d37c31c9dae3b0e0e467dceeeecaefc webd
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/catalinux/ninedogs

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/catalinux/ninedogs

Clone this repository using git:
git clone git://git.rocketgit.com/user/catalinux/ninedogs

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main