/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
/* Copyright (c) 2021, Oracle and/or its affiliates. */

#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include <bpf/btf.h>

#include "ksnoop.h"
#include "ksnoop.skel.h"

#ifndef KSNOOP_VERSION
#define KSNOOP_VERSION	"0.1"
#endif

static volatile sig_atomic_t exiting = 0;

static struct btf *vmlinux_btf;
static const char *bin_name;
static int pages = PAGES_DEFAULT;

enum log_level {
	DEBUG,
	WARN,
	ERROR,
};

static enum log_level log_level = WARN;
static bool verbose = false;

static __u32 filter_pid;
static bool stack_mode;


static void __p(enum log_level level, char *level_str, char *fmt, ...)
{
	va_list ap;

	if (level < log_level)
		return;
	va_start(ap, fmt);
	fprintf(stderr, "%s: ", level_str);
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n");
	va_end(ap);
	fflush(stderr);
}

#define p_err(fmt, ...)		__p(ERROR, "Error", fmt, ##__VA_ARGS__)
#define p_warn(fmt, ...)	__p(WARNING, "Warn", fmt, ##__VA_ARGS__)
#define	p_debug(fmt, ...)	__p(DEBUG, "Debug", fmt, ##__VA_ARGS__)

static int do_version(int argc, char **argv)
{
	printf("%s v%s\n", bin_name, KSNOOP_VERSION);
	return 0;
}

static int cmd_help(int argc, char **argv)
{
	fprintf(stderr,
		"Usage: %s [OPTIONS] [COMMAND | help] FUNC\n"
		"	COMMAND	:= { trace | info }\n"
		"	FUNC	:= { name | name(ARG[,ARG]*) }\n"
		"	ARG	:= { arg | arg [PRED] | arg->member [PRED] }\n"
		"	PRED	:= { == | != | > | >= | < | <=  value }\n"
		"	OPTIONS	:= { {-d|--debug} | {-v|--verbose} | {-V|--version} |\n"
		"                    {-p|--pid filter_pid}|\n"
		"                    {-P|--pages nr_pages} }\n"
		"                    {-s|--stack}\n",
		bin_name);
	fprintf(stderr,
		"Examples:\n"
		"	%s info ip_send_skb\n"
		"	%s trace ip_send_skb\n"
		"	%s trace \"ip_send_skb(skb, return)\"\n"
		"	%s trace \"ip_send_skb(skb->sk, return)\"\n"
		"	%s trace \"ip_send_skb(skb->len > 128, skb)\"\n"
		"	%s trace -s udp_sendmsg ip_send_skb\n",
		bin_name, bin_name, bin_name, bin_name, bin_name, bin_name);
	return 0;
}

static void usage(void)
{
	cmd_help(0, NULL);
	exit(1);
}

static void type_to_value(struct btf *btf, char *name, __u32 type_id,
			  struct value *val)
{
	const struct btf_type *type;
	__s32 id = type_id;

	if (strlen(val->name) == 0) {
		if (name)
			strncpy(val->name, name,
				sizeof(val->name) - 1);
		else
			val->name[0] = '\0';
	}
	do {
		type = btf__type_by_id(btf, id);

		switch (BTF_INFO_KIND(type->info)) {
		case BTF_KIND_CONST:
		case BTF_KIND_VOLATILE:
		case BTF_KIND_RESTRICT:
			id = type->type;
			break;
		case BTF_KIND_PTR:
			val->flags |= KSNOOP_F_PTR;
			id = type->type;
			break;
		default:
			val->type_id = id;
			goto done;
		}
	} while (id >= 0);

	val->type_id = KSNOOP_ID_UNKNOWN;
	return;
done:
	val->size = btf__resolve_size(btf, val->type_id);
}

static int member_to_value(struct btf *btf, const char *name, __u32 type_id,
			   struct value *val, int lvl)
{
	const struct btf_member *member;
	const struct btf_type *type;
	const char *pname;
	__s32 id = type_id;
	int i, nmembers;
	__u8 kind;

	/* type_to_value has already stripped qualifiers, so
	 * we either have a base type, a struct, union, etc.
	 * Only struct/unions have named members so anything
	 * else is invalid.
	 */
	p_debug("Looking for member '%s' in type id %d", name, type_id);
	type = btf__type_by_id(btf, id);
	pname = btf__str_by_offset(btf, type->name_off);
	if (strlen(pname) == 0)
		pname = "<anon>";

	kind = BTF_INFO_KIND(type->info);
	switch (kind) {
	case BTF_KIND_STRUCT:
	case BTF_KIND_UNION:
		nmembers = BTF_INFO_VLEN(type->info);
		p_debug("Checking %d members...", nmembers);
		for (member = (struct btf_member *)(type + 1), i = 0;
		     i < nmembers;
		     member++, i++) {
			const char *mname;
			__u16 offset;

			type = btf__type_by_id(btf, member->type);
			mname = btf__str_by_offset(btf, member->name_off);
			offset = member->offset / 8;

			p_debug("Checking member '%s' type %d offset %d",
				mname, member->type, offset);

			/* anonymous struct member? */
			kind = BTF_INFO_KIND(type->info);
			if (strlen(mname) == 0 &&
			    (kind == BTF_KIND_STRUCT ||
			     kind == BTF_KIND_UNION)) {
				p_debug("Checking anon struct/union %d",
					member->type);
				val->offset += offset;
				if (!member_to_value(btf, name, member->type,
						     val, lvl + 1))
					return 0;
				val->offset -= offset;
				continue;
			}

			if (strcmp(mname, name) == 0) {
				val->offset += offset;
				val->flags |= KSNOOP_F_MEMBER;
				type_to_value(btf, NULL, member->type, val);
				p_debug("Member '%s', offset %d, flags %x size %d",
					mname, val->offset, val->flags,
					val->size);
				return 0;
			}
		}
		if (lvl > 0)
			break;
		p_err("No member '%s' found in %s [%d], offset %d", name, pname,
		      id, val->offset);
		break;
	default:
		p_err("'%s' is not a struct/union", pname);
		break;
	}
	return -ENOENT;
}

static int get_func_btf(struct btf *btf, struct func *func)
{
	const struct btf_param *param;
	const struct btf_type *type;
	__u8 i;

	func->id = btf__find_by_name_kind(btf, func->name, BTF_KIND_FUNC);
	if (func->id <= 0) {
		p_err("Cannot find function '%s' in BTF: %s",
		       func->name, strerror(-func->id));
		return -ENOENT;
	}
	type = btf__type_by_id(btf, func->id);
	if (!type || BTF_INFO_KIND(type->info) != BTF_KIND_FUNC) {
		p_err("Error looking up function type via id '%d'", func->id);
		return -EINVAL;
	}
	type = btf__type_by_id(btf, type->type);
	if (!type || BTF_INFO_KIND(type->info) != BTF_KIND_FUNC_PROTO) {
		p_err("Error looking up function proto type via id '%d'",
		      func->id);
		return -EINVAL;
	}
	for (param = (struct btf_param *)(type + 1), i = 0;
	     i < BTF_INFO_VLEN(type->info) && i < MAX_ARGS;
	     param++, i++) {
		type_to_value(btf,
			      (char *)btf__str_by_offset(btf, param->name_off),
			      param->type, &func->args[i]);
		p_debug("arg #%d: <name '%s', type id '%u'>",
			i + 1, func->args[i].name, func->args[i].type_id);
	}

	/* real number of args, even if it is > number we recorded. */
	func->nr_args = BTF_INFO_VLEN(type->info);

	type_to_value(btf, KSNOOP_RETURN_NAME, type->type,
		      &func->args[KSNOOP_RETURN]);
	p_debug("return value: type id '%u'>",
		func->args[KSNOOP_RETURN].type_id);
	return 0;
}

static int predicate_to_value(char *predicate, struct value *val)
{
	char pred[MAX_STR];
	long v;

	if (!predicate)
		return 0;

	p_debug("checking predicate '%s' for '%s'", predicate, val->name);

	if (sscanf(predicate, "%[!=><]%li", pred, &v) != 2) {
		p_err("Invalid specification; expected predicate, not '%s'",
		      predicate);
		return -EINVAL;
	}
	if (!(val->flags & KSNOOP_F_PTR) &&
	    (val->size == 0 || val->size > sizeof(__u64))) {
		p_err("'%s' (size %d) does not support predicate comparison",
		      val->name, val->size);
		return -EINVAL;
	}
	val->predicate_value = (__u64)v;

	if (strcmp(pred, "==") == 0) {
		val->flags |= KSNOOP_F_PREDICATE_EQ;
		goto out;
	} else if (strcmp(pred, "!=") == 0) {
		val->flags |= KSNOOP_F_PREDICATE_NOTEQ;
		goto out;
	}
	if (pred[0] == '>')
		val->flags |= KSNOOP_F_PREDICATE_GT;
	else if (pred[0] == '<')
		val->flags |= KSNOOP_F_PREDICATE_LT;

	if (strlen(pred) == 1)
		goto out;

	if (pred[1] != '=') {
		p_err("Invalid predicate specification '%s'", predicate);
		return -EINVAL;
	}
	val->flags |= KSNOOP_F_PREDICATE_EQ;

out:
	p_debug("predicate '%s', flags 0x%x value %x",
		pred, val->flags, val->predicate_value);

	return 0;
}

static int trace_to_value(struct btf *btf, struct func *func, char *argname,
			  char *membername, char *predicate, struct value *val)
{
	__u8 i;

	if (strlen(membername) > 0)
		snprintf(val->name, sizeof(val->name), "%s->%s",
			 argname, membername);
	else
		strncpy(val->name, argname, sizeof(val->name));

	for (i = 0; i < MAX_TRACES; i++) {
		if (!func->args[i].name)
			continue;
		if (strcmp(argname, func->args[i].name) != 0)
			continue;
		p_debug("setting base arg for val %s to %d", val->name, i);
		val->base_arg = i;

		if (strlen(membername) > 0) {
			if (member_to_value(btf, membername,
					    func->args[i].type_id, val, 0))
				return -ENOENT;
		} else {
			val->type_id = func->args[i].type_id;
			val->flags |= func->args[i].flags;
			val->size = func->args[i].size;
		}
		return predicate_to_value(predicate, val);
	}
	p_err("Could not find '%s' in arguments/return value for '%s'",
	      argname, func->name);
	return -ENOENT;
}

static struct btf *get_btf(const char *name)
{
	struct btf *mod_btf;
	int err;

	p_debug("getting BTF for %s",
		name && strlen(name) > 0 ? name : "vmlinux");

	if (!vmlinux_btf) {
		vmlinux_btf = btf__load_vmlinux_btf();
		if (!vmlinux_btf) {
			err = -errno;
			p_err("No BTF, cannot determine type info: %s", strerror(-err));
			return NULL;
		}
	}
	if (!name || strlen(name) == 0)
		return vmlinux_btf;

	mod_btf = btf__load_module_btf(name, vmlinux_btf);
	if (!mod_btf) {
		err = -errno;
		p_err("No BTF for module '%s': %s", name, strerror(-err));
		return NULL;
	}
	return mod_btf;
}

static void copy_without_spaces(char *target, char *src)
{
	for (; *src != '\0'; src++)
		if (!isspace(*src))
			*(target++) = *src;
	*target = '\0';
}

static char *type_id_to_str(struct btf *btf, __s32 type_id, char *str)
{
	const struct btf_type *type;
	const char *name = "";
	char *prefix = "";
	char *suffix = " ";
	char *ptr = "";

	str[0] = '\0';

	switch (type_id) {
	case 0:
		name = "void";
		break;
	case KSNOOP_ID_UNKNOWN:
		name = "?";
		break;
	default:
		do {
			type = btf__type_by_id(btf, type_id);
			if (!type) {
				name = "?";
				break;
			}

			switch (BTF_INFO_KIND(type->info)) {
			case BTF_KIND_CONST:
			case BTF_KIND_VOLATILE:
			case BTF_KIND_RESTRICT:
				type_id = type->type;
				break;
			case BTF_KIND_PTR:
				ptr = "* ";
				type_id = type->type;
				break;
			case BTF_KIND_ARRAY:
				suffix = "[]";
				type_id = type->type;
				break;
			case BTF_KIND_STRUCT:
				prefix = "struct ";
				name = btf__str_by_offset(btf, type->name_off);
				break;
			case BTF_KIND_UNION:
				prefix = "union ";
				name = btf__str_by_offset(btf, type->name_off);
				break;
			case BTF_KIND_ENUM:
				prefix = "enum ";
				name = btf__str_by_offset(btf, type->name_off);
				break;
			case BTF_KIND_TYPEDEF:
				name = btf__str_by_offset(btf, type->name_off);
				break;
			default:
				name = btf__str_by_offset(btf, type->name_off);
				break;
			}
		} while (type_id >= 0 && strlen(name) == 0);
		break;
	}
	snprintf(str, MAX_STR, "%s%s%s%s", prefix, name, suffix, ptr);

	return str;
}

static char *value_to_str(struct btf *btf, struct value *val, char *str)
{

	str = type_id_to_str(btf, val->type_id, str);
	if (val->flags & KSNOOP_F_PTR)
		strncat(str, "*", MAX_STR);
	if (strlen(val->name) > 0 &&
	    strcmp(val->name, KSNOOP_RETURN_NAME) != 0)
		strncat(str, val->name, MAX_STR);

	return str;
}

/* based heavily on bpf_object__read_kallsyms_file() in libbpf.c */
static int get_func_ip_mod(struct func *func)
{
	char sym_type, sym_name[MAX_STR], mod_info[MAX_STR];
	unsigned long long sym_addr;
	int ret, err = 0;
	FILE *f;

	f = fopen("/proc/kallsyms", "r");
	if (!f) {
		err = errno;
		p_err("failed to open /proc/kallsyms: %s", strerror(err));
		return err;
	}

	while (true) {
		ret = fscanf(f, "%llx %c %128s%[^\n]\n",
			     &sym_addr, &sym_type, sym_name, mod_info);
		if (ret == EOF && feof(f))
			break;
		if (ret < 3) {
			p_err("failed to read kallsyms entry: %d", ret);
			err = -EINVAL;
			goto out;
		}
		if (strcmp(func->name, sym_name) != 0)
			continue;
		func->ip = sym_addr;
		func->mod[0] = '\0';
		/* get module name from [modname] */
		if (ret == 4) {
			if (sscanf(mod_info, "%*[\t ][%[^]]", func->mod) < 1) {
				p_err("failed to read module name");
				err = -EINVAL;
				goto out;
			}
		}
		p_debug("%s =  <ip %llx, mod %s>", func->name, func->ip,
			strlen(func->mod) > 0 ? func->mod : "vmlinux");
		break;
	}
out:
	fclose(f);
	return err;
}

static void trace_printf(void *ctx, const char *fmt, va_list args)
{
	vprintf(fmt, args);
}

#define VALID_NAME	"%[A-Za-z0-9\\-_]"
#define ARGDATA		"%[^)]"

static int parse_trace(char *str, struct trace *trace)
{
	__u8 i, nr_predicates = 0, nr_entry = 0, nr_return = 0;
	char argname[MAX_NAME], membername[MAX_NAME];
	char tracestr[MAX_STR], argdata[MAX_STR];
	struct func *func = &trace->func;
	char *arg, *saveptr;
	int ret;

	copy_without_spaces(tracestr, str);

	p_debug("Parsing trace '%s'", tracestr);

	trace->filter_pid = (__u32)filter_pid;
	if (filter_pid)
		p_debug("Using pid %lu as filter", trace->filter_pid);

	trace->btf = vmlinux_btf;

	ret = sscanf(tracestr, VALID_NAME "(" ARGDATA ")", func->name, argdata);
	if (ret <= 0)
		usage();
	if (ret == 1) {
		if (strlen(tracestr) > strlen(func->name)) {
			p_err("Invalid function specification '%s'", tracestr);
			usage();
		}
		argdata[0] = '\0';
		p_debug("got func '%s'", func->name);
	} else {
		if (strlen(tracestr) >
		    strlen(func->name) + strlen(argdata) + 2) {
			p_err("Invalid function specification '%s'", tracestr);
			usage();
		}
		p_debug("got func '%s', args '%s'", func->name, argdata);
		trace->flags |= KSNOOP_F_CUSTOM;
	}

	ret = get_func_ip_mod(func);
	if (ret) {
		p_err("could not get address of '%s'", func->name);
		return ret;
	}
	trace->btf = get_btf(func->mod);
	if (!trace->btf) {
		ret = -errno;
		p_err("could not get BTF for '%s': %s",
		      strlen(func->mod) ? func->mod : "vmlinux",
		      strerror(-ret));
		return -ENOENT;
	}
	trace->dump = btf_dump__new(trace->btf, trace_printf, NULL, NULL);
	if (!trace->dump) {
		ret = -errno;
		p_err("could not create BTF dump : %s", strerror(-ret));
		return -EINVAL;
	}

	ret = get_func_btf(trace->btf, func);
	if (ret) {
		p_debug("unexpected return value '%d' getting function", ret);
		return ret;
	}

	for (arg = strtok_r(argdata, ",", &saveptr), i = 0;
	     arg;
	     arg = strtok_r(NULL, ",", &saveptr), i++) {
		char *predicate = NULL;

		ret = sscanf(arg, VALID_NAME "->" VALID_NAME,
			     argname, membername);
		if (ret == 2) {
			if (strlen(arg) >
			    strlen(argname) + strlen(membername) + 2) {
				predicate = arg + strlen(argname) +
					    strlen(membername) + 2;
			}
			p_debug("'%s' dereferences '%s', predicate '%s'",
				argname, membername, predicate);
		} else {
			if (strlen(arg) > strlen(argname))
				predicate = arg + strlen(argname);
			p_debug("'%s' arg, predcate '%s'", argname, predicate);
			membername[0] = '\0';
		}

		if (i >= MAX_TRACES) {
			p_err("Too many arguments; up to %d are supported",
			      MAX_TRACES);
			return -EINVAL;
		}
		if (trace_to_value(trace->btf, func, argname, membername,
				   predicate, &trace->traces[i]))
			return -EINVAL;

		if (predicate)
			nr_predicates++;
		if (trace->traces[i].base_arg == KSNOOP_RETURN)
			nr_return++;
		else
			nr_entry++;
		trace->nr_traces++;
	}

	if (trace->nr_traces > 0) {
		trace->flags |= KSNOOP_F_CUSTOM;
		p_debug("custom trace with %d args", trace->nr_traces);

		/* If we have one or more predicates _and_ references to
		 * entry and return values, we need to activate "stash"
		 * mode where arg traces are stored on entry and not
		 * sent until return to ensure predicates are satisfied.
		 */
		if (nr_predicates > 0 && nr_entry > 0 && nr_return > 0) {
			trace->flags |= KSNOOP_F_STASH;
			p_debug("activating stash mode on entry");
		}
	} else {
		p_debug("Standard trace, function with %d arguments",
			func->nr_args);
		/* copy function arg/return value to trace specification. */
		memcpy(trace->traces, func->args, sizeof(trace->traces));
		for (i = 0; i < MAX_TRACES; i++)
			trace->traces[i].base_arg = i;
		trace->nr_traces = MAX_TRACES;
	}

	return 0;
}

static int parse_traces(int argc, char **argv, struct trace **traces)
{
	__u8 i;

	if (argc == 0)
		usage();

	if (argc > MAX_FUNC_TRACES) {
		p_err("A maximum of %d traces are supported", MAX_FUNC_TRACES);
		return -EINVAL;
	}
	*traces = calloc(argc, sizeof(struct trace));
	if (!*traces) {
		p_err("Could not allocate %d traces", argc);
		return -ENOMEM;
	}
	for (i = 0; i < argc; i++) {
		if (parse_trace(argv[i], &((*traces)[i])))
			return -EINVAL;
		if (!stack_mode || i == 0)
			continue;
		/* tell stack mode trace which function to expect next */
		(*traces)[i].prev_ip = (*traces)[i-1].func.ip;
		(*traces)[i-1].next_ip = (*traces)[i].func.ip;
	}
	return i;
}

static int cmd_info(int argc, char **argv)
{
	struct trace *traces = NULL;
	char str[MAX_STR];
	int nr_traces;
	__u8 i, j;

	nr_traces = parse_traces(argc, argv, &traces);
	if (nr_traces < 0)
		return nr_traces;

	for (i = 0; i < nr_traces; i++) {
		struct func *func = &traces[i].func;

		printf("%s%s(",
		       value_to_str(traces[i].btf, &func->args[KSNOOP_RETURN],
				    str),
		       func->name);
		for (j = 0; j < func->nr_args; j++) {
			if (j > 0)
				printf(", ");
			printf("%s", value_to_str(traces[i].btf, &func->args[j],
						  str));
		}
		if (func->nr_args > MAX_ARGS)
			printf(" /* and %d more args that are not traceable */",
			       func->nr_args - MAX_ARGS);
		printf(");\n");
	}
	free(traces);
	return 0;
}

static void trace_handler(void *ctx, int cpu, void *data, __u32 size)
{
	struct trace *trace = data;
	int i, shown, ret;

	p_debug("got trace, size %d", size);
	if (size < (sizeof(*trace) - MAX_TRACE_BUF)) {
		p_err("\t/* trace buffer size '%u' < min %ld */",
			size, sizeof(trace) - MAX_TRACE_BUF);
		return;
	}
	printf("%16lld %4d %8u %s(\n", trace->time, trace->cpu, trace->pid,
	       trace->func.name);

	for (i = 0, shown = 0; i < trace->nr_traces; i++) {
		DECLARE_LIBBPF_OPTS(btf_dump_type_data_opts, opts);
		bool entry = trace->data_flags & KSNOOP_F_ENTRY;
		struct value *val = &trace->traces[i];
		struct trace_data *data = &trace->trace_data[i];

		opts.indent_level = 36;
		opts.indent_str = " ";

		/* skip if it's entry data and trace data is for return, or
		 * if it's return and trace data is entry; only exception in
		 * the latter case is if we stashed data; in such cases we
		 * want to see it as it's a mix of entry/return data with
		 * predicates.
 		 */
		if ((entry && !base_arg_is_entry(val->base_arg)) ||
		    (!entry && base_arg_is_entry(val->base_arg) &&
		     !(trace->flags & KSNOOP_F_STASH)))
			continue;

		if (val->type_id == 0)
			continue;

		if (shown > 0)
			printf(",\n");
		printf("%34s %s = ", "", val->name);
		if (val->flags & KSNOOP_F_PTR)
			printf("*(0x%llx)", data->raw_value);
		printf("\n");

		if (data->err_type_id != 0) {
			char typestr[MAX_STR];

			printf("%36s /* Cannot show '%s' as '%s%s'; invalid/userspace ptr? */\n",
			       "",
			       val->name,
			       type_id_to_str(trace->btf,
					      val->type_id,
					      typestr),
			       val->flags & KSNOOP_F_PTR ?
			       " *" : "");
		} else {
			ret = btf_dump__dump_type_data
				(trace->dump, val->type_id,
				 trace->buf + data->buf_offset,
				 data->buf_len, &opts);
			/* truncated? */
			if (ret == -E2BIG)
				printf("%36s... /* %d bytes of %d */", "",
				       data->buf_len,
				       val->size);
		}
		shown++;

	}
	printf("\n%31s);\n\n", "");
	fflush(stdout);
}

static void lost_handler(void *ctx, int cpu, __u64 cnt)
{
	p_err("\t/* lost %llu events */", cnt);
}

static void sig_int(int signo)
{
	exiting = 1;
}

static int add_traces(struct bpf_map *func_map, struct trace *traces,
		      int nr_traces)
{
	int i, j, ret, nr_cpus = libbpf_num_possible_cpus();
	struct trace *map_traces;

	map_traces = calloc(nr_cpus, sizeof(struct trace));
	if (!map_traces) {
		p_err("Could not allocate memory for %d traces", nr_traces);
		return -ENOMEM;
	}
	for (i = 0; i < nr_traces; i++) {
		for (j = 0; j < nr_cpus; j++)
			memcpy(&map_traces[j], &traces[i],
			       sizeof(map_traces[j]));

		ret = bpf_map_update_elem(bpf_map__fd(func_map),
					  &traces[i].func.ip,
					  map_traces,
					  BPF_NOEXIST);
		if (ret) {
			p_err("Could not add map entry for '%s': %s",
			      traces[i].func.name, strerror(-ret));
			break;
		}
	}
	free(map_traces);
	return ret;
}

static int attach_traces(struct ksnoop_bpf *skel, struct trace *traces,
			 int nr_traces)
{
	int i, ret;

	for (i = 0; i < nr_traces; i++) {
		traces[i].links[0] =
			bpf_program__attach_kprobe(skel->progs.kprobe_entry,
						   false,
						   traces[i].func.name);
		if (!traces[i].links[0]) {
			ret = -errno;
			p_err("Could not attach kprobe to '%s': %s",
			      traces[i].func.name, strerror(-ret));
				return ret;
		}
		p_debug("Attached kprobe for '%s'", traces[i].func.name);

		traces[i].links[1] =
			bpf_program__attach_kprobe(skel->progs.kprobe_return,
						   true,
						   traces[i].func.name);
		if (!traces[i].links[1]) {
			ret = -errno;
			p_err("Could not attach kretprobe to '%s': %s",
			      traces[i].func.name, strerror(-ret));
			return ret;
		}
		p_debug("Attached kretprobe for '%s'", traces[i].func.name);
	}
	return 0;
}

static int cmd_trace(int argc, char **argv)
{
	struct bpf_map *perf_map, *func_map;
	struct perf_buffer *pb = NULL;
	struct ksnoop_bpf *skel;
	int i, nr_traces, ret = -1;
	struct trace *traces = NULL;

	nr_traces = parse_traces(argc, argv, &traces);
	if (nr_traces < 0)
		return nr_traces;

	skel = ksnoop_bpf__open_and_load();
	if (!skel) {
		ret = -errno;
		p_err("Could not load ksnoop BPF: %s", strerror(-ret));
		return 1;
	}

	perf_map = skel->maps.ksnoop_perf_map;
	if (!perf_map) {
		p_err("Could not find '%s'", "ksnoop_perf_map");
		goto cleanup;
	}
	func_map = bpf_object__find_map_by_name(skel->obj, "ksnoop_func_map");
	if (!func_map) {
		p_err("Could not find '%s'", "ksnoop_func_map");
		goto cleanup;
	}

	if (add_traces(func_map, traces, nr_traces)) {
		p_err("Could not add traces to '%s'", "ksnoop_func_map");
		goto cleanup;
	}

	if (attach_traces(skel, traces, nr_traces)) {
		p_err("Could not attach %d traces", nr_traces);
		goto cleanup;
	}

	pb = perf_buffer__new(bpf_map__fd(perf_map), pages,
			      trace_handler, lost_handler, NULL, NULL);
	if (!pb) {
		ret = -errno;
		p_err("Could not create perf buffer: %s", strerror(-ret));
		goto cleanup;
	}

	printf("%16s %4s %8s %s\n", "TIME", "CPU", "PID", "FUNCTION/ARGS");

	if (signal(SIGINT, sig_int) == SIG_ERR) {
		fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
		ret = 1;
		goto cleanup;
	}

	while (!exiting) {
		ret = perf_buffer__poll(pb, 1);
		if (ret < 0 && ret != -EINTR) {
			fprintf(stderr, "error polling perf buffer: %s\n", strerror(-ret));
			goto cleanup;
		}
		/* reset ret to return 0 if exiting */
		ret = 0;
	}

cleanup:
	for (i = 0; i < nr_traces; i++) {
		bpf_link__destroy(traces[i].links[0]);
		bpf_link__destroy(traces[i].links[1]);
	}
	free(traces);
	perf_buffer__free(pb);
	ksnoop_bpf__destroy(skel);

	return ret;
}

struct cmd {
	const char *cmd;
	int (*func)(int argc, char **argv);
};

struct cmd cmds[] = {
	{ "info",	cmd_info },
	{ "trace",	cmd_trace },
	{ "help",	cmd_help },
	{ NULL,		NULL }
};

static int cmd_select(int argc, char **argv)
{
	int i;

	for (i = 0; cmds[i].cmd; i++) {
		if (strncmp(*argv, cmds[i].cmd, strlen(*argv)) == 0)
			return cmds[i].func(argc - 1, argv + 1);
	}
	return cmd_trace(argc, argv);
}

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
	if (level == LIBBPF_DEBUG && !verbose)
		return 0;
	return vfprintf(stderr, format, args);
}

int main(int argc, char *argv[])
{
	static const struct option options[] = {
		{ "debug",	no_argument,		NULL,	'd' },
		{ "verbose",	no_argument,		NULL,	'v' },
		{ "help",	no_argument,		NULL,	'h' },
		{ "version",	no_argument,		NULL,	'V' },
		{ "pages",	required_argument,	NULL,	'P' },
		{ "pid",	required_argument,	NULL,	'p' },
		{ 0 }
	};
	int opt;

	bin_name = argv[0];

	while ((opt = getopt_long(argc, argv, "dvhp:P:sV", options,
				  NULL)) >= 0) {
		switch (opt) {
		case 'd':
			verbose = true;
			log_level = DEBUG;
			break;
		case 'v':
			verbose = true;
			log_level = DEBUG;
			break;
		case 'h':
			return cmd_help(argc, argv);
		case 'V':
			return do_version(argc, argv);
		case 'p':
			filter_pid = atoi(optarg);
			break;
		case 'P':
			pages = atoi(optarg);
			break;
		case 's':
			stack_mode = true;
			break;
		default:
			p_err("unrecognized option '%s'", argv[optind - 1]);
			usage();
		}
	}
	if (argc == 1)
		usage();
	argc -= optind;
	argv += optind;
	if (argc < 0)
		usage();

	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
	libbpf_set_print(libbpf_print_fn);

	return cmd_select(argc, argv);
}
