text: // SPDX-License-Identifier: GPL-2.0+
  // Copyright (C) 2018 Facebook
  // Author: Yonghong Song <yhs@fb.com>
  
  #define _GNU_SOURCE
  #include <ctype.h>
  #include <errno.h>
  #include <fcntl.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/stat.h>
  #include <sys/types.h>
  #include <unistd.h>
  #include <ftw.h>
  
  #include <bpf.h>
  
  #include "main.h"
  
  /* 0: undecided, 1: supported, 2: not supported */
  static int perf_query_supported;
  static bool has_perf_query_support(void)
  {
  	__u64 probe_offset, probe_addr;
  	__u32 len, prog_id, fd_type;
  	char buf[256];
  	int fd;
  
  	if (perf_query_supported)
  		goto out;
  
  	fd = open("/", O_RDONLY);
  	if (fd < 0) {
  		p_err("perf_query_support: cannot open directory \"/\" (%s)",
  		      strerror(errno));
  		goto out;
  	}
  
  	/* the following query will fail as no bpf attachment,
  	 * the expected errno is ENOTSUPP
  	 */
  	errno = 0;
  	len = sizeof(buf);
  	bpf_task_fd_query(getpid(), fd, 0, buf, &len, &prog_id,
  			  &fd_type, &probe_offset, &probe_addr);
  
  	if (errno == 524 /* ENOTSUPP */) {
  		perf_query_supported = 1;
  		goto close_fd;
  	}
  
  	perf_query_supported = 2;
  	p_err("perf_query_support: %s", strerror(errno));
  	fprintf(stderr,
  		"HINT: non root or kernel doesn't support TASK_FD_QUERY\n");
  
  close_fd:
  	close(fd);
  out:
  	return perf_query_supported == 1;
  }
  
  static void print_perf_json(int pid, int fd, __u32 prog_id, __u32 fd_type,
  			    char *buf, __u64 probe_offset, __u64 probe_addr)
  {
  	jsonw_start_object(json_wtr);
  	jsonw_int_field(json_wtr, "pid", pid);
  	jsonw_int_field(json_wtr, "fd", fd);
  	jsonw_uint_field(json_wtr, "prog_id", prog_id);
  	switch (fd_type) {
  	case BPF_FD_TYPE_RAW_TRACEPOINT:
  		jsonw_string_field(json_wtr, "fd_type", "raw_tracepoint");
  		jsonw_string_field(json_wtr, "tracepoint", buf);
  		break;
  	case BPF_FD_TYPE_TRACEPOINT:
  		jsonw_string_field(json_wtr, "fd_type", "tracepoint");
  		jsonw_string_field(json_wtr, "tracepoint", buf);
  		break;
  	case BPF_FD_TYPE_KPROBE:
  		jsonw_string_field(json_wtr, "fd_type", "kprobe");
  		if (buf[0] != '\0') {
  			jsonw_string_field(json_wtr, "func", buf);
  			jsonw_lluint_field(json_wtr, "offset", probe_offset);
  		} else {
  			jsonw_lluint_field(json_wtr, "addr", probe_addr);
  		}
  		break;
  	case BPF_FD_TYPE_KRETPROBE:
  		jsonw_string_field(json_wtr, "fd_type", "kretprobe");
  		if (buf[0] != '\0') {
  			jsonw_string_field(json_wtr, "func", buf);
  			jsonw_lluint_field(json_wtr, "offset", probe_offset);
  		} else {
  			jsonw_lluint_field(json_wtr, "addr", probe_addr);
  		}
  		break;
  	case BPF_FD_TYPE_UPROBE:
  		jsonw_string_field(json_wtr, "fd_type", "uprobe");
  		jsonw_string_field(json_wtr, "filename", buf);
  		jsonw_lluint_field(json_wtr, "offset", probe_offset);
  		break;
  	case BPF_FD_TYPE_URETPROBE:
  		jsonw_string_field(json_wtr, "fd_type", "uretprobe");
  		jsonw_string_field(json_wtr, "filename", buf);
  		jsonw_lluint_field(json_wtr, "offset", probe_offset);
  		break;
  	}
  	jsonw_end_object(json_wtr);
  }
  
  static void print_perf_plain(int pid, int fd, __u32 prog_id, __u32 fd_type,
  			     char *buf, __u64 probe_offset, __u64 probe_addr)
  {
  	printf("pid %d  fd %d: prog_id %u  ", pid, fd, prog_id);
  	switch (fd_type) {
  	case BPF_FD_TYPE_RAW_TRACEPOINT:
  		printf("raw_tracepoint  %s\n", buf);
  		break;
  	case BPF_FD_TYPE_TRACEPOINT:
  		printf("tracepoint  %s\n", buf);
  		break;
  	case BPF_FD_TYPE_KPROBE:
  		if (buf[0] != '\0')
  			printf("kprobe  func %s  offset %llu\n", buf,
  			       probe_offset);
  		else
  			printf("kprobe  addr %llu\n", probe_addr);
  		break;
  	case BPF_FD_TYPE_KRETPROBE:
  		if (buf[0] != '\0')
  			printf("kretprobe  func %s  offset %llu\n", buf,
  			       probe_offset);
  		else
  			printf("kretprobe  addr %llu\n", probe_addr);
  		break;
  	case BPF_FD_TYPE_UPROBE:
  		printf("uprobe  filename %s  offset %llu\n", buf, probe_offset);
  		break;
  	case BPF_FD_TYPE_URETPROBE:
  		printf("uretprobe  filename %s  offset %llu\n", buf,
  		       probe_offset);
  		break;
  	}
  }
  
  static int show_proc(const char *fpath, const struct stat *sb,
  		     int tflag, struct FTW *ftwbuf)
  {
  	__u64 probe_offset, probe_addr;
  	__u32 len, prog_id, fd_type;
  	int err, pid = 0, fd = 0;
  	const char *pch;
  	char buf[4096];
  
  	/* prefix always /proc */
  	pch = fpath + 5;
  	if (*pch == '\0')
  		return 0;
  
  	/* pid should be all numbers */
  	pch++;
  	while (isdigit(*pch)) {
  		pid = pid * 10 + *pch - '0';
  		pch++;
  	}
  	if (*pch == '\0')
  		return 0;
  	if (*pch != '/')
  		return FTW_SKIP_SUBTREE;
  
  	/* check /proc/<pid>/fd directory */
  	pch++;
  	if (strncmp(pch, "fd", 2))
  		return FTW_SKIP_SUBTREE;
  	pch += 2;
  	if (*pch == '\0')
  		return 0;
  	if (*pch != '/')
  		return FTW_SKIP_SUBTREE;
  
  	/* check /proc/<pid>/fd/<fd_num> */
  	pch++;
  	while (isdigit(*pch)) {
  		fd = fd * 10 + *pch - '0';
  		pch++;
  	}
  	if (*pch != '\0')
  		return FTW_SKIP_SUBTREE;
  
  	/* query (pid, fd) for potential perf events */
  	len = sizeof(buf);
  	err = bpf_task_fd_query(pid, fd, 0, buf, &len, &prog_id, &fd_type,
  				&probe_offset, &probe_addr);
  	if (err < 0)
  		return 0;
  
  	if (json_output)
  		print_perf_json(pid, fd, prog_id, fd_type, buf, probe_offset,
  				probe_addr);
  	else
  		print_perf_plain(pid, fd, prog_id, fd_type, buf, probe_offset,
  				 probe_addr);
  
  	return 0;
  }
  
  static int do_show(int argc, char **argv)
  {
  	int flags = FTW_ACTIONRETVAL | FTW_PHYS;
  	int err = 0, nopenfd = 16;
  
  	if (!has_perf_query_support())
  		return -1;
  
  	if (json_output)
  		jsonw_start_array(json_wtr);
  	if (nftw("/proc", show_proc, nopenfd, flags) == -1) {
  		p_err("%s", strerror(errno));
  		err = -1;
  	}
  	if (json_output)
  		jsonw_end_array(json_wtr);
  
  	return err;
  }
  
  static int do_help(int argc, char **argv)
  {
  	fprintf(stderr,
  		"Usage: %s %s { show | list | help }\n"
  		"",
  		bin_name, argv[-2]);
  
  	return 0;
  }
  
  static const struct cmd cmds[] = {
  	{ "show",	do_show },
  	{ "list",	do_show },
  	{ "help",	do_help },
  	{ 0 }
  };
  
  int do_perf(int argc, char **argv)
  {
  	return cmd_select(cmds, argc, argv, do_help);
  }
  