2 * lsfd-cdev.c - handle associations opening character devices
4 * Copyright (C) 2021 Red Hat, Inc. All rights reserved.
5 * Written by Masatake YAMATO <yamato@redhat.com>
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.
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.
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
24 static struct list_head miscdevs
;
25 static struct list_head ttydrvs
;
28 struct list_head miscdevs
;
34 struct list_head ttydrvs
;
36 unsigned long minor_start
, minor_end
;
38 unsigned int is_ptmx
: 1;
39 unsigned int is_pts
: 1;
45 const struct cdev_ops
*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
*,
55 struct libscols_line
*,
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
*);
66 static bool cdev_fill_column(struct proc
*proc
__attribute__((__unused__
)),
68 struct libscols_line
*ln
,
72 struct cdev
*cdev
= (struct cdev
*)file
;
73 const struct cdev_ops
*ops
= cdev
->cdev_ops
;
78 if (cdev
->cdev_ops
->get_name
) {
79 str
= cdev
->cdev_ops
->get_name(cdev
);
85 if (scols_line_set_data(ln
, column_index
, "CHR"))
86 err(EXIT_FAILURE
, _("failed to add output data"));
89 if (scols_line_set_data(ln
, column_index
,
91 err(EXIT_FAILURE
, _("failed to add output data"));
95 str
= xstrdup(cdev
->devdrv
);
98 major(file
->stat
.st_rdev
));
103 && ops
->fill_column(proc
, cdev
, ln
,
104 column_id
, column_index
, &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"));
119 static struct miscdev
*new_miscdev(unsigned long minor
, const char *name
)
121 struct miscdev
*miscdev
= xcalloc(1, sizeof(*miscdev
));
123 INIT_LIST_HEAD(&miscdev
->miscdevs
);
125 miscdev
->minor
= minor
;
126 miscdev
->name
= xstrdup(name
);
131 static void free_miscdev(struct miscdev
*miscdev
)
137 static void read_misc(struct list_head
*miscdevs_list
, FILE *misc_fp
)
141 char name
[sizeof(line
)];
143 while (fgets(line
, sizeof(line
), misc_fp
)) {
144 struct miscdev
*miscdev
;
146 if (sscanf(line
, "%lu %s", &minor
, name
) != 2)
149 miscdev
= new_miscdev(minor
, name
);
150 list_add_tail(&miscdev
->miscdevs
, miscdevs_list
);
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
,
160 struct ttydrv
*ttydrv
= xmalloc(sizeof(*ttydrv
));
162 INIT_LIST_HEAD(&ttydrv
->ttydrvs
);
164 ttydrv
->major
= major
;
165 ttydrv
->minor_start
= minor_start
;
166 ttydrv
->minor_end
= minor_end
;
167 ttydrv
->name
= xstrdup(name
);
169 if (strcmp(name
, "ptmx") == 0)
172 if (strcmp(name
, "pts") == 0)
178 static void free_ttydrv(struct ttydrv
*ttydrv
)
184 static bool is_pty(const struct ttydrv
*ttydrv
)
186 return ttydrv
->is_ptmx
|| ttydrv
->is_pts
;
189 static struct ttydrv
*read_ttydrv(const char *line
)
192 char name
[TTY_DRIVERS_LINE_LEN
];
194 unsigned long minor_range
[2];
196 p
= strchr(line
, ' ');
200 p
= strstr(p
, "/dev/");
203 p
+= (sizeof("/dev/") - 1); /* Ignore the last null byte. */
205 if (sscanf(p
, "%" stringify_value(TTY_DRIVERS_LINE_LEN0
) "[^ ]", name
) != 1)
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];
217 return new_ttydrv(major
, minor_range
[0], minor_range
[1], name
);
220 static void read_tty_drivers(struct list_head
*ttydrvs_list
, FILE *ttydrvs_fp
)
222 char line
[TTY_DRIVERS_LINE_LEN
];
224 while (fgets(line
, sizeof(line
), ttydrvs_fp
)) {
225 struct ttydrv
*ttydrv
= read_ttydrv(line
);
227 list_add_tail(&ttydrv
->ttydrvs
, ttydrvs_list
);
231 static void cdev_class_initialize(void)
236 INIT_LIST_HEAD(&miscdevs
);
237 INIT_LIST_HEAD(&ttydrvs
);
239 misc_fp
= fopen("/proc/misc", "r");
241 read_misc(&miscdevs
, misc_fp
);
245 ttydrvs_fp
= fopen("/proc/tty/drivers", "r");
247 read_tty_drivers(&ttydrvs
, ttydrvs_fp
);
252 static void cdev_class_finalize(void)
254 list_free(&miscdevs
, struct miscdev
, miscdevs
, free_miscdev
);
255 list_free(&ttydrvs
, struct ttydrv
, ttydrvs
, free_ttydrv
);
258 const char *get_miscdev(unsigned long minor
)
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
;
269 static const struct ttydrv
*get_ttydrv(unsigned long major
,
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
)
287 * generic (fallback implementation)
289 static bool cdev_generic_probe(struct cdev
*cdev
__attribute__((__unused__
))) {
293 static bool cdev_generic_fill_column(struct proc
*proc
__attribute__((__unused__
)),
295 struct libscols_line
*ln
__attribute__((__unused__
)),
297 size_t column_index
__attribute__((__unused__
)),
300 struct file
*file
= &cdev
->file
;
305 xasprintf(str
, "%s:%u", cdev
->devdrv
,
306 minor(file
->stat
.st_rdev
));
311 xasprintf(str
, "%u:%u",
312 major(file
->stat
.st_rdev
),
313 minor(file
->stat
.st_rdev
));
320 static struct cdev_ops cdev_generic_ops
= {
321 .probe
= cdev_generic_probe
,
322 .fill_column
= cdev_generic_fill_column
,
328 static bool cdev_misc_probe(struct cdev
*cdev
) {
329 return cdev
->devdrv
&& strcmp(cdev
->devdrv
, "misc") == 0;
332 static bool cdev_misc_fill_column(struct proc
*proc
__attribute__((__unused__
)),
334 struct libscols_line
*ln
__attribute__((__unused__
)),
336 size_t column_index
__attribute__((__unused__
)),
339 struct file
*file
= &cdev
->file
;
344 miscdev
= get_miscdev(minor(file
->stat
.st_rdev
));
346 *str
= xstrdup(miscdev
);
349 minor(file
->stat
.st_rdev
));
352 miscdev
= get_miscdev(minor(file
->stat
.st_rdev
));
354 xasprintf(str
, "misc:%s", miscdev
);
356 xasprintf(str
, "misc:%u",
357 minor(file
->stat
.st_rdev
));
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
,
372 static bool cdev_tun_probe(struct cdev
*cdev
)
376 if ((!cdev
->devdrv
) || strcmp(cdev
->devdrv
, "misc"))
379 miscdev
= get_miscdev(minor(cdev
->file
.stat
.st_rdev
));
380 if (miscdev
&& strcmp(miscdev
, "tun") == 0)
385 static void cdev_tun_free(const struct cdev
*cdev
)
388 free(cdev
->cdev_data
);
391 static char * cdev_tun_get_name(struct cdev
*cdev
)
395 if (cdev
->cdev_data
== NULL
)
398 xasprintf(&str
, "iface=%s", (const char *)cdev
->cdev_data
);
402 static bool cdev_tun_fill_column(struct proc
*proc
__attribute__((__unused__
)),
404 struct libscols_line
*ln
__attribute__((__unused__
)),
406 size_t column_index
__attribute__((__unused__
)),
411 *str
= xstrdup("tun");
414 *str
= xstrdup("misc:tun");
417 if (cdev
->cdev_data
) {
418 *str
= xstrdup(cdev
->cdev_data
);
425 static int cdev_tun_handle_fdinfo(struct cdev
*cdev
, const char *key
, const char *val
)
427 if (strcmp(key
, "iff") == 0 && cdev
->cdev_data
== NULL
) {
428 cdev
->cdev_data
= xstrdup(val
);
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
,
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
;
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
;
462 data
= xmalloc(sizeof(struct ttydata
));
465 data
->tty_index
= NO_TTY_INDEX
;
466 cdev
->cdev_data
= data
;
471 static void cdev_tty_free(const struct cdev
*cdev
)
474 free(cdev
->cdev_data
);
477 static char * cdev_tty_get_name(struct cdev
*cdev
)
479 struct ttydata
*data
= cdev
->cdev_data
;
482 if (!data
->drv
->is_ptmx
)
485 if (data
->tty_index
== NO_TTY_INDEX
)
486 str
= xstrdup("tty-index=");
488 xasprintf(&str
, "tty-index=%d", data
->tty_index
);
492 static inline char *cdev_tty_xstrendpoint(struct file
*file
)
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': '-');
502 static bool cdev_tty_fill_column(struct proc
*proc
__attribute__((__unused__
)),
504 struct libscols_line
*ln
__attribute__((__unused__
)),
506 size_t column_index
__attribute__((__unused__
)),
509 struct file
*file
= &cdev
->file
;
510 struct ttydata
*data
= cdev
->cdev_data
;
514 if (data
->drv
->minor_start
== data
->drv
->minor_end
)
515 *str
= xstrdup(data
->drv
->name
);
517 xasprintf(str
, "%s:%u", data
->drv
->name
,
518 minor(file
->stat
.st_rdev
));
520 case COL_PTMX_TTY_INDEX
:
521 if (data
->drv
->is_ptmx
) {
522 xasprintf(str
, "%d", data
->tty_index
);
527 if (is_pty(data
->drv
)) {
528 struct ttydata
*this = data
;
530 foreach_endpoint(e
, data
->endpoint
) {
532 struct ttydata
*other
= list_entry(e
, struct ttydata
, endpoint
.endpoints
);
536 if ((this->drv
->is_ptmx
&& !other
->drv
->is_pts
)
537 || (this->drv
->is_pts
&& !other
->drv
->is_ptmx
))
542 estr
= cdev_tty_xstrendpoint(&other
->cdev
->file
);
543 xstrappend(str
, estr
);
555 static int cdev_tty_handle_fdinfo(struct cdev
*cdev
, const char *key
, const char *val
)
557 struct ttydata
*data
= cdev
->cdev_data
;
559 if (!data
->drv
->is_ptmx
)
562 if (strcmp(key
, "tty-index") == 0) {
564 data
->tty_index
= (int)strtol(val
, NULL
, 10);
566 data
->tty_index
= NO_TTY_INDEX
;
575 struct cdev_pty_ipc
{
580 static unsigned int cdev_pty_get_hash(struct file
*file
)
582 struct cdev
*cdev
= (struct cdev
*)file
;
583 struct ttydata
*data
= cdev
->cdev_data
;
585 return data
->drv
->is_ptmx
?
586 (unsigned int)data
->tty_index
:
587 (unsigned int)minor(file
->stat
.st_rdev
);
590 static bool cdev_pty_is_suitable_ipc(struct ipc
*ipc
, struct file
*file
)
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
;
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
);
601 static const struct ipc_class
*cdev_tty_get_ipc_class(struct cdev
*cdev
)
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
,
609 struct ttydata
*data
= cdev
->cdev_data
;
611 if (is_pty(data
->drv
))
612 return &cdev_pty_ipc_class
;
617 static void cdev_tty_attach_xinfo(struct cdev
*cdev
)
619 struct ttydata
*data
= cdev
->cdev_data
;
624 if (! is_pty(data
->drv
))
627 init_endpoint(&data
->endpoint
);
628 ipc
= get_ipc(&cdev
->file
);
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
;
638 add_endpoint(&data
->endpoint
, ipc
);
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
,
652 static const struct cdev_ops
*cdev_ops
[] = {
656 &cdev_generic_ops
/* This must be at the end. */
659 static const struct cdev_ops
*cdev_probe(struct cdev
*cdev
)
661 const struct cdev_ops
*r
= NULL
;
663 for (size_t i
= 0; i
< ARRAY_SIZE(cdev_ops
); i
++) {
664 if (cdev_ops
[i
]->probe(cdev
)) {
674 static void init_cdev_content(struct file
*file
)
676 struct cdev
*cdev
= (struct cdev
*)file
;
678 cdev
->devdrv
= get_chrdrv(major(file
->stat
.st_rdev
));
680 cdev
->cdev_data
= NULL
;
681 cdev
->cdev_ops
= cdev_probe(cdev
);
682 if (cdev
->cdev_ops
->init
)
683 cdev
->cdev_ops
->init(cdev
);
686 static void free_cdev_content(struct file
*file
)
688 struct cdev
*cdev
= (struct cdev
*)file
;
690 if (cdev
->cdev_ops
->free
)
691 cdev
->cdev_ops
->free(cdev
);
694 static void cdev_attach_xinfo(struct file
*file
)
696 struct cdev
*cdev
= (struct cdev
*)file
;
698 if (cdev
->cdev_ops
->attach_xinfo
)
699 cdev
->cdev_ops
->attach_xinfo(cdev
);
702 static int cdev_handle_fdinfo(struct file
*file
, const char *key
, const char *value
)
704 struct cdev
*cdev
= (struct cdev
*)file
;
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 */
711 static const struct ipc_class
*cdev_get_ipc_class(struct file
*file
)
713 struct cdev
*cdev
= (struct cdev
*)file
;
715 if (cdev
->cdev_ops
->get_ipc_class
)
716 return cdev
->cdev_ops
->get_ipc_class(cdev
);
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
,