summaryrefslogtreecommitdiffstats
path: root/misc-utils/lsfd-cdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'misc-utils/lsfd-cdev.c')
-rw-r--r--misc-utils/lsfd-cdev.c641
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,
};