]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/lsfd-cdev.c
lsns: continue the executing even if opening a /proc/$pid fails
[thirdparty/util-linux.git] / misc-utils / lsfd-cdev.c
1 /*
2 * lsfd-cdev.c - handle associations opening character devices
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 "lsfd.h"
23
24 static struct list_head miscdevs;
25 static struct list_head ttydrvs;
26
27 struct miscdev {
28 struct list_head miscdevs;
29 unsigned long minor;
30 char *name;
31 };
32
33 struct ttydrv {
34 struct list_head ttydrvs;
35 unsigned long major;
36 unsigned long minor_start, minor_end;
37 char *name;
38 unsigned int is_ptmx: 1;
39 unsigned int is_pts: 1;
40 };
41
42 struct cdev {
43 struct file file;
44 const char *devdrv;
45 const struct cdev_ops *cdev_ops;
46 void *cdev_data;
47 };
48
49 struct cdev_ops {
50 const struct cdev_ops *parent;
51 bool (*probe)(struct cdev *);
52 char * (*get_name)(struct cdev *);
53 bool (*fill_column)(struct proc *,
54 struct cdev *,
55 struct libscols_line *,
56 int,
57 size_t,
58 char **);
59 void (*init)(const struct cdev *);
60 void (*free)(const struct cdev *);
61 void (*attach_xinfo)(struct cdev *);
62 int (*handle_fdinfo)(struct cdev *, const char *, const char *);
63 const struct ipc_class * (*get_ipc_class)(struct cdev *);
64 };
65
66 static bool cdev_fill_column(struct proc *proc __attribute__((__unused__)),
67 struct file *file,
68 struct libscols_line *ln,
69 int column_id,
70 size_t column_index)
71 {
72 struct cdev *cdev = (struct cdev *)file;
73 const struct cdev_ops *ops = cdev->cdev_ops;
74 char *str = NULL;
75
76 switch(column_id) {
77 case COL_NAME:
78 if (cdev->cdev_ops->get_name) {
79 str = cdev->cdev_ops->get_name(cdev);
80 if (str)
81 break;
82 }
83 return false;
84 case COL_TYPE:
85 if (scols_line_set_data(ln, column_index, "CHR"))
86 err(EXIT_FAILURE, _("failed to add output data"));
87 return true;
88 case COL_DEVTYPE:
89 if (scols_line_set_data(ln, column_index,
90 "char"))
91 err(EXIT_FAILURE, _("failed to add output data"));
92 return true;
93 case COL_CHRDRV:
94 if (cdev->devdrv)
95 str = xstrdup(cdev->devdrv);
96 else
97 xasprintf(&str, "%u",
98 major(file->stat.st_rdev));
99 break;
100 default:
101 while (ops) {
102 if (ops->fill_column
103 && ops->fill_column(proc, cdev, ln,
104 column_id, column_index, &str))
105 goto out;
106 ops = ops->parent;
107 }
108 return false;
109 }
110
111 out:
112 if (!str)
113 err(EXIT_FAILURE, _("failed to add output data"));
114 if (scols_line_refer_data(ln, column_index, str))
115 err(EXIT_FAILURE, _("failed to add output data"));
116 return true;
117 }
118
119 static struct miscdev *new_miscdev(unsigned long minor, const char *name)
120 {
121 struct miscdev *miscdev = xcalloc(1, sizeof(*miscdev));
122
123 INIT_LIST_HEAD(&miscdev->miscdevs);
124
125 miscdev->minor = minor;
126 miscdev->name = xstrdup(name);
127
128 return miscdev;
129 }
130
131 static void free_miscdev(struct miscdev *miscdev)
132 {
133 free(miscdev->name);
134 free(miscdev);
135 }
136
137 static void read_misc(struct list_head *miscdevs_list, FILE *misc_fp)
138 {
139 unsigned long minor;
140 char line[256];
141 char name[sizeof(line)];
142
143 while (fgets(line, sizeof(line), misc_fp)) {
144 struct miscdev *miscdev;
145
146 if (sscanf(line, "%lu %s", &minor, name) != 2)
147 continue;
148
149 miscdev = new_miscdev(minor, name);
150 list_add_tail(&miscdev->miscdevs, miscdevs_list);
151 }
152 }
153
154 #define TTY_DRIVERS_LINE_LEN0 1023
155 #define TTY_DRIVERS_LINE_LEN (TTY_DRIVERS_LINE_LEN0 + 1)
156 static struct ttydrv *new_ttydrv(unsigned int major,
157 unsigned int minor_start, unsigned int minor_end,
158 const char *name)
159 {
160 struct ttydrv *ttydrv = xmalloc(sizeof(*ttydrv));
161
162 INIT_LIST_HEAD(&ttydrv->ttydrvs);
163
164 ttydrv->major = major;
165 ttydrv->minor_start = minor_start;
166 ttydrv->minor_end = minor_end;
167 ttydrv->name = xstrdup(name);
168 ttydrv->is_ptmx = 0;
169 if (strcmp(name, "ptmx") == 0)
170 ttydrv->is_ptmx = 1;
171 ttydrv->is_pts = 0;
172 if (strcmp(name, "pts") == 0)
173 ttydrv->is_pts = 1;
174
175 return ttydrv;
176 }
177
178 static void free_ttydrv(struct ttydrv *ttydrv)
179 {
180 free(ttydrv->name);
181 free(ttydrv);
182 }
183
184 static bool is_pty(const struct ttydrv *ttydrv)
185 {
186 return ttydrv->is_ptmx || ttydrv->is_pts;
187 }
188
189 static struct ttydrv *read_ttydrv(const char *line)
190 {
191 const char *p;
192 char name[TTY_DRIVERS_LINE_LEN];
193 unsigned long major;
194 unsigned long minor_range[2];
195
196 p = strchr(line, ' ');
197 if (p == NULL)
198 return NULL;
199
200 p = strstr(p, "/dev/");
201 if (p == NULL)
202 return NULL;
203 p += (sizeof("/dev/") - 1); /* Ignore the last null byte. */
204
205 if (sscanf(p, "%" stringify_value(TTY_DRIVERS_LINE_LEN0) "[^ ]", name) != 1)
206 return NULL;
207
208 p += strlen(name);
209 if (sscanf(p, " %lu %lu-%lu ", &major,
210 minor_range, minor_range + 1) != 3) {
211 if (sscanf(p, " %lu %lu ", &major, minor_range) == 2)
212 minor_range[1] = minor_range[0];
213 else
214 return NULL;
215 }
216
217 return new_ttydrv(major, minor_range[0], minor_range[1], name);
218 }
219
220 static void read_tty_drivers(struct list_head *ttydrvs_list, FILE *ttydrvs_fp)
221 {
222 char line[TTY_DRIVERS_LINE_LEN];
223
224 while (fgets(line, sizeof(line), ttydrvs_fp)) {
225 struct ttydrv *ttydrv = read_ttydrv(line);
226 if (ttydrv)
227 list_add_tail(&ttydrv->ttydrvs, ttydrvs_list);
228 }
229 }
230
231 static void cdev_class_initialize(void)
232 {
233 FILE *misc_fp;
234 FILE *ttydrvs_fp;
235
236 INIT_LIST_HEAD(&miscdevs);
237 INIT_LIST_HEAD(&ttydrvs);
238
239 misc_fp = fopen("/proc/misc", "r");
240 if (misc_fp) {
241 read_misc(&miscdevs, misc_fp);
242 fclose(misc_fp);
243 }
244
245 ttydrvs_fp = fopen("/proc/tty/drivers", "r");
246 if (ttydrvs_fp) {
247 read_tty_drivers(&ttydrvs, ttydrvs_fp);
248 fclose(ttydrvs_fp);
249 }
250 }
251
252 static void cdev_class_finalize(void)
253 {
254 list_free(&miscdevs, struct miscdev, miscdevs, free_miscdev);
255 list_free(&ttydrvs, struct ttydrv, ttydrvs, free_ttydrv);
256 }
257
258 const char *get_miscdev(unsigned long minor)
259 {
260 struct list_head *c;
261 list_for_each(c, &miscdevs) {
262 struct miscdev *miscdev = list_entry(c, struct miscdev, miscdevs);
263 if (miscdev->minor == minor)
264 return miscdev->name;
265 }
266 return NULL;
267 }
268
269 static const struct ttydrv *get_ttydrv(unsigned long major,
270 unsigned long minor)
271 {
272 struct list_head *c;
273
274 list_for_each(c, &ttydrvs) {
275 struct ttydrv *ttydrv = list_entry(c, struct ttydrv, ttydrvs);
276 if (ttydrv->major == major
277 && ttydrv->minor_start <= minor
278 && minor <= ttydrv->minor_end)
279 return ttydrv;
280 }
281
282 return NULL;
283 }
284
285
286 /*
287 * generic (fallback implementation)
288 */
289 static bool cdev_generic_probe(struct cdev *cdev __attribute__((__unused__))) {
290 return true;
291 }
292
293 static bool cdev_generic_fill_column(struct proc *proc __attribute__((__unused__)),
294 struct cdev *cdev,
295 struct libscols_line *ln __attribute__((__unused__)),
296 int column_id,
297 size_t column_index __attribute__((__unused__)),
298 char **str)
299 {
300 struct file *file = &cdev->file;
301
302 switch(column_id) {
303 case COL_SOURCE:
304 if (cdev->devdrv) {
305 xasprintf(str, "%s:%u", cdev->devdrv,
306 minor(file->stat.st_rdev));
307 return true;
308 }
309 /* FALL THROUGH */
310 case COL_MAJMIN:
311 xasprintf(str, "%u:%u",
312 major(file->stat.st_rdev),
313 minor(file->stat.st_rdev));
314 return true;
315 default:
316 return false;
317 }
318 }
319
320 static struct cdev_ops cdev_generic_ops = {
321 .probe = cdev_generic_probe,
322 .fill_column = cdev_generic_fill_column,
323 };
324
325 /*
326 * misc device driver
327 */
328 static bool cdev_misc_probe(struct cdev *cdev) {
329 return cdev->devdrv && strcmp(cdev->devdrv, "misc") == 0;
330 }
331
332 static bool cdev_misc_fill_column(struct proc *proc __attribute__((__unused__)),
333 struct cdev *cdev,
334 struct libscols_line *ln __attribute__((__unused__)),
335 int column_id,
336 size_t column_index __attribute__((__unused__)),
337 char **str)
338 {
339 struct file *file = &cdev->file;
340 const char *miscdev;
341
342 switch(column_id) {
343 case COL_MISCDEV:
344 miscdev = get_miscdev(minor(file->stat.st_rdev));
345 if (miscdev)
346 *str = xstrdup(miscdev);
347 else
348 xasprintf(str, "%u",
349 minor(file->stat.st_rdev));
350 return true;
351 case COL_SOURCE:
352 miscdev = get_miscdev(minor(file->stat.st_rdev));
353 if (miscdev)
354 xasprintf(str, "misc:%s", miscdev);
355 else
356 xasprintf(str, "misc:%u",
357 minor(file->stat.st_rdev));
358 return true;
359 }
360 return false;
361 }
362
363 static struct cdev_ops cdev_misc_ops = {
364 .parent = &cdev_generic_ops,
365 .probe = cdev_misc_probe,
366 .fill_column = cdev_misc_fill_column,
367 };
368
369 /*
370 * tun devcie driver
371 */
372 static bool cdev_tun_probe(struct cdev *cdev)
373 {
374 const char *miscdev;
375
376 if ((!cdev->devdrv) || strcmp(cdev->devdrv, "misc"))
377 return false;
378
379 miscdev = get_miscdev(minor(cdev->file.stat.st_rdev));
380 if (miscdev && strcmp(miscdev, "tun") == 0)
381 return true;
382 return false;
383 }
384
385 static void cdev_tun_free(const struct cdev *cdev)
386 {
387 if (cdev->cdev_data)
388 free(cdev->cdev_data);
389 }
390
391 static char * cdev_tun_get_name(struct cdev *cdev)
392 {
393 char *str = NULL;
394
395 if (cdev->cdev_data == NULL)
396 return NULL;
397
398 xasprintf(&str, "iface=%s", (const char *)cdev->cdev_data);
399 return str;
400 }
401
402 static bool cdev_tun_fill_column(struct proc *proc __attribute__((__unused__)),
403 struct cdev *cdev,
404 struct libscols_line *ln __attribute__((__unused__)),
405 int column_id,
406 size_t column_index __attribute__((__unused__)),
407 char **str)
408 {
409 switch(column_id) {
410 case COL_MISCDEV:
411 *str = xstrdup("tun");
412 return true;
413 case COL_SOURCE:
414 *str = xstrdup("misc:tun");
415 return true;
416 case COL_TUN_IFACE:
417 if (cdev->cdev_data) {
418 *str = xstrdup(cdev->cdev_data);
419 return true;
420 }
421 }
422 return false;
423 }
424
425 static int cdev_tun_handle_fdinfo(struct cdev *cdev, const char *key, const char *val)
426 {
427 if (strcmp(key, "iff") == 0 && cdev->cdev_data == NULL) {
428 cdev->cdev_data = xstrdup(val);
429 return 1;
430 }
431 return false;
432 }
433
434 static struct cdev_ops cdev_tun_ops = {
435 .parent = &cdev_misc_ops,
436 .probe = cdev_tun_probe,
437 .free = cdev_tun_free,
438 .get_name = cdev_tun_get_name,
439 .fill_column = cdev_tun_fill_column,
440 .handle_fdinfo = cdev_tun_handle_fdinfo,
441 };
442
443 /*
444 * tty devices
445 */
446 struct ttydata {
447 struct cdev *cdev;
448 const struct ttydrv *drv;
449 #define NO_TTY_INDEX -1
450 int tty_index; /* used only in ptmx devices */
451 struct ipc_endpoint endpoint;
452 };
453
454 static bool cdev_tty_probe(struct cdev *cdev) {
455 const struct ttydrv *ttydrv = get_ttydrv(major(cdev->file.stat.st_rdev),
456 minor(cdev->file.stat.st_rdev));
457 struct ttydata *data;
458
459 if (!ttydrv)
460 return false;
461
462 data = xmalloc(sizeof(struct ttydata));
463 data->cdev = cdev;
464 data->drv = ttydrv;
465 data->tty_index = NO_TTY_INDEX;
466 cdev->cdev_data = data;
467
468 return true;
469 }
470
471 static void cdev_tty_free(const struct cdev *cdev)
472 {
473 if (cdev->cdev_data)
474 free(cdev->cdev_data);
475 }
476
477 static char * cdev_tty_get_name(struct cdev *cdev)
478 {
479 struct ttydata *data = cdev->cdev_data;
480 char *str = NULL;
481
482 if (!data->drv->is_ptmx)
483 return NULL;
484
485 if (data->tty_index == NO_TTY_INDEX)
486 str = xstrdup("tty-index=");
487 else
488 xasprintf(&str, "tty-index=%d", data->tty_index);
489 return str;
490 }
491
492 static inline char *cdev_tty_xstrendpoint(struct file *file)
493 {
494 char *str = NULL;
495 xasprintf(&str, "%d,%s,%d%c%c",
496 file->proc->pid, file->proc->command, file->association,
497 (file->mode & S_IRUSR)? 'r': '-',
498 (file->mode & S_IWUSR)? 'w': '-');
499 return str;
500 }
501
502 static bool cdev_tty_fill_column(struct proc *proc __attribute__((__unused__)),
503 struct cdev *cdev,
504 struct libscols_line *ln __attribute__((__unused__)),
505 int column_id,
506 size_t column_index __attribute__((__unused__)),
507 char **str)
508 {
509 struct file *file = &cdev->file;
510 struct ttydata *data = cdev->cdev_data;
511
512 switch(column_id) {
513 case COL_SOURCE:
514 if (data->drv->minor_start == data->drv->minor_end)
515 *str = xstrdup(data->drv->name);
516 else
517 xasprintf(str, "%s:%u", data->drv->name,
518 minor(file->stat.st_rdev));
519 return true;
520 case COL_PTMX_TTY_INDEX:
521 if (data->drv->is_ptmx) {
522 xasprintf(str, "%d", data->tty_index);
523 return true;
524 }
525 return false;
526 case COL_ENDPOINTS:
527 if (is_pty(data->drv)) {
528 struct ttydata *this = data;
529 struct list_head *e;
530 foreach_endpoint(e, data->endpoint) {
531 char *estr;
532 struct ttydata *other = list_entry(e, struct ttydata, endpoint.endpoints);
533 if (this == other)
534 continue;
535
536 if ((this->drv->is_ptmx && !other->drv->is_pts)
537 || (this->drv->is_pts && !other->drv->is_ptmx))
538 continue;
539
540 if (*str)
541 xstrputc(str, '\n');
542 estr = cdev_tty_xstrendpoint(&other->cdev->file);
543 xstrappend(str, estr);
544 free(estr);
545 }
546 if (*str)
547 return true;
548 }
549 return false;
550 default:
551 return false;
552 }
553 }
554
555 static int cdev_tty_handle_fdinfo(struct cdev *cdev, const char *key, const char *val)
556 {
557 struct ttydata *data = cdev->cdev_data;
558
559 if (!data->drv->is_ptmx)
560 return 0;
561
562 if (strcmp(key, "tty-index") == 0) {
563 errno = 0;
564 data->tty_index = (int)strtol(val, NULL, 10);
565 if (errno) {
566 data->tty_index = NO_TTY_INDEX;
567 return 0;
568 }
569 return 1;
570 }
571
572 return 0;
573 }
574
575 struct cdev_pty_ipc {
576 struct ipc ipc;
577 int tty_index;
578 };
579
580 static unsigned int cdev_pty_get_hash(struct file *file)
581 {
582 struct cdev *cdev = (struct cdev *)file;
583 struct ttydata *data = cdev->cdev_data;
584
585 return data->drv->is_ptmx?
586 (unsigned int)data->tty_index:
587 (unsigned int)minor(file->stat.st_rdev);
588 }
589
590 static bool cdev_pty_is_suitable_ipc(struct ipc *ipc, struct file *file)
591 {
592 struct cdev *cdev = (struct cdev *)file;
593 struct ttydata *data = cdev->cdev_data;
594 struct cdev_pty_ipc *cdev_pty_ipc = (struct cdev_pty_ipc *)ipc;
595
596 return (data->drv->is_ptmx)?
597 cdev_pty_ipc->tty_index == (int)data->tty_index:
598 cdev_pty_ipc->tty_index == (int)minor(file->stat.st_rdev);
599 }
600
601 static const struct ipc_class *cdev_tty_get_ipc_class(struct cdev *cdev)
602 {
603 static const struct ipc_class cdev_pty_ipc_class = {
604 .size = sizeof(struct cdev_pty_ipc),
605 .get_hash = cdev_pty_get_hash,
606 .is_suitable_ipc = cdev_pty_is_suitable_ipc,
607 };
608
609 struct ttydata *data = cdev->cdev_data;
610
611 if (is_pty(data->drv))
612 return &cdev_pty_ipc_class;
613
614 return NULL;
615 }
616
617 static void cdev_tty_attach_xinfo(struct cdev *cdev)
618 {
619 struct ttydata *data = cdev->cdev_data;
620 struct ipc *ipc;
621 unsigned int hash;
622
623
624 if (! is_pty(data->drv))
625 return;
626
627 init_endpoint(&data->endpoint);
628 ipc = get_ipc(&cdev->file);
629 if (ipc)
630 goto link;
631
632 ipc = new_ipc(cdev_tty_get_ipc_class(cdev));
633 hash = cdev_pty_get_hash(&cdev->file);
634 ((struct cdev_pty_ipc *)ipc)->tty_index = (int)hash;
635
636 add_ipc(ipc, hash);
637 link:
638 add_endpoint(&data->endpoint, ipc);
639 }
640
641 static struct cdev_ops cdev_tty_ops = {
642 .parent = &cdev_generic_ops,
643 .probe = cdev_tty_probe,
644 .free = cdev_tty_free,
645 .get_name = cdev_tty_get_name,
646 .fill_column = cdev_tty_fill_column,
647 .attach_xinfo = cdev_tty_attach_xinfo,
648 .handle_fdinfo = cdev_tty_handle_fdinfo,
649 .get_ipc_class = cdev_tty_get_ipc_class,
650 };
651
652 static const struct cdev_ops *cdev_ops[] = {
653 &cdev_tun_ops,
654 &cdev_misc_ops,
655 &cdev_tty_ops,
656 &cdev_generic_ops /* This must be at the end. */
657 };
658
659 static const struct cdev_ops *cdev_probe(struct cdev *cdev)
660 {
661 const struct cdev_ops *r = NULL;
662
663 for (size_t i = 0; i < ARRAY_SIZE(cdev_ops); i++) {
664 if (cdev_ops[i]->probe(cdev)) {
665 r = cdev_ops[i];
666 break;
667 }
668 }
669
670 assert(r);
671 return r;
672 }
673
674 static void init_cdev_content(struct file *file)
675 {
676 struct cdev *cdev = (struct cdev *)file;
677
678 cdev->devdrv = get_chrdrv(major(file->stat.st_rdev));
679
680 cdev->cdev_data = NULL;
681 cdev->cdev_ops = cdev_probe(cdev);
682 if (cdev->cdev_ops->init)
683 cdev->cdev_ops->init(cdev);
684 }
685
686 static void free_cdev_content(struct file *file)
687 {
688 struct cdev *cdev = (struct cdev *)file;
689
690 if (cdev->cdev_ops->free)
691 cdev->cdev_ops->free(cdev);
692 }
693
694 static void cdev_attach_xinfo(struct file *file)
695 {
696 struct cdev *cdev = (struct cdev *)file;
697
698 if (cdev->cdev_ops->attach_xinfo)
699 cdev->cdev_ops->attach_xinfo(cdev);
700 }
701
702 static int cdev_handle_fdinfo(struct file *file, const char *key, const char *value)
703 {
704 struct cdev *cdev = (struct cdev *)file;
705
706 if (cdev->cdev_ops->handle_fdinfo)
707 return cdev->cdev_ops->handle_fdinfo(cdev, key, value);
708 return 0; /* Should be handled in parents */
709 }
710
711 static const struct ipc_class *cdev_get_ipc_class(struct file *file)
712 {
713 struct cdev *cdev = (struct cdev *)file;
714
715 if (cdev->cdev_ops->get_ipc_class)
716 return cdev->cdev_ops->get_ipc_class(cdev);
717 return NULL;
718 }
719
720 const struct file_class cdev_class = {
721 .super = &file_class,
722 .size = sizeof(struct cdev),
723 .initialize_class = cdev_class_initialize,
724 .finalize_class = cdev_class_finalize,
725 .fill_column = cdev_fill_column,
726 .initialize_content = init_cdev_content,
727 .free_content = free_cdev_content,
728 .attach_xinfo = cdev_attach_xinfo,
729 .handle_fdinfo = cdev_handle_fdinfo,
730 .get_ipc_class = cdev_get_ipc_class,
731 };