linux/drivers/isdn/divert/divert_procfs.c
Arjan van de Ven 2b8693c061 [PATCH] mark struct file_operations const 3
Many struct file_operations in the kernel can be "const".  Marking them const
moves these to the .rodata section, which avoids false sharing with potential
dirty data.  In addition it'll catch accidental writes at compile time to
these shared resources.

Signed-off-by: Arjan van de Ven <arjan@linux.intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2007-02-12 09:48:45 -08:00

318 lines
8.4 KiB
C

/* $Id: divert_procfs.c,v 1.11.6.2 2001/09/23 22:24:36 kai Exp $
*
* Filesystem handling for the diversion supplementary services.
*
* Copyright 1998 by Werner Cornelius (werner@isdn4linux.de)
*
* This software may be used and distributed according to the terms
* of the GNU General Public License, incorporated herein by reference.
*
*/
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/smp_lock.h>
#ifdef CONFIG_PROC_FS
#include <linux/proc_fs.h>
#else
#include <linux/fs.h>
#endif
#include <linux/isdnif.h>
#include "isdn_divert.h"
/*********************************/
/* Variables for interface queue */
/*********************************/
ulong if_used = 0; /* number of interface users */
static struct divert_info *divert_info_head = NULL; /* head of queue */
static struct divert_info *divert_info_tail = NULL; /* pointer to last entry */
static DEFINE_SPINLOCK(divert_info_lock);/* lock for queue */
static wait_queue_head_t rd_queue;
/*********************************/
/* put an info buffer into queue */
/*********************************/
void
put_info_buffer(char *cp)
{
struct divert_info *ib;
unsigned long flags;
if (if_used <= 0)
return;
if (!cp)
return;
if (!*cp)
return;
if (!(ib = kmalloc(sizeof(struct divert_info) + strlen(cp), GFP_ATOMIC)))
return; /* no memory */
strcpy(ib->info_start, cp); /* set output string */
ib->next = NULL;
spin_lock_irqsave( &divert_info_lock, flags );
ib->usage_cnt = if_used;
if (!divert_info_head)
divert_info_head = ib; /* new head */
else
divert_info_tail->next = ib; /* follows existing messages */
divert_info_tail = ib; /* new tail */
/* delete old entrys */
while (divert_info_head->next) {
if ((divert_info_head->usage_cnt <= 0) &&
(divert_info_head->next->usage_cnt <= 0)) {
ib = divert_info_head;
divert_info_head = divert_info_head->next;
kfree(ib);
} else
break;
} /* divert_info_head->next */
spin_unlock_irqrestore( &divert_info_lock, flags );
wake_up_interruptible(&(rd_queue));
} /* put_info_buffer */
/**********************************/
/* deflection device read routine */
/**********************************/
static ssize_t
isdn_divert_read(struct file *file, char __user *buf, size_t count, loff_t * off)
{
struct divert_info *inf;
int len;
if (!*((struct divert_info **) file->private_data)) {
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
interruptible_sleep_on(&(rd_queue));
}
if (!(inf = *((struct divert_info **) file->private_data)))
return (0);
inf->usage_cnt--; /* new usage count */
file->private_data = &inf->next; /* next structure */
if ((len = strlen(inf->info_start)) <= count) {
if (copy_to_user(buf, inf->info_start, len))
return -EFAULT;
*off += len;
return (len);
}
return (0);
} /* isdn_divert_read */
/**********************************/
/* deflection device write routine */
/**********************************/
static ssize_t
isdn_divert_write(struct file *file, const char __user *buf, size_t count, loff_t * off)
{
return (-ENODEV);
} /* isdn_divert_write */
/***************************************/
/* select routines for various kernels */
/***************************************/
static unsigned int
isdn_divert_poll(struct file *file, poll_table * wait)
{
unsigned int mask = 0;
poll_wait(file, &(rd_queue), wait);
/* mask = POLLOUT | POLLWRNORM; */
if (*((struct divert_info **) file->private_data)) {
mask |= POLLIN | POLLRDNORM;
}
return mask;
} /* isdn_divert_poll */
/****************/
/* Open routine */
/****************/
static int
isdn_divert_open(struct inode *ino, struct file *filep)
{
unsigned long flags;
spin_lock_irqsave( &divert_info_lock, flags );
if_used++;
if (divert_info_head)
filep->private_data = &(divert_info_tail->next);
else
filep->private_data = &divert_info_head;
spin_unlock_irqrestore( &divert_info_lock, flags );
/* start_divert(); */
return nonseekable_open(ino, filep);
} /* isdn_divert_open */
/*******************/
/* close routine */
/*******************/
static int
isdn_divert_close(struct inode *ino, struct file *filep)
{
struct divert_info *inf;
unsigned long flags;
spin_lock_irqsave( &divert_info_lock, flags );
if_used--;
inf = *((struct divert_info **) filep->private_data);
while (inf) {
inf->usage_cnt--;
inf = inf->next;
}
if (if_used <= 0)
while (divert_info_head) {
inf = divert_info_head;
divert_info_head = divert_info_head->next;
kfree(inf);
}
spin_unlock_irqrestore( &divert_info_lock, flags );
return (0);
} /* isdn_divert_close */
/*********/
/* IOCTL */
/*********/
static int
isdn_divert_ioctl(struct inode *inode, struct file *file,
uint cmd, ulong arg)
{
divert_ioctl dioctl;
int i;
unsigned long flags;
divert_rule *rulep;
char *cp;
if (copy_from_user(&dioctl, (void __user *) arg, sizeof(dioctl)))
return -EFAULT;
switch (cmd) {
case IIOCGETVER:
dioctl.drv_version = DIVERT_IIOC_VERSION; /* set version */
break;
case IIOCGETDRV:
if ((dioctl.getid.drvid = divert_if.name_to_drv(dioctl.getid.drvnam)) < 0)
return (-EINVAL);
break;
case IIOCGETNAM:
cp = divert_if.drv_to_name(dioctl.getid.drvid);
if (!cp)
return (-EINVAL);
if (!*cp)
return (-EINVAL);
strcpy(dioctl.getid.drvnam, cp);
break;
case IIOCGETRULE:
if (!(rulep = getruleptr(dioctl.getsetrule.ruleidx)))
return (-EINVAL);
dioctl.getsetrule.rule = *rulep; /* copy data */
break;
case IIOCMODRULE:
if (!(rulep = getruleptr(dioctl.getsetrule.ruleidx)))
return (-EINVAL);
spin_lock_irqsave(&divert_lock, flags);
*rulep = dioctl.getsetrule.rule; /* copy data */
spin_unlock_irqrestore(&divert_lock, flags);
return (0); /* no copy required */
break;
case IIOCINSRULE:
return (insertrule(dioctl.getsetrule.ruleidx, &dioctl.getsetrule.rule));
break;
case IIOCDELRULE:
return (deleterule(dioctl.getsetrule.ruleidx));
break;
case IIOCDODFACT:
return (deflect_extern_action(dioctl.fwd_ctrl.subcmd,
dioctl.fwd_ctrl.callid,
dioctl.fwd_ctrl.to_nr));
case IIOCDOCFACT:
case IIOCDOCFDIS:
case IIOCDOCFINT:
if (!divert_if.drv_to_name(dioctl.cf_ctrl.drvid))
return (-EINVAL); /* invalid driver */
if ((i = cf_command(dioctl.cf_ctrl.drvid,
(cmd == IIOCDOCFACT) ? 1 : (cmd == IIOCDOCFDIS) ? 0 : 2,
dioctl.cf_ctrl.cfproc,
dioctl.cf_ctrl.msn,
dioctl.cf_ctrl.service,
dioctl.cf_ctrl.fwd_nr,
&dioctl.cf_ctrl.procid)))
return (i);
break;
default:
return (-EINVAL);
} /* switch cmd */
return copy_to_user((void __user *)arg, &dioctl, sizeof(dioctl)) ? -EFAULT : 0;
} /* isdn_divert_ioctl */
#ifdef CONFIG_PROC_FS
static const struct file_operations isdn_fops =
{
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = isdn_divert_read,
.write = isdn_divert_write,
.poll = isdn_divert_poll,
.ioctl = isdn_divert_ioctl,
.open = isdn_divert_open,
.release = isdn_divert_close,
};
/****************************/
/* isdn subdir in /proc/net */
/****************************/
static struct proc_dir_entry *isdn_proc_entry = NULL;
static struct proc_dir_entry *isdn_divert_entry = NULL;
#endif /* CONFIG_PROC_FS */
/***************************************************************************/
/* divert_dev_init must be called before the proc filesystem may be used */
/***************************************************************************/
int
divert_dev_init(void)
{
init_waitqueue_head(&rd_queue);
#ifdef CONFIG_PROC_FS
isdn_proc_entry = proc_mkdir("net/isdn", NULL);
if (!isdn_proc_entry)
return (-1);
isdn_divert_entry = create_proc_entry("divert", S_IFREG | S_IRUGO, isdn_proc_entry);
if (!isdn_divert_entry) {
remove_proc_entry("net/isdn", NULL);
return (-1);
}
isdn_divert_entry->proc_fops = &isdn_fops;
isdn_divert_entry->owner = THIS_MODULE;
#endif /* CONFIG_PROC_FS */
return (0);
} /* divert_dev_init */
/***************************************************************************/
/* divert_dev_deinit must be called before leaving isdn when included as */
/* a module. */
/***************************************************************************/
int
divert_dev_deinit(void)
{
#ifdef CONFIG_PROC_FS
remove_proc_entry("divert", isdn_proc_entry);
remove_proc_entry("net/isdn", NULL);
#endif /* CONFIG_PROC_FS */
return (0);
} /* divert_dev_deinit */