diff options
Diffstat (limited to 'misc-utils/lsfd-unkn.c')
-rw-r--r-- | misc-utils/lsfd-unkn.c | 1134 |
1 files changed, 1123 insertions, 11 deletions
diff --git a/misc-utils/lsfd-unkn.c b/misc-utils/lsfd-unkn.c index 087e31d..8f6e908 100644 --- a/misc-utils/lsfd-unkn.c +++ b/misc-utils/lsfd-unkn.c @@ -19,12 +19,19 @@ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "xalloc.h" -#include "nls.h" -#include "libsmartcols.h" +#include <linux/bpf.h> +#include <sys/syscall.h> +#include <sys/timerfd.h> +#include <time.h> + +#include "signames.h" +#include "timeutils.h" #include "lsfd.h" +#define offsetofend(TYPE, MEMBER) \ + (offsetof(TYPE, MEMBER) + sizeof_member(TYPE, MEMBER)) + struct unkn { struct file file; const struct anon_ops *anon_ops; @@ -33,6 +40,7 @@ struct unkn { struct anon_ops { const char *class; + bool (*probe)(const char *); char * (*get_name)(struct unkn *); /* Return true is handled the column. */ bool (*fill_column)(struct proc *, @@ -44,10 +52,11 @@ struct anon_ops { void (*init)(struct unkn *); void (*free)(struct unkn *); int (*handle_fdinfo)(struct unkn *, const char *, const char *); + void (*attach_xinfo)(struct unkn *); + const struct ipc_class *ipc_class; }; -static const struct anon_ops anon_generic_ops; -static const struct anon_ops anon_pidfd_ops; +static const struct anon_ops *anon_probe(const char *); static char * anon_get_class(struct unkn *unkn) { @@ -117,6 +126,22 @@ static bool unkn_fill_column(struct proc *proc, return true; } +static void unkn_attach_xinfo(struct file *file) +{ + struct unkn *unkn = (struct unkn *)file; + if (unkn->anon_ops && unkn->anon_ops->attach_xinfo) + unkn->anon_ops->attach_xinfo(unkn); +} + +static const struct ipc_class *unkn_get_ipc_class(struct file *file) +{ + struct unkn *unkn = (struct unkn *)file; + + if (unkn->anon_ops && unkn->anon_ops->ipc_class) + return unkn->anon_ops->ipc_class; + return NULL; +} + static void unkn_init_content(struct file *file) { struct unkn *unkn = (struct unkn *)file; @@ -129,10 +154,7 @@ static void unkn_init_content(struct file *file) && strncmp(file->name, "anon_inode:", 11) == 0) { const char *rest = file->name + 11; - if (strncmp(rest, "[pidfd]", 7) == 0) - unkn->anon_ops = &anon_pidfd_ops; - else - unkn->anon_ops = &anon_generic_ops; + unkn->anon_ops = anon_probe(rest); if (unkn->anon_ops->init) unkn->anon_ops->init(unkn); @@ -166,6 +188,11 @@ struct anon_pidfd_data { char *nspid; }; +static bool anon_pidfd_probe(const char *str) +{ + return strncmp(str, "[pidfd]", 7) == 0; +} + static char *anon_pidfd_get_name(struct unkn *unkn) { char *str = NULL; @@ -207,8 +234,7 @@ static int anon_pidfd_handle_fdinfo(struct unkn *unkn, const char *key, const ch return 0; /* ignore -- parse failed */ ((struct anon_pidfd_data *)unkn->anon_data)->pid = (pid_t)pid; return 1; - } - else if (strcmp(key, "NSpid") == 0) { + } else if (strcmp(key, "NSpid") == 0) { ((struct anon_pidfd_data *)unkn->anon_data)->nspid = xstrdup(value); return 1; @@ -253,6 +279,7 @@ static bool anon_pidfd_fill_column(struct proc *proc __attribute__((__unused__) static const struct anon_ops anon_pidfd_ops = { .class = "pidfd", + .probe = anon_pidfd_probe, .get_name = anon_pidfd_get_name, .fill_column = anon_pidfd_fill_column, .init = anon_pidfd_init, @@ -261,6 +288,1070 @@ static const struct anon_ops anon_pidfd_ops = { }; /* + * eventfd + */ +struct anon_eventfd_data { + int id; + struct unkn *backptr; + struct ipc_endpoint endpoint; +}; + +struct eventfd_ipc { + struct ipc ipc; + int id; +}; + +static unsigned int anon_eventfd_get_hash(struct file *file) +{ + struct unkn *unkn = (struct unkn *)file; + struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data; + + return (unsigned int)data->id; +} + +static bool anon_eventfd_is_suitable_ipc(struct ipc *ipc, struct file *file) +{ + struct unkn *unkn = (struct unkn *)file; + struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data; + + return ((struct eventfd_ipc *)ipc)->id == data->id; +} + +static const struct ipc_class anon_eventfd_ipc_class = { + .size = sizeof(struct eventfd_ipc), + .get_hash = anon_eventfd_get_hash, + .is_suitable_ipc = anon_eventfd_is_suitable_ipc, + .free = NULL, +}; + +static bool anon_eventfd_probe(const char *str) +{ + return strncmp(str, "[eventfd]", 9) == 0; +} + +static char *anon_eventfd_get_name(struct unkn *unkn) +{ + char *str = NULL; + struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data; + + xasprintf(&str, "id=%d", data->id); + return str; +} + +static void anon_eventfd_init(struct unkn *unkn) +{ + struct anon_eventfd_data *data = xcalloc(1, sizeof(struct anon_eventfd_data)); + init_endpoint(&data->endpoint); + data->backptr = unkn; + unkn->anon_data = data; +} + +static void anon_eventfd_free(struct unkn *unkn) +{ + free(unkn->anon_data); +} + +static void anon_eventfd_attach_xinfo(struct unkn *unkn) +{ + struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data; + unsigned int hash; + struct ipc *ipc = get_ipc(&unkn->file); + if (ipc) + goto link; + + ipc = new_ipc(&anon_eventfd_ipc_class); + ((struct eventfd_ipc *)ipc)->id = data->id; + + hash = anon_eventfd_get_hash(&unkn->file); + add_ipc(ipc, hash); + + link: + add_endpoint(&data->endpoint, ipc); +} + +static int anon_eventfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + if (strcmp(key, "eventfd-id") == 0) { + int64_t id; + + int rc = ul_strtos64(value, &id, 10); + if (rc < 0) + return 0; + ((struct anon_eventfd_data *)unkn->anon_data)->id = (int)id; + return 1; + } + return 0; +} + +static inline char *anon_eventfd_data_xstrendpoint(struct file *file) +{ + char *str = NULL; + xasprintf(&str, "%d,%s,%d", + file->proc->pid, file->proc->command, file->association); + return str; +} + +static bool anon_eventfd_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data; + + switch(column_id) { + case COL_EVENTFD_ID: + xasprintf(str, "%d", data->id); + return true; + case COL_ENDPOINTS: { + struct list_head *e; + char *estr; + foreach_endpoint(e, data->endpoint) { + struct anon_eventfd_data *other = list_entry(e, + struct anon_eventfd_data, + endpoint.endpoints); + if (data == other) + continue; + if (*str) + xstrputc(str, '\n'); + estr = anon_eventfd_data_xstrendpoint(&other->backptr->file); + xstrappend(str, estr); + free(estr); + } + if (!*str) + return false; + return true; + } + default: + return false; + } +} + +static const struct anon_ops anon_eventfd_ops = { + .class = "eventfd", + .probe = anon_eventfd_probe, + .get_name = anon_eventfd_get_name, + .fill_column = anon_eventfd_fill_column, + .init = anon_eventfd_init, + .free = anon_eventfd_free, + .handle_fdinfo = anon_eventfd_handle_fdinfo, + .attach_xinfo = anon_eventfd_attach_xinfo, + .ipc_class = &anon_eventfd_ipc_class, +}; + +/* + * eventpoll + */ +struct anon_eventpoll_data { + size_t count; + int *tfds; + struct list_head siblings; +}; + +static bool anon_eventpoll_probe(const char *str) +{ + return strncmp(str, "[eventpoll]", 11) == 0; +} + +static void anon_eventpoll_init(struct unkn *unkn) +{ + struct anon_eventpoll_data *data = xcalloc(1, sizeof(struct anon_eventpoll_data)); + INIT_LIST_HEAD(&data->siblings); + unkn->anon_data = data; +} + +static void anon_eventpoll_free(struct unkn *unkn) +{ + struct anon_eventpoll_data *data = unkn->anon_data; + free(data->tfds); + free(data); +} + +static int anon_eventpoll_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + struct anon_eventpoll_data *data; + if (strcmp(key, "tfd") == 0) { + unsigned long tfd; + char *end = NULL; + + errno = 0; + tfd = strtoul(value, &end, 0); + if (errno != 0) + return 0; /* ignore -- parse failed */ + + data = (struct anon_eventpoll_data *)unkn->anon_data; + data->tfds = xreallocarray(data->tfds, ++data->count, sizeof(int)); + data->tfds[data->count - 1] = (int)tfd; + return 1; + } + return 0; +} + +static int intcmp(const void *a, const void *b) +{ + int ai = *(int *)a; + int bi = *(int *)b; + + return ai - bi; +} + +static void anon_eventpoll_attach_xinfo(struct unkn *unkn) +{ + struct anon_eventpoll_data *data = (struct anon_eventpoll_data *)unkn->anon_data; + if (data->count > 0) { + qsort(data->tfds, data->count, sizeof(data->tfds[0]), + intcmp); + list_add_tail(&data->siblings, + &unkn->file.proc->eventpolls); + } +} + +static char *anon_eventpoll_make_tfds_string(struct anon_eventpoll_data *data, + const char *prefix, + const char sep) +{ + char *str = prefix? xstrdup(prefix): NULL; + + char buf[256]; + for (size_t i = 0; i < data->count; i++) { + size_t offset = 0; + + if (i > 0) { + buf[0] = sep; + offset = 1; + } + snprintf(buf + offset, sizeof(buf) - offset, "%d", data->tfds[i]); + xstrappend(&str, buf); + } + return str; +} + +static char *anon_eventpoll_get_name(struct unkn *unkn) +{ + return anon_eventpoll_make_tfds_string((struct anon_eventpoll_data *)unkn->anon_data, + "tfds=", ','); +} + +static bool anon_eventpoll_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_eventpoll_data *data = (struct anon_eventpoll_data *)unkn->anon_data; + + switch(column_id) { + case COL_EVENTPOLL_TFDS: + *str =anon_eventpoll_make_tfds_string(data, NULL, '\n'); + if (*str) + return true; + break; + } + + return false; +} + +static const struct anon_ops anon_eventpoll_ops = { + .class = "eventpoll", + .probe = anon_eventpoll_probe, + .get_name = anon_eventpoll_get_name, + .fill_column = anon_eventpoll_fill_column, + .init = anon_eventpoll_init, + .free = anon_eventpoll_free, + .handle_fdinfo = anon_eventpoll_handle_fdinfo, + .attach_xinfo = anon_eventpoll_attach_xinfo, +}; + +static int numcomp(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +bool is_multiplexed_by_eventpoll(int fd, struct list_head *eventpolls) +{ + struct list_head *t; + list_for_each (t, eventpolls) { + struct anon_eventpoll_data *data = list_entry(t, struct anon_eventpoll_data, siblings); + if (data->count) { + if (bsearch(&fd, data->tfds, + data->count, sizeof(data->tfds[0]), + numcomp)) + return true; + } + } + return false; +} + +/* + * timerfd + */ +struct anon_timerfd_data { + int clockid; + struct itimerspec itimerspec; +}; + +static bool anon_timerfd_probe(const char *str) +{ + return strncmp(str, "[timerfd]", 9) == 0; +} + +static void anon_timerfd_init(struct unkn *unkn) +{ + unkn->anon_data = xcalloc(1, sizeof(struct anon_timerfd_data)); +} + +static void anon_timerfd_free(struct unkn *unkn) +{ + struct anon_timerfd_data *data = unkn->anon_data; + free(data); +} + +static int anon_timerfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + struct anon_timerfd_data *data = (struct anon_timerfd_data *)unkn->anon_data; + + if (strcmp(key, "clockid") == 0) { + unsigned long clockid; + char *end = NULL; + + errno = 0; + clockid = strtoul(value, &end, 0); + if (errno != 0) + return 0; /* ignore -- parse failed */ + if (*end != '\0') + return 0; /* ignore -- garbage remains. */ + + data->clockid = clockid; + return 1; + } else { + struct timespec *t; + uint64_t tv_sec; + uint64_t tv_nsec; + + if (strcmp(key, "it_value") == 0) + t = &data->itimerspec.it_value; + else if (strcmp(key, "it_interval") == 0) + t = &data->itimerspec.it_interval; + else + return 0; + + if (sscanf(value, "(%"SCNu64", %"SCNu64")", + &tv_sec, &tv_nsec) == 2) { + t->tv_sec = (time_t)tv_sec; + t->tv_nsec = (long)tv_nsec; + return 1; + } + + return 0; + } +} + +static const char *anon_timerfd_decode_clockid(int clockid) +{ + switch (clockid) { + case CLOCK_REALTIME: + return "realtime"; + case CLOCK_MONOTONIC: + return "monotonic"; + case CLOCK_BOOTTIME: + return "boottime"; + case CLOCK_REALTIME_ALARM: + return "realtime-alarm"; + case CLOCK_BOOTTIME_ALARM: + return "boottime-alarm"; + default: + return "unknown"; + } +} + +static void anon_timerfd_render_timespec_string(char *buf, size_t size, + const char *prefix, + const struct timespec *t) +{ + snprintf(buf, size, "%s%llu.%09ld", + prefix? prefix: "", + (unsigned long long)t->tv_sec, t->tv_nsec); +} + +static char *anon_timerfd_get_name(struct unkn *unkn) +{ + char *str = NULL; + + struct anon_timerfd_data *data = (struct anon_timerfd_data *)unkn->anon_data; + const struct timespec *exp; + const struct timespec *ival; + + const char *clockid_name; + char exp_buf[BUFSIZ] = {'\0'}; + char ival_buf[BUFSIZ] = {'\0'}; + + clockid_name = anon_timerfd_decode_clockid(data->clockid); + + exp = &data->itimerspec.it_value; + if (is_timespecset(exp)) + anon_timerfd_render_timespec_string(exp_buf, sizeof(exp_buf), + " remaining=", exp); + + ival = &data->itimerspec.it_interval; + if (is_timespecset(ival)) + anon_timerfd_render_timespec_string(ival_buf, sizeof(ival_buf), + " interval=", ival); + + xasprintf(&str, "clockid=%s%s%s", clockid_name, exp_buf, ival_buf); + return str; +} + +static bool anon_timerfd_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_timerfd_data *data = (struct anon_timerfd_data *)unkn->anon_data; + char buf[BUFSIZ] = {'\0'}; + + switch(column_id) { + case COL_TIMERFD_CLOCKID: + *str = xstrdup(anon_timerfd_decode_clockid(data->clockid)); + return true; + case COL_TIMERFD_INTERVAL: + anon_timerfd_render_timespec_string(buf, sizeof(buf), NULL, + &data->itimerspec.it_interval); + *str = xstrdup(buf); + return true; + case COL_TIMERFD_REMAINING: + anon_timerfd_render_timespec_string(buf, sizeof(buf), NULL, + &data->itimerspec.it_value); + *str = xstrdup(buf); + return true; + } + + return false; +} + +static const struct anon_ops anon_timerfd_ops = { + .class = "timerfd", + .probe = anon_timerfd_probe, + .get_name = anon_timerfd_get_name, + .fill_column = anon_timerfd_fill_column, + .init = anon_timerfd_init, + .free = anon_timerfd_free, + .handle_fdinfo = anon_timerfd_handle_fdinfo, +}; + +/* + * signalfd + */ +struct anon_signalfd_data { + uint64_t sigmask; +}; + +static bool anon_signalfd_probe(const char *str) +{ + return strncmp(str, "[signalfd]", 10) == 0; +} + +static void anon_signalfd_init(struct unkn *unkn) +{ + unkn->anon_data = xcalloc(1, sizeof(struct anon_signalfd_data)); +} + +static void anon_signalfd_free(struct unkn *unkn) +{ + struct anon_signalfd_data *data = unkn->anon_data; + free(data); +} + +static int anon_signalfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + struct anon_signalfd_data *data = (struct anon_signalfd_data *)unkn->anon_data; + + if (strcmp(key, "sigmask") == 0) { + if (ul_strtou64(value, &data->sigmask, 16) < 0) { + data->sigmask = 0; + return 0; + } + } + return 0; +} + +static char *anon_signalfd_make_mask_string(const char* prefix, uint64_t sigmask) +{ + char *str = NULL; + + for (size_t i = 0; i < sizeof(sigmask) * 8; i++) { + if ((((uint64_t)0x1) << i) & sigmask) { + const int signum = i + 1; + const char *signame = signum_to_signame(signum); + + if (str) + xstrappend(&str, ","); + else if (prefix) + xstrappend(&str, prefix); + + if (signame) { + xstrappend(&str, signame); + } else { + char buf[BUFSIZ]; + snprintf(buf, sizeof(buf), "%d", signum); + xstrappend(&str, buf); + } + } + } + + return str; +} + +static char *anon_signalfd_get_name(struct unkn *unkn) +{ + struct anon_signalfd_data *data = (struct anon_signalfd_data *)unkn->anon_data; + return anon_signalfd_make_mask_string("mask=", data->sigmask); +} + +static bool anon_signalfd_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_signalfd_data *data = (struct anon_signalfd_data *)unkn->anon_data; + + switch(column_id) { + case COL_SIGNALFD_MASK: + *str = anon_signalfd_make_mask_string(NULL, data->sigmask); + return true; + default: + return false; + } +} + +static const struct anon_ops anon_signalfd_ops = { + .class = "signalfd", + .probe = anon_signalfd_probe, + .get_name = anon_signalfd_get_name, + .fill_column = anon_signalfd_fill_column, + .init = anon_signalfd_init, + .free = anon_signalfd_free, + .handle_fdinfo = anon_signalfd_handle_fdinfo, +}; + +/* + * inotify + */ +struct anon_inotify_data { + struct list_head inodes; +}; + +struct anon_inotify_inode { + ino_t ino; + dev_t sdev; + struct list_head inodes; +}; + +static bool anon_inotify_probe(const char *str) +{ + return strncmp(str, "inotify", 7) == 0; +} + +/* A device number appeared in fdinfo of an inotify file uses the kernel + * internal representation. It is different from what we are familiar with; + * major(3) and minor(3) don't work with the representation. + * See linux/include/linux/kdev_t.h. */ +#define ANON_INOTIFY_MINORBITS 20 +#define ANON_INOTIFY_MINORMASK ((1U << ANON_INOTIFY_MINORBITS) - 1) + +#define ANON_INOTIFY_MAJOR(dev) ((unsigned int) ((dev) >> ANON_INOTIFY_MINORBITS)) +#define ANON_INOTIFY_MINOR(dev) ((unsigned int) ((dev) & ANON_INOTIFY_MINORMASK)) + +static char *anon_inotify_make_inodes_string(const char *prefix, + const char *sep, + enum decode_source_level decode_level, + struct anon_inotify_data *data) +{ + char *str = NULL; + char buf[BUFSIZ] = {'\0'}; + bool first_element = true; + + struct list_head *i; + list_for_each(i, &data->inodes) { + char source[BUFSIZ/2] = {'\0'}; + struct anon_inotify_inode *inode = list_entry(i, + struct anon_inotify_inode, + inodes); + + decode_source(source, sizeof(source), + ANON_INOTIFY_MAJOR(inode->sdev), ANON_INOTIFY_MINOR(inode->sdev), + decode_level); + snprintf(buf, sizeof(buf), "%s%llu@%s", first_element? prefix: sep, + (unsigned long long)inode->ino, source); + first_element = false; + + xstrappend(&str, buf); + } + + return str; +} + +static char *anon_inotify_get_name(struct unkn *unkn) +{ + return anon_inotify_make_inodes_string("inodes=", ",", DECODE_SOURCE_FULL, + (struct anon_inotify_data *)unkn->anon_data); +} + +static void anon_inotify_init(struct unkn *unkn) +{ + struct anon_inotify_data *data = xcalloc(1, sizeof(struct anon_inotify_data)); + INIT_LIST_HEAD (&data->inodes); + unkn->anon_data = data; +} + +static void anon_inotify_free(struct unkn *unkn) +{ + struct anon_inotify_data *data = unkn->anon_data; + + list_free(&data->inodes, struct anon_inotify_inode, inodes, + free); + free(data); +} + +static void add_inode(struct anon_inotify_data *data, ino_t ino, dev_t sdev) +{ + struct anon_inotify_inode *inode = xmalloc(sizeof(*inode)); + + INIT_LIST_HEAD (&inode->inodes); + inode->ino = ino; + inode->sdev = sdev; + + list_add_tail(&inode->inodes, &data->inodes); +} + +static int anon_inotify_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + struct anon_inotify_data *data = (struct anon_inotify_data *)unkn->anon_data; + + if (strcmp(key, "inotify wd") == 0) { + unsigned long long ino; + unsigned long long sdev; + + if (sscanf(value, "%*d ino:%llx sdev:%llx %*s", &ino, &sdev) == 2) { + add_inode(data, (ino_t)ino, (dev_t)sdev); + return 1; + } + } + return 0; +} + +static bool anon_inotify_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_inotify_data *data = (struct anon_inotify_data *)unkn->anon_data; + + switch(column_id) { + case COL_INOTIFY_INODES: + *str = anon_inotify_make_inodes_string("", "\n", DECODE_SOURCE_FULL, + data); + if (*str) + return true; + break; + case COL_INOTIFY_INODES_RAW: + *str = anon_inotify_make_inodes_string("", "\n", DECODE_SOURCE_MAJMIN, + data); + if (*str) + return true; + break; + } + + return false; +} + +static const struct anon_ops anon_inotify_ops = { + .class = "inotify", + .probe = anon_inotify_probe, + .get_name = anon_inotify_get_name, + .fill_column = anon_inotify_fill_column, + .init = anon_inotify_init, + .free = anon_inotify_free, + .handle_fdinfo = anon_inotify_handle_fdinfo, +}; + +/* + * bpf-prog + * + * Generally, we use "-" as the word separators in lsfd's output. + * However, about bpf*, we use "_" because bpftool uses "_". + */ +static const char *bpf_prog_type_table[] = { + [0] = "unspec", /* BPF_PROG_TYPE_UNSPEC*/ + [1] = "socket_filter", /* BPF_PROG_TYPE_SOCKET_FILTER*/ + [2] = "kprobe", /* BPF_PROG_TYPE_KPROBE*/ + [3] = "sched_cls", /* BPF_PROG_TYPE_SCHED_CLS*/ + [4] = "sched_act", /* BPF_PROG_TYPE_SCHED_ACT*/ + [5] = "tracepoint", /* BPF_PROG_TYPE_TRACEPOINT*/ + [6] = "xdp", /* BPF_PROG_TYPE_XDP*/ + [7] = "perf_event", /* BPF_PROG_TYPE_PERF_EVENT*/ + [8] = "cgroup_skb", /* BPF_PROG_TYPE_CGROUP_SKB*/ + [9] = "cgroup_sock", /* BPF_PROG_TYPE_CGROUP_SOCK*/ + [10] = "lwt_in", /* BPF_PROG_TYPE_LWT_IN*/ + [11] = "lwt_out", /* BPF_PROG_TYPE_LWT_OUT*/ + [12] = "lwt_xmit", /* BPF_PROG_TYPE_LWT_XMIT*/ + [13] = "sock_ops", /* BPF_PROG_TYPE_SOCK_OPS*/ + [14] = "sk_skb", /* BPF_PROG_TYPE_SK_SKB*/ + [15] = "cgroup_device", /* BPF_PROG_TYPE_CGROUP_DEVICE*/ + [16] = "sk_msg", /* BPF_PROG_TYPE_SK_MSG*/ + [17] = "raw_tracepoint", /* BPF_PROG_TYPE_RAW_TRACEPOINT*/ + [18] = "cgroup_sock_addr", /* BPF_PROG_TYPE_CGROUP_SOCK_ADDR*/ + [19] = "lwt_seg6local", /* BPF_PROG_TYPE_LWT_SEG6LOCAL*/ + [20] = "lirc_mode2", /* BPF_PROG_TYPE_LIRC_MODE2*/ + [21] = "sk_reuseport", /* BPF_PROG_TYPE_SK_REUSEPORT*/ + [22] = "flow_dissector", /* BPF_PROG_TYPE_FLOW_DISSECTOR*/ + [23] = "cgroup_sysctl", /* BPF_PROG_TYPE_CGROUP_SYSCTL*/ + [24] = "raw_tracepoint_writable", /* BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE*/ + [25] = "cgroup_sockopt", /* BPF_PROG_TYPE_CGROUP_SOCKOPT*/ + [26] = "tracing", /* BPF_PROG_TYPE_TRACING*/ + [27] = "struct_ops", /* BPF_PROG_TYPE_STRUCT_OPS*/ + [28] = "ext", /* BPF_PROG_TYPE_EXT*/ + [29] = "lsm", /* BPF_PROG_TYPE_LSM*/ + [30] = "sk_lookup", /* BPF_PROG_TYPE_SK_LOOKUP*/ + [31] = "syscall", /* BPF_PROG_TYPE_SYSCALL*/ +}; + +struct anon_bpf_prog_data { + int type; + int id; + char name[BPF_OBJ_NAME_LEN + 1]; +}; + +static bool anon_bpf_prog_probe(const char *str) +{ + return strncmp(str, "bpf-prog", 8) == 0; +} + +static const char *anon_bpf_prog_get_prog_type_name(int type) +{ + if (0 <= type && type < (int)ARRAY_SIZE(bpf_prog_type_table)) + return bpf_prog_type_table[type]; + return NULL; +} + +static bool anon_bpf_prog_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data; + const char *t; + + switch(column_id) { + case COL_BPF_PROG_ID: + xasprintf(str, "%d", data->id); + return true; + case COL_BPF_PROG_TYPE_RAW: + xasprintf(str, "%d", data->type); + return true; + case COL_BPF_PROG_TYPE: + t = anon_bpf_prog_get_prog_type_name(data->type); + if (t) + *str = xstrdup(t); + else + xasprintf(str, "UNKNOWN(%d)", data->type); + return true; + case COL_BPF_NAME: + *str = xstrdup(data->name); + return true; + default: + return false; + } +} + +static char *anon_bpf_prog_get_name(struct unkn *unkn) +{ + const char *t; + char *str = NULL; + struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data; + + t = anon_bpf_prog_get_prog_type_name(data->type); + if (t) + xasprintf(&str, "id=%d type=%s", data->id, t); + else + xasprintf(&str, "id=%d type=UNKNOWN(%d)", data->id, data->type); + + if (*data->name) + xstrfappend(&str, " name=%s", data->name); + + return str; +} + + +static void anon_bpf_prog_init(struct unkn *unkn) +{ + struct anon_bpf_prog_data *data = xmalloc(sizeof(*data)); + data->type = -1; + data->id = -1; + data->name[0] = '\0'; + unkn->anon_data = data; +} + +static void anon_bpf_prog_free(struct unkn *unkn) +{ + struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data; + free(data); +} + +static void anon_bpf_prog_get_more_info(struct anon_bpf_prog_data *prog_data) +{ + union bpf_attr attr = { + .prog_id = (int32_t)prog_data->id, + .next_id = 0, + .open_flags = 0, + }; + struct bpf_prog_info info = { 0 }; + union bpf_attr info_attr = { + .info.info_len = sizeof(info), + .info.info = (uint64_t)(uintptr_t)&info, + }; + + int bpf_fd = syscall(SYS_bpf, BPF_PROG_GET_FD_BY_ID, &attr, sizeof(attr)); + if (bpf_fd < 0) + return; + + info_attr.info.bpf_fd = bpf_fd; + if (syscall(SYS_bpf, BPF_OBJ_GET_INFO_BY_FD, &info_attr, offsetofend(union bpf_attr, info)) == 0) { + memcpy(prog_data->name, + info.name, + BPF_OBJ_NAME_LEN); + prog_data->name[BPF_OBJ_NAME_LEN] = '\0'; + } + close(bpf_fd); +} + +static int anon_bpf_prog_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + if (strcmp(key, "prog_id") == 0) { + int32_t t = -1; + int rc = ul_strtos32(value, &t, 10); + if (rc < 0) + return 0; /* ignore -- parse failed */ + ((struct anon_bpf_prog_data *)unkn->anon_data)->id = (int)t; + anon_bpf_prog_get_more_info((struct anon_bpf_prog_data *)unkn->anon_data); + return 1; + } + + if (strcmp(key, "prog_type") == 0) { + int32_t t = -1; + int rc = ul_strtos32(value, &t, 10); + if (rc < 0) + return 0; /* ignore -- parse failed */ + ((struct anon_bpf_prog_data *)unkn->anon_data)->type = (int)t; + return 1; + } + + return 0; +} + +static const struct anon_ops anon_bpf_prog_ops = { + .class = "bpf-prog", + .probe = anon_bpf_prog_probe, + .get_name = anon_bpf_prog_get_name, + .fill_column = anon_bpf_prog_fill_column, + .init = anon_bpf_prog_init, + .free = anon_bpf_prog_free, + .handle_fdinfo = anon_bpf_prog_handle_fdinfo, +}; + +/* + * bpf-map + */ +static const char *bpf_map_type_table[] = { + [0] = "unspec", /* BPF_MAP_TYPE_UNSPEC */ + [1] = "hash", /* BPF_MAP_TYPE_HASH */ + [2] = "array", /* BPF_MAP_TYPE_ARRAY */ + [3] = "prog-array", /* BPF_MAP_TYPE_PROG_ARRAY */ + [4] = "perf-event-array", /* BPF_MAP_TYPE_PERF_EVENT_ARRAY */ + [5] = "percpu-hash", /* BPF_MAP_TYPE_PERCPU_HASH */ + [6] = "percpu-array", /* BPF_MAP_TYPE_PERCPU_ARRAY */ + [7] = "stack-trace", /* BPF_MAP_TYPE_STACK_TRACE */ + [8] = "cgroup-array", /* BPF_MAP_TYPE_CGROUP_ARRAY */ + [9] = "lru-hash", /* BPF_MAP_TYPE_LRU_HASH */ + [10] = "lru-percpu-hash", /* BPF_MAP_TYPE_LRU_PERCPU_HASH */ + [11] = "lpm-trie", /* BPF_MAP_TYPE_LPM_TRIE */ + [12] = "array-of-maps", /* BPF_MAP_TYPE_ARRAY_OF_MAPS */ + [13] = "hash-of-maps", /* BPF_MAP_TYPE_HASH_OF_MAPS */ + [14] = "devmap", /* BPF_MAP_TYPE_DEVMAP */ + [15] = "sockmap", /* BPF_MAP_TYPE_SOCKMAP */ + [16] = "cpumap", /* BPF_MAP_TYPE_CPUMAP */ + [17] = "xskmap", /* BPF_MAP_TYPE_XSKMAP */ + [18] = "sockhash", /* BPF_MAP_TYPE_SOCKHASH */ + [19] = "cgroup-storage", /* BPF_MAP_TYPE_CGROUP_STORAGE */ + [20] = "reuseport-sockarray", /* BPF_MAP_TYPE_REUSEPORT_SOCKARRAY */ + [21] = "percpu-cgroup-storage", /* BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE */ + [22] = "queue", /* BPF_MAP_TYPE_QUEUE */ + [23] = "stack", /* BPF_MAP_TYPE_STACK */ + [24] = "sk-storage", /* BPF_MAP_TYPE_SK_STORAGE */ + [25] = "devmap-hash", /* BPF_MAP_TYPE_DEVMAP_HASH */ + [26] = "struct-ops", /* BPF_MAP_TYPE_STRUCT_OPS */ + [27] = "ringbuf", /* BPF_MAP_TYPE_RINGBUF */ + [28] = "inode-storage", /* BPF_MAP_TYPE_INODE_STORAGE */ + [29] = "task-storage", /* BPF_MAP_TYPE_TASK_STORAGE */ + [30] = "bloom-filter", /* BPF_MAP_TYPE_BLOOM_FILTER */ + [31] = "user-ringbuf", /* BPF_MAP_TYPE_USER_RINGBUF */ + [32] = "cgrp-storage", /* BPF_MAP_TYPE_CGRP_STORAGE */ +}; + +struct anon_bpf_map_data { + int type; + int id; + char name[BPF_OBJ_NAME_LEN + 1]; +}; + +static bool anon_bpf_map_probe(const char *str) +{ + return strncmp(str, "bpf-map", 8) == 0; +} + +static const char *anon_bpf_map_get_map_type_name(int type) +{ + if (0 <= type && type < (int)ARRAY_SIZE(bpf_map_type_table)) + return bpf_map_type_table[type]; + return NULL; +} + +static bool anon_bpf_map_fill_column(struct proc *proc __attribute__((__unused__)), + struct unkn *unkn, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data; + const char *t; + + switch(column_id) { + case COL_BPF_MAP_ID: + xasprintf(str, "%d", data->id); + return true; + case COL_BPF_MAP_TYPE_RAW: + xasprintf(str, "%d", data->type); + return true; + case COL_BPF_MAP_TYPE: + t = anon_bpf_map_get_map_type_name(data->type); + if (t) + *str = xstrdup(t); + else + xasprintf(str, "UNKNOWN(%d)", data->type); + return true; + case COL_BPF_NAME: + *str = xstrdup(data->name); + return true; + default: + return false; + } +} + +static char *anon_bpf_map_get_name(struct unkn *unkn) +{ + const char *t; + char *str = NULL; + struct anon_bpf_map_data *data = (struct anon_bpf_map_data *)unkn->anon_data; + + t = anon_bpf_map_get_map_type_name(data->type); + if (t) + xasprintf(&str, "id=%d type=%s", data->id, t); + else + xasprintf(&str, "id=%d type=UNKNOWN(%d)", data->id, data->type); + + if (*data->name) + xstrfappend(&str, " name=%s", data->name); + + return str; +} + +static void anon_bpf_map_init(struct unkn *unkn) +{ + struct anon_bpf_map_data *data = xmalloc(sizeof(*data)); + data->type = -1; + data->id = -1; + data->name[0] = '\0'; + unkn->anon_data = data; +} + +static void anon_bpf_map_free(struct unkn *unkn) +{ + struct anon_bpf_map_data *data = (struct anon_bpf_map_data *)unkn->anon_data; + free(data); +} + +static void anon_bpf_map_get_more_info(struct anon_bpf_map_data *map_data) +{ + union bpf_attr attr = { + .map_id = (int32_t)map_data->id, + .next_id = 0, + .open_flags = 0, + }; + struct bpf_map_info info = { 0 }; + union bpf_attr info_attr = { + .info.info_len = sizeof(info), + .info.info = (uint64_t)(uintptr_t)&info, + }; + + int bpf_fd = syscall(SYS_bpf, BPF_MAP_GET_FD_BY_ID, &attr, sizeof(attr)); + if (bpf_fd < 0) + return; + + info_attr.info.bpf_fd = bpf_fd; + if (syscall(SYS_bpf, BPF_OBJ_GET_INFO_BY_FD, &info_attr, offsetofend(union bpf_attr, info)) == 0) { + memcpy(map_data->name, + info.name, + BPF_OBJ_NAME_LEN); + map_data->name[BPF_OBJ_NAME_LEN] = '\0'; + } + close(bpf_fd); +} + +static int anon_bpf_map_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) +{ + if (strcmp(key, "map_id") == 0) { + int32_t t = -1; + int rc = ul_strtos32(value, &t, 10); + if (rc < 0) + return 0; /* ignore -- parse failed */ + ((struct anon_bpf_map_data *)unkn->anon_data)->id = (int)t; + anon_bpf_map_get_more_info((struct anon_bpf_map_data *)unkn->anon_data); + return 1; + } + + if (strcmp(key, "map_type") == 0) { + int32_t t = -1; + int rc = ul_strtos32(value, &t, 10); + if (rc < 0) + return 0; /* ignore -- parse failed */ + ((struct anon_bpf_map_data *)unkn->anon_data)->type = (int)t; + return 1; + } + + return 0; +} + +static const struct anon_ops anon_bpf_map_ops = { + .class = "bpf-map", + .probe = anon_bpf_map_probe, + .get_name = anon_bpf_map_get_name, + .fill_column = anon_bpf_map_fill_column, + .init = anon_bpf_map_init, + .free = anon_bpf_map_free, + .handle_fdinfo = anon_bpf_map_handle_fdinfo, +}; + +/* * generic (fallback implementation) */ static const struct anon_ops anon_generic_ops = { @@ -272,6 +1363,25 @@ static const struct anon_ops anon_generic_ops = { .handle_fdinfo = NULL, }; +static const struct anon_ops *anon_ops[] = { + &anon_pidfd_ops, + &anon_eventfd_ops, + &anon_eventpoll_ops, + &anon_timerfd_ops, + &anon_signalfd_ops, + &anon_inotify_ops, + &anon_bpf_prog_ops, + &anon_bpf_map_ops, +}; + +static const struct anon_ops *anon_probe(const char *str) +{ + for (size_t i = 0; i < ARRAY_SIZE(anon_ops); i++) + if (anon_ops[i]->probe(str)) + return anon_ops[i]; + return &anon_generic_ops; +} + const struct file_class unkn_class = { .super = &file_class, .size = sizeof(struct unkn), @@ -279,4 +1389,6 @@ const struct file_class unkn_class = { .initialize_content = unkn_init_content, .free_content = unkn_content_free, .handle_fdinfo = unkn_handle_fdinfo, + .attach_xinfo = unkn_attach_xinfo, + .get_ipc_class = unkn_get_ipc_class, }; |