1/* $Id: divert_procfs.c,v 1.1.1.1 2007/08/03 18:52:34 Exp $ 2 * 3 * Filesystem handling for the diversion supplementary services. 4 * 5 * Copyright 1998 by Werner Cornelius (werner@isdn4linux.de) 6 * 7 * This software may be used and distributed according to the terms 8 * of the GNU General Public License, incorporated herein by reference. 9 * 10 */ 11 12#include <linux/module.h> 13#include <linux/poll.h> 14#ifdef CONFIG_PROC_FS 15#include <linux/proc_fs.h> 16#else 17#include <linux/fs.h> 18#endif 19#include <linux/isdnif.h> 20#include "isdn_divert.h" 21 22 23/*********************************/ 24/* Variables for interface queue */ 25/*********************************/ 26ulong if_used = 0; /* number of interface users */ 27static struct divert_info *divert_info_head = NULL; /* head of queue */ 28static struct divert_info *divert_info_tail = NULL; /* pointer to last entry */ 29static DEFINE_SPINLOCK(divert_info_lock);/* lock for queue */ 30static wait_queue_head_t rd_queue; 31 32/*********************************/ 33/* put an info buffer into queue */ 34/*********************************/ 35void 36put_info_buffer(char *cp) 37{ 38 struct divert_info *ib; 39 unsigned long flags; 40 41 if (if_used <= 0) 42 return; 43 if (!cp) 44 return; 45 if (!*cp) 46 return; 47 if (!(ib = kmalloc(sizeof(struct divert_info) + strlen(cp), GFP_ATOMIC))) 48 return; /* no memory */ 49 strcpy(ib->info_start, cp); /* set output string */ 50 ib->next = NULL; 51 spin_lock_irqsave( &divert_info_lock, flags ); 52 ib->usage_cnt = if_used; 53 if (!divert_info_head) 54 divert_info_head = ib; /* new head */ 55 else 56 divert_info_tail->next = ib; /* follows existing messages */ 57 divert_info_tail = ib; /* new tail */ 58 59 /* delete old entrys */ 60 while (divert_info_head->next) { 61 if ((divert_info_head->usage_cnt <= 0) && 62 (divert_info_head->next->usage_cnt <= 0)) { 63 ib = divert_info_head; 64 divert_info_head = divert_info_head->next; 65 kfree(ib); 66 } else 67 break; 68 } /* divert_info_head->next */ 69 spin_unlock_irqrestore( &divert_info_lock, flags ); 70 wake_up_interruptible(&(rd_queue)); 71} /* put_info_buffer */ 72 73/**********************************/ 74/* deflection device read routine */ 75/**********************************/ 76static ssize_t 77isdn_divert_read(struct file *file, char __user *buf, size_t count, loff_t * off) 78{ 79 struct divert_info *inf; 80 int len; 81 82 if (!*((struct divert_info **) file->private_data)) { 83 if (file->f_flags & O_NONBLOCK) 84 return -EAGAIN; 85 interruptible_sleep_on(&(rd_queue)); 86 } 87 if (!(inf = *((struct divert_info **) file->private_data))) 88 return (0); 89 90 inf->usage_cnt--; /* new usage count */ 91 file->private_data = &inf->next; /* next structure */ 92 if ((len = strlen(inf->info_start)) <= count) { 93 if (copy_to_user(buf, inf->info_start, len)) 94 return -EFAULT; 95 *off += len; 96 return (len); 97 } 98 return (0); 99} /* isdn_divert_read */ 100 101/**********************************/ 102/* deflection device write routine */ 103/**********************************/ 104static ssize_t 105isdn_divert_write(struct file *file, const char __user *buf, size_t count, loff_t * off) 106{ 107 return (-ENODEV); 108} /* isdn_divert_write */ 109 110 111/***************************************/ 112/* select routines for various kernels */ 113/***************************************/ 114static unsigned int 115isdn_divert_poll(struct file *file, poll_table * wait) 116{ 117 unsigned int mask = 0; 118 119 poll_wait(file, &(rd_queue), wait); 120 /* mask = POLLOUT | POLLWRNORM; */ 121 if (*((struct divert_info **) file->private_data)) { 122 mask |= POLLIN | POLLRDNORM; 123 } 124 return mask; 125} /* isdn_divert_poll */ 126 127/****************/ 128/* Open routine */ 129/****************/ 130static int 131isdn_divert_open(struct inode *ino, struct file *filep) 132{ 133 unsigned long flags; 134 135 spin_lock_irqsave( &divert_info_lock, flags ); 136 if_used++; 137 if (divert_info_head) 138 filep->private_data = &(divert_info_tail->next); 139 else 140 filep->private_data = &divert_info_head; 141 spin_unlock_irqrestore( &divert_info_lock, flags ); 142 /* start_divert(); */ 143 return nonseekable_open(ino, filep); 144} /* isdn_divert_open */ 145 146/*******************/ 147/* close routine */ 148/*******************/ 149static int 150isdn_divert_close(struct inode *ino, struct file *filep) 151{ 152 struct divert_info *inf; 153 unsigned long flags; 154 155 spin_lock_irqsave( &divert_info_lock, flags ); 156 if_used--; 157 inf = *((struct divert_info **) filep->private_data); 158 while (inf) { 159 inf->usage_cnt--; 160 inf = inf->next; 161 } 162 if (if_used <= 0) 163 while (divert_info_head) { 164 inf = divert_info_head; 165 divert_info_head = divert_info_head->next; 166 kfree(inf); 167 } 168 spin_unlock_irqrestore( &divert_info_lock, flags ); 169 return (0); 170} /* isdn_divert_close */ 171 172/*********/ 173/* IOCTL */ 174/*********/ 175static int 176isdn_divert_ioctl(struct inode *inode, struct file *file, 177 uint cmd, ulong arg) 178{ 179 divert_ioctl dioctl; 180 int i; 181 unsigned long flags; 182 divert_rule *rulep; 183 char *cp; 184 185 if (copy_from_user(&dioctl, (void __user *) arg, sizeof(dioctl))) 186 return -EFAULT; 187 188 switch (cmd) { 189 case IIOCGETVER: 190 dioctl.drv_version = DIVERT_IIOC_VERSION; /* set version */ 191 break; 192 193 case IIOCGETDRV: 194 if ((dioctl.getid.drvid = divert_if.name_to_drv(dioctl.getid.drvnam)) < 0) 195 return (-EINVAL); 196 break; 197 198 case IIOCGETNAM: 199 cp = divert_if.drv_to_name(dioctl.getid.drvid); 200 if (!cp) 201 return (-EINVAL); 202 if (!*cp) 203 return (-EINVAL); 204 strcpy(dioctl.getid.drvnam, cp); 205 break; 206 207 case IIOCGETRULE: 208 if (!(rulep = getruleptr(dioctl.getsetrule.ruleidx))) 209 return (-EINVAL); 210 dioctl.getsetrule.rule = *rulep; /* copy data */ 211 break; 212 213 case IIOCMODRULE: 214 if (!(rulep = getruleptr(dioctl.getsetrule.ruleidx))) 215 return (-EINVAL); 216 spin_lock_irqsave(&divert_lock, flags); 217 *rulep = dioctl.getsetrule.rule; /* copy data */ 218 spin_unlock_irqrestore(&divert_lock, flags); 219 return (0); /* no copy required */ 220 break; 221 222 case IIOCINSRULE: 223 return (insertrule(dioctl.getsetrule.ruleidx, &dioctl.getsetrule.rule)); 224 break; 225 226 case IIOCDELRULE: 227 return (deleterule(dioctl.getsetrule.ruleidx)); 228 break; 229 230 case IIOCDODFACT: 231 return (deflect_extern_action(dioctl.fwd_ctrl.subcmd, 232 dioctl.fwd_ctrl.callid, 233 dioctl.fwd_ctrl.to_nr)); 234 235 case IIOCDOCFACT: 236 case IIOCDOCFDIS: 237 case IIOCDOCFINT: 238 if (!divert_if.drv_to_name(dioctl.cf_ctrl.drvid)) 239 return (-EINVAL); /* invalid driver */ 240 if ((i = cf_command(dioctl.cf_ctrl.drvid, 241 (cmd == IIOCDOCFACT) ? 1 : (cmd == IIOCDOCFDIS) ? 0 : 2, 242 dioctl.cf_ctrl.cfproc, 243 dioctl.cf_ctrl.msn, 244 dioctl.cf_ctrl.service, 245 dioctl.cf_ctrl.fwd_nr, 246 &dioctl.cf_ctrl.procid))) 247 return (i); 248 break; 249 250 default: 251 return (-EINVAL); 252 } /* switch cmd */ 253 return copy_to_user((void __user *)arg, &dioctl, sizeof(dioctl)) ? -EFAULT : 0; 254} /* isdn_divert_ioctl */ 255 256 257#ifdef CONFIG_PROC_FS 258static const struct file_operations isdn_fops = 259{ 260 .owner = THIS_MODULE, 261 .llseek = no_llseek, 262 .read = isdn_divert_read, 263 .write = isdn_divert_write, 264 .poll = isdn_divert_poll, 265 .ioctl = isdn_divert_ioctl, 266 .open = isdn_divert_open, 267 .release = isdn_divert_close, 268}; 269 270/****************************/ 271/* isdn subdir in /proc/net */ 272/****************************/ 273static struct proc_dir_entry *isdn_proc_entry = NULL; 274static struct proc_dir_entry *isdn_divert_entry = NULL; 275#endif /* CONFIG_PROC_FS */ 276 277/***************************************************************************/ 278/* divert_dev_init must be called before the proc filesystem may be used */ 279/***************************************************************************/ 280int 281divert_dev_init(void) 282{ 283 284 init_waitqueue_head(&rd_queue); 285 286#ifdef CONFIG_PROC_FS 287 isdn_proc_entry = proc_mkdir("net/isdn", NULL); 288 if (!isdn_proc_entry) 289 return (-1); 290 isdn_divert_entry = create_proc_entry("divert", S_IFREG | S_IRUGO, isdn_proc_entry); 291 if (!isdn_divert_entry) { 292 remove_proc_entry("net/isdn", NULL); 293 return (-1); 294 } 295 isdn_divert_entry->proc_fops = &isdn_fops; 296 isdn_divert_entry->owner = THIS_MODULE; 297#endif /* CONFIG_PROC_FS */ 298 299 return (0); 300} /* divert_dev_init */ 301 302/***************************************************************************/ 303/* divert_dev_deinit must be called before leaving isdn when included as */ 304/* a module. */ 305/***************************************************************************/ 306int 307divert_dev_deinit(void) 308{ 309 310#ifdef CONFIG_PROC_FS 311 remove_proc_entry("divert", isdn_proc_entry); 312 remove_proc_entry("net/isdn", NULL); 313#endif /* CONFIG_PROC_FS */ 314 315 return (0); 316} /* divert_dev_deinit */ 317