diff options
Diffstat (limited to 'misc-utils/lsfd-cdev.c')
-rw-r--r-- | misc-utils/lsfd-cdev.c | 641 |
1 files changed, 597 insertions, 44 deletions
diff --git a/misc-utils/lsfd-cdev.c b/misc-utils/lsfd-cdev.c index 4e35f15..cbb2d05 100644 --- a/misc-utils/lsfd-cdev.c +++ b/misc-utils/lsfd-cdev.c @@ -19,13 +19,10 @@ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include "xalloc.h" -#include "nls.h" -#include "libsmartcols.h" - #include "lsfd.h" static struct list_head miscdevs; +static struct list_head ttydrvs; struct miscdev { struct list_head miscdevs; @@ -33,70 +30,85 @@ struct miscdev { char *name; }; +struct ttydrv { + struct list_head ttydrvs; + unsigned long major; + unsigned long minor_start, minor_end; + char *name; + unsigned int is_ptmx: 1; + unsigned int is_pts: 1; +}; + +struct cdev { + struct file file; + const char *devdrv; + const struct cdev_ops *cdev_ops; + void *cdev_data; +}; + +struct cdev_ops { + const struct cdev_ops *parent; + bool (*probe)(struct cdev *); + char * (*get_name)(struct cdev *); + bool (*fill_column)(struct proc *, + struct cdev *, + struct libscols_line *, + int, + size_t, + char **); + void (*init)(const struct cdev *); + void (*free)(const struct cdev *); + void (*attach_xinfo)(struct cdev *); + int (*handle_fdinfo)(struct cdev *, const char *, const char *); + const struct ipc_class * (*get_ipc_class)(struct cdev *); +}; + static bool cdev_fill_column(struct proc *proc __attribute__((__unused__)), - struct file *file __attribute__((__unused__)), + struct file *file, struct libscols_line *ln, int column_id, size_t column_index) { + struct cdev *cdev = (struct cdev *)file; + const struct cdev_ops *ops = cdev->cdev_ops; char *str = NULL; - const char *devdrv; - const char *miscdev; switch(column_id) { + case COL_NAME: + if (cdev->cdev_ops->get_name) { + str = cdev->cdev_ops->get_name(cdev); + if (str) + break; + } + return false; case COL_TYPE: if (scols_line_set_data(ln, column_index, "CHR")) err(EXIT_FAILURE, _("failed to add output data")); return true; - case COL_MISCDEV: - devdrv = get_chrdrv(major(file->stat.st_rdev)); - if (devdrv && strcmp(devdrv, "misc") == 0) { - miscdev = get_miscdev(minor(file->stat.st_rdev)); - if (miscdev) - str = xstrdup(miscdev); - else - xasprintf(&str, "%u", - minor(file->stat.st_rdev)); - break; - } - return true; case COL_DEVTYPE: if (scols_line_set_data(ln, column_index, "char")) err(EXIT_FAILURE, _("failed to add output data")); return true; case COL_CHRDRV: - devdrv = get_chrdrv(major(file->stat.st_rdev)); - if (devdrv) - str = xstrdup(devdrv); + if (cdev->devdrv) + str = xstrdup(cdev->devdrv); else xasprintf(&str, "%u", major(file->stat.st_rdev)); break; - case COL_SOURCE: - devdrv = get_chrdrv(major(file->stat.st_rdev)); - miscdev = NULL; - if (devdrv && strcmp(devdrv, "misc") == 0) - miscdev = get_miscdev(minor(file->stat.st_rdev)); - if (devdrv) { - if (miscdev) { - xasprintf(&str, "misc:%s", miscdev); - } else { - xasprintf(&str, "%s:%u", devdrv, - minor(file->stat.st_rdev)); - } - break; - } - /* FALL THROUGH */ - case COL_MAJMIN: - xasprintf(&str, "%u:%u", - major(file->stat.st_rdev), - minor(file->stat.st_rdev)); - break; default: + while (ops) { + if (ops->fill_column + && ops->fill_column(proc, cdev, ln, + column_id, column_index, &str)) + goto out; + ops = ops->parent; + } return false; } + out: if (!str) err(EXIT_FAILURE, _("failed to add output data")); if (scols_line_refer_data(ln, column_index, str)) @@ -139,22 +151,108 @@ static void read_misc(struct list_head *miscdevs_list, FILE *misc_fp) } } +#define TTY_DRIVERS_LINE_LEN0 1023 +#define TTY_DRIVERS_LINE_LEN (TTY_DRIVERS_LINE_LEN0 + 1) +static struct ttydrv *new_ttydrv(unsigned int major, + unsigned int minor_start, unsigned int minor_end, + const char *name) +{ + struct ttydrv *ttydrv = xmalloc(sizeof(*ttydrv)); + + INIT_LIST_HEAD(&ttydrv->ttydrvs); + + ttydrv->major = major; + ttydrv->minor_start = minor_start; + ttydrv->minor_end = minor_end; + ttydrv->name = xstrdup(name); + ttydrv->is_ptmx = 0; + if (strcmp(name, "ptmx") == 0) + ttydrv->is_ptmx = 1; + ttydrv->is_pts = 0; + if (strcmp(name, "pts") == 0) + ttydrv->is_pts = 1; + + return ttydrv; +} + +static void free_ttydrv(struct ttydrv *ttydrv) +{ + free(ttydrv->name); + free(ttydrv); +} + +static bool is_pty(const struct ttydrv *ttydrv) +{ + return ttydrv->is_ptmx || ttydrv->is_pts; +} + +static struct ttydrv *read_ttydrv(const char *line) +{ + const char *p; + char name[TTY_DRIVERS_LINE_LEN]; + unsigned long major; + unsigned long minor_range[2]; + + p = strchr(line, ' '); + if (p == NULL) + return NULL; + + p = strstr(p, "/dev/"); + if (p == NULL) + return NULL; + p += (sizeof("/dev/") - 1); /* Ignore the last null byte. */ + + if (sscanf(p, "%" stringify_value(TTY_DRIVERS_LINE_LEN0) "[^ ]", name) != 1) + return NULL; + + p += strlen(name); + if (sscanf(p, " %lu %lu-%lu ", &major, + minor_range, minor_range + 1) != 3) { + if (sscanf(p, " %lu %lu ", &major, minor_range) == 2) + minor_range[1] = minor_range[0]; + else + return NULL; + } + + return new_ttydrv(major, minor_range[0], minor_range[1], name); +} + +static void read_tty_drivers(struct list_head *ttydrvs_list, FILE *ttydrvs_fp) +{ + char line[TTY_DRIVERS_LINE_LEN]; + + while (fgets(line, sizeof(line), ttydrvs_fp)) { + struct ttydrv *ttydrv = read_ttydrv(line); + if (ttydrv) + list_add_tail(&ttydrv->ttydrvs, ttydrvs_list); + } +} + static void cdev_class_initialize(void) { FILE *misc_fp; + FILE *ttydrvs_fp; INIT_LIST_HEAD(&miscdevs); + INIT_LIST_HEAD(&ttydrvs); misc_fp = fopen("/proc/misc", "r"); if (misc_fp) { read_misc(&miscdevs, misc_fp); fclose(misc_fp); } + + ttydrvs_fp = fopen("/proc/tty/drivers", "r"); + if (ttydrvs_fp) { + read_tty_drivers(&ttydrvs, ttydrvs_fp); + fclose(ttydrvs_fp); + } } static void cdev_class_finalize(void) { list_free(&miscdevs, struct miscdev, miscdevs, free_miscdev); + list_free(&ttydrvs, struct ttydrv, ttydrvs, free_ttydrv); } const char *get_miscdev(unsigned long minor) @@ -168,11 +266,466 @@ const char *get_miscdev(unsigned long minor) return NULL; } +static const struct ttydrv *get_ttydrv(unsigned long major, + unsigned long minor) +{ + struct list_head *c; + + list_for_each(c, &ttydrvs) { + struct ttydrv *ttydrv = list_entry(c, struct ttydrv, ttydrvs); + if (ttydrv->major == major + && ttydrv->minor_start <= minor + && minor <= ttydrv->minor_end) + return ttydrv; + } + + return NULL; +} + + +/* + * generic (fallback implementation) + */ +static bool cdev_generic_probe(struct cdev *cdev __attribute__((__unused__))) { + return true; +} + +static bool cdev_generic_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct file *file = &cdev->file; + + switch(column_id) { + case COL_SOURCE: + if (cdev->devdrv) { + xasprintf(str, "%s:%u", cdev->devdrv, + minor(file->stat.st_rdev)); + return true; + } + /* FALL THROUGH */ + case COL_MAJMIN: + xasprintf(str, "%u:%u", + major(file->stat.st_rdev), + minor(file->stat.st_rdev)); + return true; + default: + return false; + } +} + +static struct cdev_ops cdev_generic_ops = { + .probe = cdev_generic_probe, + .fill_column = cdev_generic_fill_column, +}; + +/* + * misc device driver + */ +static bool cdev_misc_probe(struct cdev *cdev) { + return cdev->devdrv && strcmp(cdev->devdrv, "misc") == 0; +} + +static bool cdev_misc_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct file *file = &cdev->file; + const char *miscdev; + + switch(column_id) { + case COL_MISCDEV: + miscdev = get_miscdev(minor(file->stat.st_rdev)); + if (miscdev) + *str = xstrdup(miscdev); + else + xasprintf(str, "%u", + minor(file->stat.st_rdev)); + return true; + case COL_SOURCE: + miscdev = get_miscdev(minor(file->stat.st_rdev)); + if (miscdev) + xasprintf(str, "misc:%s", miscdev); + else + xasprintf(str, "misc:%u", + minor(file->stat.st_rdev)); + return true; + } + return false; +} + +static struct cdev_ops cdev_misc_ops = { + .parent = &cdev_generic_ops, + .probe = cdev_misc_probe, + .fill_column = cdev_misc_fill_column, +}; + +/* + * tun devcie driver + */ +static bool cdev_tun_probe(struct cdev *cdev) +{ + const char *miscdev; + + if ((!cdev->devdrv) || strcmp(cdev->devdrv, "misc")) + return false; + + miscdev = get_miscdev(minor(cdev->file.stat.st_rdev)); + if (miscdev && strcmp(miscdev, "tun") == 0) + return true; + return false; +} + +static void cdev_tun_free(const struct cdev *cdev) +{ + if (cdev->cdev_data) + free(cdev->cdev_data); +} + +static char * cdev_tun_get_name(struct cdev *cdev) +{ + char *str = NULL; + + if (cdev->cdev_data == NULL) + return NULL; + + xasprintf(&str, "iface=%s", (const char *)cdev->cdev_data); + return str; +} + +static bool cdev_tun_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + switch(column_id) { + case COL_MISCDEV: + *str = xstrdup("tun"); + return true; + case COL_SOURCE: + *str = xstrdup("misc:tun"); + return true; + case COL_TUN_IFACE: + if (cdev->cdev_data) { + *str = xstrdup(cdev->cdev_data); + return true; + } + } + return false; +} + +static int cdev_tun_handle_fdinfo(struct cdev *cdev, const char *key, const char *val) +{ + if (strcmp(key, "iff") == 0 && cdev->cdev_data == NULL) { + cdev->cdev_data = xstrdup(val); + return 1; + } + return false; +} + +static struct cdev_ops cdev_tun_ops = { + .parent = &cdev_misc_ops, + .probe = cdev_tun_probe, + .free = cdev_tun_free, + .get_name = cdev_tun_get_name, + .fill_column = cdev_tun_fill_column, + .handle_fdinfo = cdev_tun_handle_fdinfo, +}; + +/* + * tty devices + */ +struct ttydata { + struct cdev *cdev; + const struct ttydrv *drv; +#define NO_TTY_INDEX -1 + int tty_index; /* used only in ptmx devices */ + struct ipc_endpoint endpoint; +}; + +static bool cdev_tty_probe(struct cdev *cdev) { + const struct ttydrv *ttydrv = get_ttydrv(major(cdev->file.stat.st_rdev), + minor(cdev->file.stat.st_rdev)); + struct ttydata *data; + + if (!ttydrv) + return false; + + data = xmalloc(sizeof(struct ttydata)); + data->cdev = cdev; + data->drv = ttydrv; + data->tty_index = NO_TTY_INDEX; + cdev->cdev_data = data; + + return true; +} + +static void cdev_tty_free(const struct cdev *cdev) +{ + if (cdev->cdev_data) + free(cdev->cdev_data); +} + +static char * cdev_tty_get_name(struct cdev *cdev) +{ + struct ttydata *data = cdev->cdev_data; + char *str = NULL; + + if (!data->drv->is_ptmx) + return NULL; + + if (data->tty_index == NO_TTY_INDEX) + str = xstrdup("tty-index="); + else + xasprintf(&str, "tty-index=%d", data->tty_index); + return str; +} + +static inline char *cdev_tty_xstrendpoint(struct file *file) +{ + char *str = NULL; + xasprintf(&str, "%d,%s,%d%c%c", + file->proc->pid, file->proc->command, file->association, + (file->mode & S_IRUSR)? 'r': '-', + (file->mode & S_IWUSR)? 'w': '-'); + return str; +} + +static bool cdev_tty_fill_column(struct proc *proc __attribute__((__unused__)), + struct cdev *cdev, + struct libscols_line *ln __attribute__((__unused__)), + int column_id, + size_t column_index __attribute__((__unused__)), + char **str) +{ + struct file *file = &cdev->file; + struct ttydata *data = cdev->cdev_data; + + switch(column_id) { + case COL_SOURCE: + if (data->drv->minor_start == data->drv->minor_end) + *str = xstrdup(data->drv->name); + else + xasprintf(str, "%s:%u", data->drv->name, + minor(file->stat.st_rdev)); + return true; + case COL_PTMX_TTY_INDEX: + if (data->drv->is_ptmx) { + xasprintf(str, "%d", data->tty_index); + return true; + } + return false; + case COL_ENDPOINTS: + if (is_pty(data->drv)) { + struct ttydata *this = data; + struct list_head *e; + foreach_endpoint(e, data->endpoint) { + char *estr; + struct ttydata *other = list_entry(e, struct ttydata, endpoint.endpoints); + if (this == other) + continue; + + if ((this->drv->is_ptmx && !other->drv->is_pts) + || (this->drv->is_pts && !other->drv->is_ptmx)) + continue; + + if (*str) + xstrputc(str, '\n'); + estr = cdev_tty_xstrendpoint(&other->cdev->file); + xstrappend(str, estr); + free(estr); + } + if (*str) + return true; + } + return false; + default: + return false; + } +} + +static int cdev_tty_handle_fdinfo(struct cdev *cdev, const char *key, const char *val) +{ + struct ttydata *data = cdev->cdev_data; + + if (!data->drv->is_ptmx) + return 0; + + if (strcmp(key, "tty-index") == 0) { + errno = 0; + data->tty_index = (int)strtol(val, NULL, 10); + if (errno) { + data->tty_index = NO_TTY_INDEX; + return 0; + } + return 1; + } + + return 0; +} + +struct cdev_pty_ipc { + struct ipc ipc; + int tty_index; +}; + +static unsigned int cdev_pty_get_hash(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + struct ttydata *data = cdev->cdev_data; + + return data->drv->is_ptmx? + (unsigned int)data->tty_index: + (unsigned int)minor(file->stat.st_rdev); +} + +static bool cdev_pty_is_suitable_ipc(struct ipc *ipc, struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + struct ttydata *data = cdev->cdev_data; + struct cdev_pty_ipc *cdev_pty_ipc = (struct cdev_pty_ipc *)ipc; + + return (data->drv->is_ptmx)? + cdev_pty_ipc->tty_index == (int)data->tty_index: + cdev_pty_ipc->tty_index == (int)minor(file->stat.st_rdev); +} + +static const struct ipc_class *cdev_tty_get_ipc_class(struct cdev *cdev) +{ + static const struct ipc_class cdev_pty_ipc_class = { + .size = sizeof(struct cdev_pty_ipc), + .get_hash = cdev_pty_get_hash, + .is_suitable_ipc = cdev_pty_is_suitable_ipc, + }; + + struct ttydata *data = cdev->cdev_data; + + if (is_pty(data->drv)) + return &cdev_pty_ipc_class; + + return NULL; +} + +static void cdev_tty_attach_xinfo(struct cdev *cdev) +{ + struct ttydata *data = cdev->cdev_data; + struct ipc *ipc; + unsigned int hash; + + + if (! is_pty(data->drv)) + return; + + init_endpoint(&data->endpoint); + ipc = get_ipc(&cdev->file); + if (ipc) + goto link; + + ipc = new_ipc(cdev_tty_get_ipc_class(cdev)); + hash = cdev_pty_get_hash(&cdev->file); + ((struct cdev_pty_ipc *)ipc)->tty_index = (int)hash; + + add_ipc(ipc, hash); + link: + add_endpoint(&data->endpoint, ipc); +} + +static struct cdev_ops cdev_tty_ops = { + .parent = &cdev_generic_ops, + .probe = cdev_tty_probe, + .free = cdev_tty_free, + .get_name = cdev_tty_get_name, + .fill_column = cdev_tty_fill_column, + .attach_xinfo = cdev_tty_attach_xinfo, + .handle_fdinfo = cdev_tty_handle_fdinfo, + .get_ipc_class = cdev_tty_get_ipc_class, +}; + +static const struct cdev_ops *cdev_ops[] = { + &cdev_tun_ops, + &cdev_misc_ops, + &cdev_tty_ops, + &cdev_generic_ops /* This must be at the end. */ +}; + +static const struct cdev_ops *cdev_probe(struct cdev *cdev) +{ + const struct cdev_ops *r = NULL; + + for (size_t i = 0; i < ARRAY_SIZE(cdev_ops); i++) { + if (cdev_ops[i]->probe(cdev)) { + r = cdev_ops[i]; + break; + } + } + + assert(r); + return r; +} + +static void init_cdev_content(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + + cdev->devdrv = get_chrdrv(major(file->stat.st_rdev)); + + cdev->cdev_data = NULL; + cdev->cdev_ops = cdev_probe(cdev); + if (cdev->cdev_ops->init) + cdev->cdev_ops->init(cdev); +} + +static void free_cdev_content(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + + if (cdev->cdev_ops->free) + cdev->cdev_ops->free(cdev); +} + +static void cdev_attach_xinfo(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + + if (cdev->cdev_ops->attach_xinfo) + cdev->cdev_ops->attach_xinfo(cdev); +} + +static int cdev_handle_fdinfo(struct file *file, const char *key, const char *value) +{ + struct cdev *cdev = (struct cdev *)file; + + if (cdev->cdev_ops->handle_fdinfo) + return cdev->cdev_ops->handle_fdinfo(cdev, key, value); + return 0; /* Should be handled in parents */ +} + +static const struct ipc_class *cdev_get_ipc_class(struct file *file) +{ + struct cdev *cdev = (struct cdev *)file; + + if (cdev->cdev_ops->get_ipc_class) + return cdev->cdev_ops->get_ipc_class(cdev); + return NULL; +} + const struct file_class cdev_class = { .super = &file_class, - .size = sizeof(struct file), + .size = sizeof(struct cdev), .initialize_class = cdev_class_initialize, .finalize_class = cdev_class_finalize, .fill_column = cdev_fill_column, - .free_content = NULL, + .initialize_content = init_cdev_content, + .free_content = free_cdev_content, + .attach_xinfo = cdev_attach_xinfo, + .handle_fdinfo = cdev_handle_fdinfo, + .get_ipc_class = cdev_get_ipc_class, }; |