732 lines
17 KiB
C
732 lines
17 KiB
C
/*
|
|
* lsfd-cdev.c - handle associations opening character devices
|
|
*
|
|
* Copyright (C) 2021 Red Hat, Inc. All rights reserved.
|
|
* Written by Masatake YAMATO <yamato@redhat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it would be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "lsfd.h"
|
|
|
|
static struct list_head miscdevs;
|
|
static struct list_head ttydrvs;
|
|
|
|
struct miscdev {
|
|
struct list_head miscdevs;
|
|
unsigned long minor;
|
|
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,
|
|
struct libscols_line *ln,
|
|
int column_id,
|
|
size_t column_index,
|
|
const char *uri __attribute__((__unused__)))
|
|
{
|
|
struct cdev *cdev = (struct cdev *)file;
|
|
const struct cdev_ops *ops = cdev->cdev_ops;
|
|
char *str = NULL;
|
|
|
|
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_DEVTYPE:
|
|
if (scols_line_set_data(ln, column_index,
|
|
"char"))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
case COL_CHRDRV:
|
|
if (cdev->devdrv)
|
|
str = xstrdup(cdev->devdrv);
|
|
else
|
|
xasprintf(&str, "%u",
|
|
major(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))
|
|
err(EXIT_FAILURE, _("failed to add output data"));
|
|
return true;
|
|
}
|
|
|
|
static struct miscdev *new_miscdev(unsigned long minor, const char *name)
|
|
{
|
|
struct miscdev *miscdev = xcalloc(1, sizeof(*miscdev));
|
|
|
|
INIT_LIST_HEAD(&miscdev->miscdevs);
|
|
|
|
miscdev->minor = minor;
|
|
miscdev->name = xstrdup(name);
|
|
|
|
return miscdev;
|
|
}
|
|
|
|
static void free_miscdev(struct miscdev *miscdev)
|
|
{
|
|
free(miscdev->name);
|
|
free(miscdev);
|
|
}
|
|
|
|
static void read_misc(struct list_head *miscdevs_list, FILE *misc_fp)
|
|
{
|
|
unsigned long minor;
|
|
char line[256];
|
|
char name[sizeof(line)];
|
|
|
|
while (fgets(line, sizeof(line), misc_fp)) {
|
|
struct miscdev *miscdev;
|
|
|
|
if (sscanf(line, "%lu %s", &minor, name) != 2)
|
|
continue;
|
|
|
|
miscdev = new_miscdev(minor, name);
|
|
list_add_tail(&miscdev->miscdevs, miscdevs_list);
|
|
}
|
|
}
|
|
|
|
#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)
|
|
{
|
|
struct list_head *c;
|
|
list_for_each(c, &miscdevs) {
|
|
struct miscdev *miscdev = list_entry(c, struct miscdev, miscdevs);
|
|
if (miscdev->minor == minor)
|
|
return miscdev->name;
|
|
}
|
|
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 device 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 *const 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 cdev),
|
|
.initialize_class = cdev_class_initialize,
|
|
.finalize_class = cdev_class_finalize,
|
|
.fill_column = cdev_fill_column,
|
|
.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,
|
|
};
|