]> git.ipfire.org Git - thirdparty/util-linux.git/blob - lsfd-cmd/unkn.c
lsfd: move the source code to new ./lsfd-cmd directory
[thirdparty/util-linux.git] / lsfd-cmd / unkn.c
1 /*
2 * lsfd-unkn.c - handle associations opening unknown objects
3 *
4 * Copyright (C) 2021 Red Hat, Inc. All rights reserved.
5 * Written by Masatake YAMATO <yamato@redhat.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it would be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software Foundation,
19 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22 #include <linux/bpf.h>
23 #include <sys/syscall.h>
24 #include <sys/timerfd.h>
25 #include <time.h>
26
27 #include "signames.h"
28 #include "timeutils.h"
29
30 #include "lsfd.h"
31 #include "pidfd.h"
32
33 #define offsetofend(TYPE, MEMBER) \
34 (offsetof(TYPE, MEMBER) + sizeof_member(TYPE, MEMBER))
35
36 struct unkn {
37 struct file file;
38 const struct anon_ops *anon_ops;
39 void *anon_data;
40 };
41
42 struct anon_ops {
43 const char *class;
44 bool (*probe)(const char *);
45 char * (*get_name)(struct unkn *);
46 /* Return true is handled the column. */
47 bool (*fill_column)(struct proc *,
48 struct unkn *,
49 struct libscols_line *,
50 int,
51 size_t,
52 char **str);
53 void (*init)(struct unkn *);
54 void (*free)(struct unkn *);
55 int (*handle_fdinfo)(struct unkn *, const char *, const char *);
56 void (*attach_xinfo)(struct unkn *);
57 const struct ipc_class *ipc_class;
58 };
59
60 static const struct anon_ops *anon_probe(const char *);
61
62 static char * anon_get_class(struct unkn *unkn)
63 {
64 char *name;
65
66 if (unkn->anon_ops->class)
67 return xstrdup(unkn->anon_ops->class);
68
69 /* See unkn_init_content() */
70 name = ((struct file *)unkn)->name + 11;
71 /* Does it have the form anon_inode:[class]? */
72 if (*name == '[') {
73 size_t len = strlen(name + 1);
74 if (*(name + 1 + len - 1) == ']')
75 return strndup(name + 1, len - 1);
76 }
77
78 return xstrdup(name);
79 }
80
81 static bool unkn_fill_column(struct proc *proc,
82 struct file *file,
83 struct libscols_line *ln,
84 int column_id,
85 size_t column_index)
86 {
87 char *str = NULL;
88 struct unkn *unkn = (struct unkn *)file;
89
90 switch(column_id) {
91 case COL_NAME:
92 if (unkn->anon_ops && unkn->anon_ops->get_name) {
93 str = unkn->anon_ops->get_name(unkn);
94 if (str)
95 break;
96 }
97 return false;
98 case COL_TYPE:
99 if (!unkn->anon_ops)
100 return false;
101 /* FALL THROUGH */
102 case COL_AINODECLASS:
103 if (unkn->anon_ops) {
104 str = anon_get_class(unkn);
105 break;
106 }
107 return false;
108 case COL_SOURCE:
109 if (unkn->anon_ops) {
110 str = xstrdup("anon_inodefs");
111 break;
112 }
113 return false;
114 default:
115 if (unkn->anon_ops && unkn->anon_ops->fill_column) {
116 if (unkn->anon_ops->fill_column(proc, unkn, ln,
117 column_id, column_index, &str))
118 break;
119 }
120 return false;
121 }
122
123 if (!str)
124 err(EXIT_FAILURE, _("failed to add output data"));
125 if (scols_line_refer_data(ln, column_index, str))
126 err(EXIT_FAILURE, _("failed to add output data"));
127 return true;
128 }
129
130 static void unkn_attach_xinfo(struct file *file)
131 {
132 struct unkn *unkn = (struct unkn *)file;
133 if (unkn->anon_ops && unkn->anon_ops->attach_xinfo)
134 unkn->anon_ops->attach_xinfo(unkn);
135 }
136
137 static const struct ipc_class *unkn_get_ipc_class(struct file *file)
138 {
139 struct unkn *unkn = (struct unkn *)file;
140
141 if (unkn->anon_ops && unkn->anon_ops->ipc_class)
142 return unkn->anon_ops->ipc_class;
143 return NULL;
144 }
145
146 static void unkn_init_content(struct file *file)
147 {
148 struct unkn *unkn = (struct unkn *)file;
149
150 assert(file);
151 unkn->anon_ops = NULL;
152 unkn->anon_data = NULL;
153
154 if (major(file->stat.st_dev) == 0
155 && strncmp(file->name, "anon_inode:", 11) == 0) {
156 const char *rest = file->name + 11;
157
158 unkn->anon_ops = anon_probe(rest);
159
160 if (unkn->anon_ops->init)
161 unkn->anon_ops->init(unkn);
162 }
163 }
164
165 static void unkn_content_free(struct file *file)
166 {
167 struct unkn *unkn = (struct unkn *)file;
168
169 assert(file);
170 if (unkn->anon_ops && unkn->anon_ops->free)
171 unkn->anon_ops->free((struct unkn *)file);
172 }
173
174 static int unkn_handle_fdinfo(struct file *file, const char *key, const char *value)
175 {
176 struct unkn *unkn = (struct unkn *)file;
177
178 assert(file);
179 if (unkn->anon_ops && unkn->anon_ops->handle_fdinfo)
180 return unkn->anon_ops->handle_fdinfo(unkn, key, value);
181 return 0; /* Should be handled in parents */
182 }
183
184 /*
185 * pidfd
186 */
187
188 static bool anon_pidfd_probe(const char *str)
189 {
190 return strncmp(str, "[pidfd]", 7) == 0;
191 }
192
193 static char *anon_pidfd_get_name(struct unkn *unkn)
194 {
195 struct pidfd_data *data = (struct pidfd_data *)unkn->anon_data;
196
197 return pidfd_get_name(data);
198 }
199
200 static void anon_pidfd_init(struct unkn *unkn)
201 {
202 unkn->anon_data = xcalloc(1, sizeof(struct pidfd_data));
203 }
204
205 static void anon_pidfd_free(struct unkn *unkn)
206 {
207 struct pidfd_data *data = (struct pidfd_data *)unkn->anon_data;
208
209 pidfd_free(data);
210 free(data);
211 }
212
213 static int anon_pidfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value)
214 {
215 return pidfd_handle_fdinfo((struct pidfd_data *)unkn->anon_data,
216 key, value);
217 }
218
219 static bool anon_pidfd_fill_column(struct proc *proc __attribute__((__unused__)),
220 struct unkn *unkn,
221 struct libscols_line *ln __attribute__((__unused__)),
222 int column_id,
223 size_t column_index __attribute__((__unused__)),
224 char **str)
225 {
226 return pidfd_fill_column((struct pidfd_data *)unkn->anon_data,
227 column_id,
228 str);
229 }
230
231 static const struct anon_ops anon_pidfd_ops = {
232 .class = "pidfd",
233 .probe = anon_pidfd_probe,
234 .get_name = anon_pidfd_get_name,
235 .fill_column = anon_pidfd_fill_column,
236 .init = anon_pidfd_init,
237 .free = anon_pidfd_free,
238 .handle_fdinfo = anon_pidfd_handle_fdinfo,
239 };
240
241 /*
242 * eventfd
243 */
244 struct anon_eventfd_data {
245 int id;
246 struct unkn *backptr;
247 struct ipc_endpoint endpoint;
248 };
249
250 struct eventfd_ipc {
251 struct ipc ipc;
252 int id;
253 };
254
255 static unsigned int anon_eventfd_get_hash(struct file *file)
256 {
257 struct unkn *unkn = (struct unkn *)file;
258 struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data;
259
260 return (unsigned int)data->id;
261 }
262
263 static bool anon_eventfd_is_suitable_ipc(struct ipc *ipc, struct file *file)
264 {
265 struct unkn *unkn = (struct unkn *)file;
266 struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data;
267
268 return ((struct eventfd_ipc *)ipc)->id == data->id;
269 }
270
271 static const struct ipc_class anon_eventfd_ipc_class = {
272 .size = sizeof(struct eventfd_ipc),
273 .get_hash = anon_eventfd_get_hash,
274 .is_suitable_ipc = anon_eventfd_is_suitable_ipc,
275 .free = NULL,
276 };
277
278 static bool anon_eventfd_probe(const char *str)
279 {
280 return strncmp(str, "[eventfd]", 9) == 0;
281 }
282
283 static char *anon_eventfd_get_name(struct unkn *unkn)
284 {
285 char *str = NULL;
286 struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data;
287
288 xasprintf(&str, "id=%d", data->id);
289 return str;
290 }
291
292 static void anon_eventfd_init(struct unkn *unkn)
293 {
294 struct anon_eventfd_data *data = xcalloc(1, sizeof(struct anon_eventfd_data));
295 init_endpoint(&data->endpoint);
296 data->backptr = unkn;
297 unkn->anon_data = data;
298 }
299
300 static void anon_eventfd_free(struct unkn *unkn)
301 {
302 free(unkn->anon_data);
303 }
304
305 static void anon_eventfd_attach_xinfo(struct unkn *unkn)
306 {
307 struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data;
308 unsigned int hash;
309 struct ipc *ipc = get_ipc(&unkn->file);
310 if (ipc)
311 goto link;
312
313 ipc = new_ipc(&anon_eventfd_ipc_class);
314 ((struct eventfd_ipc *)ipc)->id = data->id;
315
316 hash = anon_eventfd_get_hash(&unkn->file);
317 add_ipc(ipc, hash);
318
319 link:
320 add_endpoint(&data->endpoint, ipc);
321 }
322
323 static int anon_eventfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value)
324 {
325 if (strcmp(key, "eventfd-id") == 0) {
326 int64_t id;
327
328 int rc = ul_strtos64(value, &id, 10);
329 if (rc < 0)
330 return 0;
331 ((struct anon_eventfd_data *)unkn->anon_data)->id = (int)id;
332 return 1;
333 }
334 return 0;
335 }
336
337 static inline char *anon_eventfd_data_xstrendpoint(struct file *file)
338 {
339 char *str = NULL;
340 xasprintf(&str, "%d,%s,%d",
341 file->proc->pid, file->proc->command, file->association);
342 return str;
343 }
344
345 static bool anon_eventfd_fill_column(struct proc *proc __attribute__((__unused__)),
346 struct unkn *unkn,
347 struct libscols_line *ln __attribute__((__unused__)),
348 int column_id,
349 size_t column_index __attribute__((__unused__)),
350 char **str)
351 {
352 struct anon_eventfd_data *data = (struct anon_eventfd_data *)unkn->anon_data;
353
354 switch(column_id) {
355 case COL_EVENTFD_ID:
356 xasprintf(str, "%d", data->id);
357 return true;
358 case COL_ENDPOINTS: {
359 struct list_head *e;
360 char *estr;
361 foreach_endpoint(e, data->endpoint) {
362 struct anon_eventfd_data *other = list_entry(e,
363 struct anon_eventfd_data,
364 endpoint.endpoints);
365 if (data == other)
366 continue;
367 if (*str)
368 xstrputc(str, '\n');
369 estr = anon_eventfd_data_xstrendpoint(&other->backptr->file);
370 xstrappend(str, estr);
371 free(estr);
372 }
373 if (!*str)
374 return false;
375 return true;
376 }
377 default:
378 return false;
379 }
380 }
381
382 static const struct anon_ops anon_eventfd_ops = {
383 .class = "eventfd",
384 .probe = anon_eventfd_probe,
385 .get_name = anon_eventfd_get_name,
386 .fill_column = anon_eventfd_fill_column,
387 .init = anon_eventfd_init,
388 .free = anon_eventfd_free,
389 .handle_fdinfo = anon_eventfd_handle_fdinfo,
390 .attach_xinfo = anon_eventfd_attach_xinfo,
391 .ipc_class = &anon_eventfd_ipc_class,
392 };
393
394 /*
395 * eventpoll
396 */
397 struct anon_eventpoll_data {
398 size_t count;
399 int *tfds;
400 struct list_head siblings;
401 };
402
403 static bool anon_eventpoll_probe(const char *str)
404 {
405 return strncmp(str, "[eventpoll]", 11) == 0;
406 }
407
408 static void anon_eventpoll_init(struct unkn *unkn)
409 {
410 struct anon_eventpoll_data *data = xcalloc(1, sizeof(struct anon_eventpoll_data));
411 INIT_LIST_HEAD(&data->siblings);
412 unkn->anon_data = data;
413 }
414
415 static void anon_eventpoll_free(struct unkn *unkn)
416 {
417 struct anon_eventpoll_data *data = unkn->anon_data;
418 free(data->tfds);
419 free(data);
420 }
421
422 static int anon_eventpoll_handle_fdinfo(struct unkn *unkn, const char *key, const char *value)
423 {
424 struct anon_eventpoll_data *data;
425 if (strcmp(key, "tfd") == 0) {
426 unsigned long tfd;
427 char *end = NULL;
428
429 errno = 0;
430 tfd = strtoul(value, &end, 0);
431 if (errno != 0)
432 return 0; /* ignore -- parse failed */
433
434 data = (struct anon_eventpoll_data *)unkn->anon_data;
435 data->tfds = xreallocarray(data->tfds, ++data->count, sizeof(int));
436 data->tfds[data->count - 1] = (int)tfd;
437 return 1;
438 }
439 return 0;
440 }
441
442 static int intcmp(const void *a, const void *b)
443 {
444 int ai = *(int *)a;
445 int bi = *(int *)b;
446
447 return ai - bi;
448 }
449
450 static void anon_eventpoll_attach_xinfo(struct unkn *unkn)
451 {
452 struct anon_eventpoll_data *data = (struct anon_eventpoll_data *)unkn->anon_data;
453 if (data->count > 0) {
454 qsort(data->tfds, data->count, sizeof(data->tfds[0]),
455 intcmp);
456 list_add_tail(&data->siblings,
457 &unkn->file.proc->eventpolls);
458 }
459 }
460
461 static char *anon_eventpoll_make_tfds_string(struct anon_eventpoll_data *data,
462 const char *prefix,
463 const char sep)
464 {
465 char *str = prefix? xstrdup(prefix): NULL;
466
467 char buf[256];
468 for (size_t i = 0; i < data->count; i++) {
469 size_t offset = 0;
470
471 if (i > 0) {
472 buf[0] = sep;
473 offset = 1;
474 }
475 snprintf(buf + offset, sizeof(buf) - offset, "%d", data->tfds[i]);
476 xstrappend(&str, buf);
477 }
478 return str;
479 }
480
481 static char *anon_eventpoll_get_name(struct unkn *unkn)
482 {
483 return anon_eventpoll_make_tfds_string((struct anon_eventpoll_data *)unkn->anon_data,
484 "tfds=", ',');
485 }
486
487 static bool anon_eventpoll_fill_column(struct proc *proc __attribute__((__unused__)),
488 struct unkn *unkn,
489 struct libscols_line *ln __attribute__((__unused__)),
490 int column_id,
491 size_t column_index __attribute__((__unused__)),
492 char **str)
493 {
494 struct anon_eventpoll_data *data = (struct anon_eventpoll_data *)unkn->anon_data;
495
496 switch(column_id) {
497 case COL_EVENTPOLL_TFDS:
498 *str =anon_eventpoll_make_tfds_string(data, NULL, '\n');
499 if (*str)
500 return true;
501 break;
502 }
503
504 return false;
505 }
506
507 static const struct anon_ops anon_eventpoll_ops = {
508 .class = "eventpoll",
509 .probe = anon_eventpoll_probe,
510 .get_name = anon_eventpoll_get_name,
511 .fill_column = anon_eventpoll_fill_column,
512 .init = anon_eventpoll_init,
513 .free = anon_eventpoll_free,
514 .handle_fdinfo = anon_eventpoll_handle_fdinfo,
515 .attach_xinfo = anon_eventpoll_attach_xinfo,
516 };
517
518 static int numcomp(const void *a, const void *b)
519 {
520 return *(int *)a - *(int *)b;
521 }
522
523 bool is_multiplexed_by_eventpoll(int fd, struct list_head *eventpolls)
524 {
525 struct list_head *t;
526 list_for_each (t, eventpolls) {
527 struct anon_eventpoll_data *data = list_entry(t, struct anon_eventpoll_data, siblings);
528 if (data->count) {
529 if (bsearch(&fd, data->tfds,
530 data->count, sizeof(data->tfds[0]),
531 numcomp))
532 return true;
533 }
534 }
535 return false;
536 }
537
538 /*
539 * timerfd
540 */
541 struct anon_timerfd_data {
542 int clockid;
543 struct itimerspec itimerspec;
544 };
545
546 static bool anon_timerfd_probe(const char *str)
547 {
548 return strncmp(str, "[timerfd]", 9) == 0;
549 }
550
551 static void anon_timerfd_init(struct unkn *unkn)
552 {
553 unkn->anon_data = xcalloc(1, sizeof(struct anon_timerfd_data));
554 }
555
556 static void anon_timerfd_free(struct unkn *unkn)
557 {
558 struct anon_timerfd_data *data = unkn->anon_data;
559 free(data);
560 }
561
562 static int anon_timerfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value)
563 {
564 struct anon_timerfd_data *data = (struct anon_timerfd_data *)unkn->anon_data;
565
566 if (strcmp(key, "clockid") == 0) {
567 unsigned long clockid;
568 char *end = NULL;
569
570 errno = 0;
571 clockid = strtoul(value, &end, 0);
572 if (errno != 0)
573 return 0; /* ignore -- parse failed */
574 if (*end != '\0')
575 return 0; /* ignore -- garbage remains. */
576
577 data->clockid = clockid;
578 return 1;
579 } else {
580 struct timespec *t;
581 uint64_t tv_sec;
582 uint64_t tv_nsec;
583
584 if (strcmp(key, "it_value") == 0)
585 t = &data->itimerspec.it_value;
586 else if (strcmp(key, "it_interval") == 0)
587 t = &data->itimerspec.it_interval;
588 else
589 return 0;
590
591 if (sscanf(value, "(%"SCNu64", %"SCNu64")",
592 &tv_sec, &tv_nsec) == 2) {
593 t->tv_sec = (time_t)tv_sec;
594 t->tv_nsec = (long)tv_nsec;
595 return 1;
596 }
597
598 return 0;
599 }
600 }
601
602 static const char *anon_timerfd_decode_clockid(int clockid)
603 {
604 switch (clockid) {
605 case CLOCK_REALTIME:
606 return "realtime";
607 case CLOCK_MONOTONIC:
608 return "monotonic";
609 case CLOCK_BOOTTIME:
610 return "boottime";
611 case CLOCK_REALTIME_ALARM:
612 return "realtime-alarm";
613 case CLOCK_BOOTTIME_ALARM:
614 return "boottime-alarm";
615 default:
616 return "unknown";
617 }
618 }
619
620 static void anon_timerfd_render_timespec_string(char *buf, size_t size,
621 const char *prefix,
622 const struct timespec *t)
623 {
624 snprintf(buf, size, "%s%llu.%09ld",
625 prefix? prefix: "",
626 (unsigned long long)t->tv_sec, t->tv_nsec);
627 }
628
629 static char *anon_timerfd_get_name(struct unkn *unkn)
630 {
631 char *str = NULL;
632
633 struct anon_timerfd_data *data = (struct anon_timerfd_data *)unkn->anon_data;
634 const struct timespec *exp;
635 const struct timespec *ival;
636
637 const char *clockid_name;
638 char exp_buf[BUFSIZ] = {'\0'};
639 char ival_buf[BUFSIZ] = {'\0'};
640
641 clockid_name = anon_timerfd_decode_clockid(data->clockid);
642
643 exp = &data->itimerspec.it_value;
644 if (is_timespecset(exp))
645 anon_timerfd_render_timespec_string(exp_buf, sizeof(exp_buf),
646 " remaining=", exp);
647
648 ival = &data->itimerspec.it_interval;
649 if (is_timespecset(ival))
650 anon_timerfd_render_timespec_string(ival_buf, sizeof(ival_buf),
651 " interval=", ival);
652
653 xasprintf(&str, "clockid=%s%s%s", clockid_name, exp_buf, ival_buf);
654 return str;
655 }
656
657 static bool anon_timerfd_fill_column(struct proc *proc __attribute__((__unused__)),
658 struct unkn *unkn,
659 struct libscols_line *ln __attribute__((__unused__)),
660 int column_id,
661 size_t column_index __attribute__((__unused__)),
662 char **str)
663 {
664 struct anon_timerfd_data *data = (struct anon_timerfd_data *)unkn->anon_data;
665 char buf[BUFSIZ] = {'\0'};
666
667 switch(column_id) {
668 case COL_TIMERFD_CLOCKID:
669 *str = xstrdup(anon_timerfd_decode_clockid(data->clockid));
670 return true;
671 case COL_TIMERFD_INTERVAL:
672 anon_timerfd_render_timespec_string(buf, sizeof(buf), NULL,
673 &data->itimerspec.it_interval);
674 *str = xstrdup(buf);
675 return true;
676 case COL_TIMERFD_REMAINING:
677 anon_timerfd_render_timespec_string(buf, sizeof(buf), NULL,
678 &data->itimerspec.it_value);
679 *str = xstrdup(buf);
680 return true;
681 }
682
683 return false;
684 }
685
686 static const struct anon_ops anon_timerfd_ops = {
687 .class = "timerfd",
688 .probe = anon_timerfd_probe,
689 .get_name = anon_timerfd_get_name,
690 .fill_column = anon_timerfd_fill_column,
691 .init = anon_timerfd_init,
692 .free = anon_timerfd_free,
693 .handle_fdinfo = anon_timerfd_handle_fdinfo,
694 };
695
696 /*
697 * signalfd
698 */
699 struct anon_signalfd_data {
700 uint64_t sigmask;
701 };
702
703 static bool anon_signalfd_probe(const char *str)
704 {
705 return strncmp(str, "[signalfd]", 10) == 0;
706 }
707
708 static void anon_signalfd_init(struct unkn *unkn)
709 {
710 unkn->anon_data = xcalloc(1, sizeof(struct anon_signalfd_data));
711 }
712
713 static void anon_signalfd_free(struct unkn *unkn)
714 {
715 struct anon_signalfd_data *data = unkn->anon_data;
716 free(data);
717 }
718
719 static int anon_signalfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value)
720 {
721 struct anon_signalfd_data *data = (struct anon_signalfd_data *)unkn->anon_data;
722
723 if (strcmp(key, "sigmask") == 0) {
724 if (ul_strtou64(value, &data->sigmask, 16) < 0) {
725 data->sigmask = 0;
726 return 0;
727 }
728 }
729 return 0;
730 }
731
732 static char *anon_signalfd_make_mask_string(const char* prefix, uint64_t sigmask)
733 {
734 char *str = NULL;
735
736 for (size_t i = 0; i < sizeof(sigmask) * 8; i++) {
737 if ((((uint64_t)0x1) << i) & sigmask) {
738 const int signum = i + 1;
739 const char *signame = signum_to_signame(signum);
740
741 if (str)
742 xstrappend(&str, ",");
743 else if (prefix)
744 xstrappend(&str, prefix);
745
746 if (signame) {
747 xstrappend(&str, signame);
748 } else {
749 char buf[BUFSIZ];
750 snprintf(buf, sizeof(buf), "%d", signum);
751 xstrappend(&str, buf);
752 }
753 }
754 }
755
756 return str;
757 }
758
759 static char *anon_signalfd_get_name(struct unkn *unkn)
760 {
761 struct anon_signalfd_data *data = (struct anon_signalfd_data *)unkn->anon_data;
762 return anon_signalfd_make_mask_string("mask=", data->sigmask);
763 }
764
765 static bool anon_signalfd_fill_column(struct proc *proc __attribute__((__unused__)),
766 struct unkn *unkn,
767 struct libscols_line *ln __attribute__((__unused__)),
768 int column_id,
769 size_t column_index __attribute__((__unused__)),
770 char **str)
771 {
772 struct anon_signalfd_data *data = (struct anon_signalfd_data *)unkn->anon_data;
773
774 switch(column_id) {
775 case COL_SIGNALFD_MASK:
776 *str = anon_signalfd_make_mask_string(NULL, data->sigmask);
777 return true;
778 default:
779 return false;
780 }
781 }
782
783 static const struct anon_ops anon_signalfd_ops = {
784 .class = "signalfd",
785 .probe = anon_signalfd_probe,
786 .get_name = anon_signalfd_get_name,
787 .fill_column = anon_signalfd_fill_column,
788 .init = anon_signalfd_init,
789 .free = anon_signalfd_free,
790 .handle_fdinfo = anon_signalfd_handle_fdinfo,
791 };
792
793 /*
794 * inotify
795 */
796 struct anon_inotify_data {
797 struct list_head inodes;
798 };
799
800 struct anon_inotify_inode {
801 ino_t ino;
802 dev_t sdev;
803 struct list_head inodes;
804 };
805
806 static bool anon_inotify_probe(const char *str)
807 {
808 return strncmp(str, "inotify", 7) == 0;
809 }
810
811 /* A device number appeared in fdinfo of an inotify file uses the kernel
812 * internal representation. It is different from what we are familiar with;
813 * major(3) and minor(3) don't work with the representation.
814 * See linux/include/linux/kdev_t.h. */
815 #define ANON_INOTIFY_MINORBITS 20
816 #define ANON_INOTIFY_MINORMASK ((1U << ANON_INOTIFY_MINORBITS) - 1)
817
818 #define ANON_INOTIFY_MAJOR(dev) ((unsigned int) ((dev) >> ANON_INOTIFY_MINORBITS))
819 #define ANON_INOTIFY_MINOR(dev) ((unsigned int) ((dev) & ANON_INOTIFY_MINORMASK))
820
821 static char *anon_inotify_make_inodes_string(const char *prefix,
822 const char *sep,
823 enum decode_source_level decode_level,
824 struct anon_inotify_data *data)
825 {
826 char *str = NULL;
827 char buf[BUFSIZ] = {'\0'};
828 bool first_element = true;
829
830 struct list_head *i;
831 list_for_each(i, &data->inodes) {
832 char source[BUFSIZ/2] = {'\0'};
833 struct anon_inotify_inode *inode = list_entry(i,
834 struct anon_inotify_inode,
835 inodes);
836
837 decode_source(source, sizeof(source),
838 ANON_INOTIFY_MAJOR(inode->sdev), ANON_INOTIFY_MINOR(inode->sdev),
839 decode_level);
840 snprintf(buf, sizeof(buf), "%s%llu@%s", first_element? prefix: sep,
841 (unsigned long long)inode->ino, source);
842 first_element = false;
843
844 xstrappend(&str, buf);
845 }
846
847 return str;
848 }
849
850 static char *anon_inotify_get_name(struct unkn *unkn)
851 {
852 return anon_inotify_make_inodes_string("inodes=", ",", DECODE_SOURCE_FULL,
853 (struct anon_inotify_data *)unkn->anon_data);
854 }
855
856 static void anon_inotify_init(struct unkn *unkn)
857 {
858 struct anon_inotify_data *data = xcalloc(1, sizeof(struct anon_inotify_data));
859 INIT_LIST_HEAD (&data->inodes);
860 unkn->anon_data = data;
861 }
862
863 static void anon_inotify_free(struct unkn *unkn)
864 {
865 struct anon_inotify_data *data = unkn->anon_data;
866
867 list_free(&data->inodes, struct anon_inotify_inode, inodes,
868 free);
869 free(data);
870 }
871
872 static void add_inode(struct anon_inotify_data *data, ino_t ino, dev_t sdev)
873 {
874 struct anon_inotify_inode *inode = xmalloc(sizeof(*inode));
875
876 INIT_LIST_HEAD (&inode->inodes);
877 inode->ino = ino;
878 inode->sdev = sdev;
879
880 list_add_tail(&inode->inodes, &data->inodes);
881 }
882
883 static int anon_inotify_handle_fdinfo(struct unkn *unkn, const char *key, const char *value)
884 {
885 struct anon_inotify_data *data = (struct anon_inotify_data *)unkn->anon_data;
886
887 if (strcmp(key, "inotify wd") == 0) {
888 unsigned long long ino;
889 unsigned long long sdev;
890
891 if (sscanf(value, "%*d ino:%llx sdev:%llx %*s", &ino, &sdev) == 2) {
892 add_inode(data, (ino_t)ino, (dev_t)sdev);
893 return 1;
894 }
895 }
896 return 0;
897 }
898
899 static bool anon_inotify_fill_column(struct proc *proc __attribute__((__unused__)),
900 struct unkn *unkn,
901 struct libscols_line *ln __attribute__((__unused__)),
902 int column_id,
903 size_t column_index __attribute__((__unused__)),
904 char **str)
905 {
906 struct anon_inotify_data *data = (struct anon_inotify_data *)unkn->anon_data;
907
908 switch(column_id) {
909 case COL_INOTIFY_INODES:
910 *str = anon_inotify_make_inodes_string("", "\n", DECODE_SOURCE_FULL,
911 data);
912 if (*str)
913 return true;
914 break;
915 case COL_INOTIFY_INODES_RAW:
916 *str = anon_inotify_make_inodes_string("", "\n", DECODE_SOURCE_MAJMIN,
917 data);
918 if (*str)
919 return true;
920 break;
921 }
922
923 return false;
924 }
925
926 static const struct anon_ops anon_inotify_ops = {
927 .class = "inotify",
928 .probe = anon_inotify_probe,
929 .get_name = anon_inotify_get_name,
930 .fill_column = anon_inotify_fill_column,
931 .init = anon_inotify_init,
932 .free = anon_inotify_free,
933 .handle_fdinfo = anon_inotify_handle_fdinfo,
934 };
935
936 /*
937 * bpf-prog
938 *
939 * Generally, we use "-" as the word separators in lsfd's output.
940 * However, about bpf*, we use "_" because bpftool uses "_".
941 */
942 static const char *bpf_prog_type_table[] = {
943 [0] = "unspec", /* BPF_PROG_TYPE_UNSPEC*/
944 [1] = "socket_filter", /* BPF_PROG_TYPE_SOCKET_FILTER*/
945 [2] = "kprobe", /* BPF_PROG_TYPE_KPROBE*/
946 [3] = "sched_cls", /* BPF_PROG_TYPE_SCHED_CLS*/
947 [4] = "sched_act", /* BPF_PROG_TYPE_SCHED_ACT*/
948 [5] = "tracepoint", /* BPF_PROG_TYPE_TRACEPOINT*/
949 [6] = "xdp", /* BPF_PROG_TYPE_XDP*/
950 [7] = "perf_event", /* BPF_PROG_TYPE_PERF_EVENT*/
951 [8] = "cgroup_skb", /* BPF_PROG_TYPE_CGROUP_SKB*/
952 [9] = "cgroup_sock", /* BPF_PROG_TYPE_CGROUP_SOCK*/
953 [10] = "lwt_in", /* BPF_PROG_TYPE_LWT_IN*/
954 [11] = "lwt_out", /* BPF_PROG_TYPE_LWT_OUT*/
955 [12] = "lwt_xmit", /* BPF_PROG_TYPE_LWT_XMIT*/
956 [13] = "sock_ops", /* BPF_PROG_TYPE_SOCK_OPS*/
957 [14] = "sk_skb", /* BPF_PROG_TYPE_SK_SKB*/
958 [15] = "cgroup_device", /* BPF_PROG_TYPE_CGROUP_DEVICE*/
959 [16] = "sk_msg", /* BPF_PROG_TYPE_SK_MSG*/
960 [17] = "raw_tracepoint", /* BPF_PROG_TYPE_RAW_TRACEPOINT*/
961 [18] = "cgroup_sock_addr", /* BPF_PROG_TYPE_CGROUP_SOCK_ADDR*/
962 [19] = "lwt_seg6local", /* BPF_PROG_TYPE_LWT_SEG6LOCAL*/
963 [20] = "lirc_mode2", /* BPF_PROG_TYPE_LIRC_MODE2*/
964 [21] = "sk_reuseport", /* BPF_PROG_TYPE_SK_REUSEPORT*/
965 [22] = "flow_dissector", /* BPF_PROG_TYPE_FLOW_DISSECTOR*/
966 [23] = "cgroup_sysctl", /* BPF_PROG_TYPE_CGROUP_SYSCTL*/
967 [24] = "raw_tracepoint_writable", /* BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE*/
968 [25] = "cgroup_sockopt", /* BPF_PROG_TYPE_CGROUP_SOCKOPT*/
969 [26] = "tracing", /* BPF_PROG_TYPE_TRACING*/
970 [27] = "struct_ops", /* BPF_PROG_TYPE_STRUCT_OPS*/
971 [28] = "ext", /* BPF_PROG_TYPE_EXT*/
972 [29] = "lsm", /* BPF_PROG_TYPE_LSM*/
973 [30] = "sk_lookup", /* BPF_PROG_TYPE_SK_LOOKUP*/
974 [31] = "syscall", /* BPF_PROG_TYPE_SYSCALL*/
975 };
976
977 struct anon_bpf_prog_data {
978 int type;
979 int id;
980 char name[BPF_OBJ_NAME_LEN + 1];
981 };
982
983 static bool anon_bpf_prog_probe(const char *str)
984 {
985 return strncmp(str, "bpf-prog", 8) == 0;
986 }
987
988 static const char *anon_bpf_prog_get_prog_type_name(int type)
989 {
990 if (0 <= type && type < (int)ARRAY_SIZE(bpf_prog_type_table))
991 return bpf_prog_type_table[type];
992 return NULL;
993 }
994
995 static bool anon_bpf_prog_fill_column(struct proc *proc __attribute__((__unused__)),
996 struct unkn *unkn,
997 struct libscols_line *ln __attribute__((__unused__)),
998 int column_id,
999 size_t column_index __attribute__((__unused__)),
1000 char **str)
1001 {
1002 struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data;
1003 const char *t;
1004
1005 switch(column_id) {
1006 case COL_BPF_PROG_ID:
1007 xasprintf(str, "%d", data->id);
1008 return true;
1009 case COL_BPF_PROG_TYPE_RAW:
1010 xasprintf(str, "%d", data->type);
1011 return true;
1012 case COL_BPF_PROG_TYPE:
1013 t = anon_bpf_prog_get_prog_type_name(data->type);
1014 if (t)
1015 *str = xstrdup(t);
1016 else
1017 xasprintf(str, "UNKNOWN(%d)", data->type);
1018 return true;
1019 case COL_BPF_NAME:
1020 *str = xstrdup(data->name);
1021 return true;
1022 default:
1023 return false;
1024 }
1025 }
1026
1027 static char *anon_bpf_prog_get_name(struct unkn *unkn)
1028 {
1029 const char *t;
1030 char *str = NULL;
1031 struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data;
1032
1033 t = anon_bpf_prog_get_prog_type_name(data->type);
1034 if (t)
1035 xasprintf(&str, "id=%d type=%s", data->id, t);
1036 else
1037 xasprintf(&str, "id=%d type=UNKNOWN(%d)", data->id, data->type);
1038
1039 if (*data->name)
1040 xstrfappend(&str, " name=%s", data->name);
1041
1042 return str;
1043 }
1044
1045
1046 static void anon_bpf_prog_init(struct unkn *unkn)
1047 {
1048 struct anon_bpf_prog_data *data = xmalloc(sizeof(*data));
1049 data->type = -1;
1050 data->id = -1;
1051 data->name[0] = '\0';
1052 unkn->anon_data = data;
1053 }
1054
1055 static void anon_bpf_prog_free(struct unkn *unkn)
1056 {
1057 struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data;
1058 free(data);
1059 }
1060
1061 static void anon_bpf_prog_get_more_info(struct anon_bpf_prog_data *prog_data)
1062 {
1063 union bpf_attr attr = {
1064 .prog_id = (int32_t)prog_data->id,
1065 .next_id = 0,
1066 .open_flags = 0,
1067 };
1068 struct bpf_prog_info info = { 0 };
1069 union bpf_attr info_attr = {
1070 .info.info_len = sizeof(info),
1071 .info.info = (uint64_t)(uintptr_t)&info,
1072 };
1073
1074 int bpf_fd = syscall(SYS_bpf, BPF_PROG_GET_FD_BY_ID, &attr, sizeof(attr));
1075 if (bpf_fd < 0)
1076 return;
1077
1078 info_attr.info.bpf_fd = bpf_fd;
1079 if (syscall(SYS_bpf, BPF_OBJ_GET_INFO_BY_FD, &info_attr, offsetofend(union bpf_attr, info)) == 0) {
1080 memcpy(prog_data->name,
1081 info.name,
1082 BPF_OBJ_NAME_LEN);
1083 prog_data->name[BPF_OBJ_NAME_LEN] = '\0';
1084 }
1085 close(bpf_fd);
1086 }
1087
1088 static int anon_bpf_prog_handle_fdinfo(struct unkn *unkn, const char *key, const char *value)
1089 {
1090 if (strcmp(key, "prog_id") == 0) {
1091 int32_t t = -1;
1092 int rc = ul_strtos32(value, &t, 10);
1093 if (rc < 0)
1094 return 0; /* ignore -- parse failed */
1095 ((struct anon_bpf_prog_data *)unkn->anon_data)->id = (int)t;
1096 anon_bpf_prog_get_more_info((struct anon_bpf_prog_data *)unkn->anon_data);
1097 return 1;
1098 }
1099
1100 if (strcmp(key, "prog_type") == 0) {
1101 int32_t t = -1;
1102 int rc = ul_strtos32(value, &t, 10);
1103 if (rc < 0)
1104 return 0; /* ignore -- parse failed */
1105 ((struct anon_bpf_prog_data *)unkn->anon_data)->type = (int)t;
1106 return 1;
1107 }
1108
1109 return 0;
1110 }
1111
1112 static const struct anon_ops anon_bpf_prog_ops = {
1113 .class = "bpf-prog",
1114 .probe = anon_bpf_prog_probe,
1115 .get_name = anon_bpf_prog_get_name,
1116 .fill_column = anon_bpf_prog_fill_column,
1117 .init = anon_bpf_prog_init,
1118 .free = anon_bpf_prog_free,
1119 .handle_fdinfo = anon_bpf_prog_handle_fdinfo,
1120 };
1121
1122 /*
1123 * bpf-map
1124 */
1125 static const char *bpf_map_type_table[] = {
1126 [0] = "unspec", /* BPF_MAP_TYPE_UNSPEC */
1127 [1] = "hash", /* BPF_MAP_TYPE_HASH */
1128 [2] = "array", /* BPF_MAP_TYPE_ARRAY */
1129 [3] = "prog-array", /* BPF_MAP_TYPE_PROG_ARRAY */
1130 [4] = "perf-event-array", /* BPF_MAP_TYPE_PERF_EVENT_ARRAY */
1131 [5] = "percpu-hash", /* BPF_MAP_TYPE_PERCPU_HASH */
1132 [6] = "percpu-array", /* BPF_MAP_TYPE_PERCPU_ARRAY */
1133 [7] = "stack-trace", /* BPF_MAP_TYPE_STACK_TRACE */
1134 [8] = "cgroup-array", /* BPF_MAP_TYPE_CGROUP_ARRAY */
1135 [9] = "lru-hash", /* BPF_MAP_TYPE_LRU_HASH */
1136 [10] = "lru-percpu-hash", /* BPF_MAP_TYPE_LRU_PERCPU_HASH */
1137 [11] = "lpm-trie", /* BPF_MAP_TYPE_LPM_TRIE */
1138 [12] = "array-of-maps", /* BPF_MAP_TYPE_ARRAY_OF_MAPS */
1139 [13] = "hash-of-maps", /* BPF_MAP_TYPE_HASH_OF_MAPS */
1140 [14] = "devmap", /* BPF_MAP_TYPE_DEVMAP */
1141 [15] = "sockmap", /* BPF_MAP_TYPE_SOCKMAP */
1142 [16] = "cpumap", /* BPF_MAP_TYPE_CPUMAP */
1143 [17] = "xskmap", /* BPF_MAP_TYPE_XSKMAP */
1144 [18] = "sockhash", /* BPF_MAP_TYPE_SOCKHASH */
1145 [19] = "cgroup-storage", /* BPF_MAP_TYPE_CGROUP_STORAGE */
1146 [20] = "reuseport-sockarray", /* BPF_MAP_TYPE_REUSEPORT_SOCKARRAY */
1147 [21] = "percpu-cgroup-storage", /* BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE */
1148 [22] = "queue", /* BPF_MAP_TYPE_QUEUE */
1149 [23] = "stack", /* BPF_MAP_TYPE_STACK */
1150 [24] = "sk-storage", /* BPF_MAP_TYPE_SK_STORAGE */
1151 [25] = "devmap-hash", /* BPF_MAP_TYPE_DEVMAP_HASH */
1152 [26] = "struct-ops", /* BPF_MAP_TYPE_STRUCT_OPS */
1153 [27] = "ringbuf", /* BPF_MAP_TYPE_RINGBUF */
1154 [28] = "inode-storage", /* BPF_MAP_TYPE_INODE_STORAGE */
1155 [29] = "task-storage", /* BPF_MAP_TYPE_TASK_STORAGE */
1156 [30] = "bloom-filter", /* BPF_MAP_TYPE_BLOOM_FILTER */
1157 [31] = "user-ringbuf", /* BPF_MAP_TYPE_USER_RINGBUF */
1158 [32] = "cgrp-storage", /* BPF_MAP_TYPE_CGRP_STORAGE */
1159 };
1160
1161 struct anon_bpf_map_data {
1162 int type;
1163 int id;
1164 char name[BPF_OBJ_NAME_LEN + 1];
1165 };
1166
1167 static bool anon_bpf_map_probe(const char *str)
1168 {
1169 return strncmp(str, "bpf-map", 8) == 0;
1170 }
1171
1172 static const char *anon_bpf_map_get_map_type_name(int type)
1173 {
1174 if (0 <= type && type < (int)ARRAY_SIZE(bpf_map_type_table))
1175 return bpf_map_type_table[type];
1176 return NULL;
1177 }
1178
1179 static bool anon_bpf_map_fill_column(struct proc *proc __attribute__((__unused__)),
1180 struct unkn *unkn,
1181 struct libscols_line *ln __attribute__((__unused__)),
1182 int column_id,
1183 size_t column_index __attribute__((__unused__)),
1184 char **str)
1185 {
1186 struct anon_bpf_prog_data *data = (struct anon_bpf_prog_data *)unkn->anon_data;
1187 const char *t;
1188
1189 switch(column_id) {
1190 case COL_BPF_MAP_ID:
1191 xasprintf(str, "%d", data->id);
1192 return true;
1193 case COL_BPF_MAP_TYPE_RAW:
1194 xasprintf(str, "%d", data->type);
1195 return true;
1196 case COL_BPF_MAP_TYPE:
1197 t = anon_bpf_map_get_map_type_name(data->type);
1198 if (t)
1199 *str = xstrdup(t);
1200 else
1201 xasprintf(str, "UNKNOWN(%d)", data->type);
1202 return true;
1203 case COL_BPF_NAME:
1204 *str = xstrdup(data->name);
1205 return true;
1206 default:
1207 return false;
1208 }
1209 }
1210
1211 static char *anon_bpf_map_get_name(struct unkn *unkn)
1212 {
1213 const char *t;
1214 char *str = NULL;
1215 struct anon_bpf_map_data *data = (struct anon_bpf_map_data *)unkn->anon_data;
1216
1217 t = anon_bpf_map_get_map_type_name(data->type);
1218 if (t)
1219 xasprintf(&str, "id=%d type=%s", data->id, t);
1220 else
1221 xasprintf(&str, "id=%d type=UNKNOWN(%d)", data->id, data->type);
1222
1223 if (*data->name)
1224 xstrfappend(&str, " name=%s", data->name);
1225
1226 return str;
1227 }
1228
1229 static void anon_bpf_map_init(struct unkn *unkn)
1230 {
1231 struct anon_bpf_map_data *data = xmalloc(sizeof(*data));
1232 data->type = -1;
1233 data->id = -1;
1234 data->name[0] = '\0';
1235 unkn->anon_data = data;
1236 }
1237
1238 static void anon_bpf_map_free(struct unkn *unkn)
1239 {
1240 struct anon_bpf_map_data *data = (struct anon_bpf_map_data *)unkn->anon_data;
1241 free(data);
1242 }
1243
1244 static void anon_bpf_map_get_more_info(struct anon_bpf_map_data *map_data)
1245 {
1246 union bpf_attr attr = {
1247 .map_id = (int32_t)map_data->id,
1248 .next_id = 0,
1249 .open_flags = 0,
1250 };
1251 struct bpf_map_info info = { 0 };
1252 union bpf_attr info_attr = {
1253 .info.info_len = sizeof(info),
1254 .info.info = (uint64_t)(uintptr_t)&info,
1255 };
1256
1257 int bpf_fd = syscall(SYS_bpf, BPF_MAP_GET_FD_BY_ID, &attr, sizeof(attr));
1258 if (bpf_fd < 0)
1259 return;
1260
1261 info_attr.info.bpf_fd = bpf_fd;
1262 if (syscall(SYS_bpf, BPF_OBJ_GET_INFO_BY_FD, &info_attr, offsetofend(union bpf_attr, info)) == 0) {
1263 memcpy(map_data->name,
1264 info.name,
1265 BPF_OBJ_NAME_LEN);
1266 map_data->name[BPF_OBJ_NAME_LEN] = '\0';
1267 }
1268 close(bpf_fd);
1269 }
1270
1271 static int anon_bpf_map_handle_fdinfo(struct unkn *unkn, const char *key, const char *value)
1272 {
1273 if (strcmp(key, "map_id") == 0) {
1274 int32_t t = -1;
1275 int rc = ul_strtos32(value, &t, 10);
1276 if (rc < 0)
1277 return 0; /* ignore -- parse failed */
1278 ((struct anon_bpf_map_data *)unkn->anon_data)->id = (int)t;
1279 anon_bpf_map_get_more_info((struct anon_bpf_map_data *)unkn->anon_data);
1280 return 1;
1281 }
1282
1283 if (strcmp(key, "map_type") == 0) {
1284 int32_t t = -1;
1285 int rc = ul_strtos32(value, &t, 10);
1286 if (rc < 0)
1287 return 0; /* ignore -- parse failed */
1288 ((struct anon_bpf_map_data *)unkn->anon_data)->type = (int)t;
1289 return 1;
1290 }
1291
1292 return 0;
1293 }
1294
1295 static const struct anon_ops anon_bpf_map_ops = {
1296 .class = "bpf-map",
1297 .probe = anon_bpf_map_probe,
1298 .get_name = anon_bpf_map_get_name,
1299 .fill_column = anon_bpf_map_fill_column,
1300 .init = anon_bpf_map_init,
1301 .free = anon_bpf_map_free,
1302 .handle_fdinfo = anon_bpf_map_handle_fdinfo,
1303 };
1304
1305 /*
1306 * generic (fallback implementation)
1307 */
1308 static const struct anon_ops anon_generic_ops = {
1309 .class = NULL,
1310 .get_name = NULL,
1311 .fill_column = NULL,
1312 .init = NULL,
1313 .free = NULL,
1314 .handle_fdinfo = NULL,
1315 };
1316
1317 static const struct anon_ops *anon_ops[] = {
1318 &anon_pidfd_ops,
1319 &anon_eventfd_ops,
1320 &anon_eventpoll_ops,
1321 &anon_timerfd_ops,
1322 &anon_signalfd_ops,
1323 &anon_inotify_ops,
1324 &anon_bpf_prog_ops,
1325 &anon_bpf_map_ops,
1326 };
1327
1328 static const struct anon_ops *anon_probe(const char *str)
1329 {
1330 for (size_t i = 0; i < ARRAY_SIZE(anon_ops); i++)
1331 if (anon_ops[i]->probe(str))
1332 return anon_ops[i];
1333 return &anon_generic_ops;
1334 }
1335
1336 const struct file_class unkn_class = {
1337 .super = &file_class,
1338 .size = sizeof(struct unkn),
1339 .fill_column = unkn_fill_column,
1340 .initialize_content = unkn_init_content,
1341 .free_content = unkn_content_free,
1342 .handle_fdinfo = unkn_handle_fdinfo,
1343 .attach_xinfo = unkn_attach_xinfo,
1344 .get_ipc_class = unkn_get_ipc_class,
1345 };