vserver 1.9.5.x5
[linux-2.6.git] / drivers / char / tty_io.c
index 9691b8e..5a6191d 100644 (file)
 #include <linux/device.h>
 #include <linux/idr.h>
 #include <linux/wait.h>
+#include <linux/bitops.h>
 
 #include <asm/uaccess.h>
 #include <asm/system.h>
-#include <asm/bitops.h>
 
 #include <linux/kbd_kern.h>
 #include <linux/vt_kern.h>
@@ -137,6 +137,7 @@ extern struct tty_driver *ptm_driver;       /* Unix98 pty masters; for /dev/ptmx */
 extern int pty_limit;          /* Config limit on Unix98 ptys */
 static DEFINE_IDR(allocated_ptys);
 static DECLARE_MUTEX(allocated_ptys_lock);
+static int ptmx_open(struct inode *, struct file *);
 #endif
 
 extern void disable_early_printk(void);
@@ -148,7 +149,6 @@ static ssize_t tty_write(struct file *, const char __user *, size_t, loff_t *);
 ssize_t redirected_tty_write(struct file *, const char __user *, size_t, loff_t *);
 static unsigned int tty_poll(struct file *, poll_table *);
 static int tty_open(struct inode *, struct file *);
-static int ptmx_open(struct inode *, struct file *);
 static int tty_release(struct inode *, struct file *);
 int tty_ioctl(struct inode * inode, struct file * file,
              unsigned int cmd, unsigned long arg);
@@ -169,6 +169,7 @@ static struct tty_struct *alloc_tty_struct(void)
 
 static inline void free_tty_struct(struct tty_struct *tty)
 {
+       kfree(tty->write_buf);
        kfree(tty);
 }
 
@@ -249,7 +250,7 @@ static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
  *     callers who will do ldisc lookups and cannot sleep.
  */
  
-static spinlock_t tty_ldisc_lock = SPIN_LOCK_UNLOCKED;
+static DEFINE_SPINLOCK(tty_ldisc_lock);
 static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait);
 static struct tty_ldisc tty_ldiscs[NR_LDISCS]; /* line disc dispatch table     */
 
@@ -327,7 +328,7 @@ void tty_ldisc_put(int disc)
        
 EXPORT_SYMBOL_GPL(tty_ldisc_put);
 
-void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
+static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
 {
        tty->ldisc = *ld;
        tty->ldisc.refcount = 0;
@@ -583,7 +584,7 @@ restart:
 /*
  * This routine returns a tty driver structure, given a device number
  */
-struct tty_driver *get_tty_driver(dev_t device, int *index)
+static struct tty_driver *get_tty_driver(dev_t device, int *index)
 {
        struct tty_driver *p;
 
@@ -690,7 +691,7 @@ static struct file_operations hung_up_tty_fops = {
        .release        = tty_release,
 };
 
-static spinlock_t redirect_lock = SPIN_LOCK_UNLOCKED;
+static DEFINE_SPINLOCK(redirect_lock);
 static struct file *redirect;
 
 /**
@@ -744,7 +745,7 @@ EXPORT_SYMBOL_GPL(tty_ldisc_flush);
  * but doesn't hold any locks, so we need to make sure we have the appropriate
  * locks for what we're doing..
  */
-void do_tty_hangup(void *data)
+static void do_tty_hangup(void *data)
 {
        struct tty_struct *tty = (struct tty_struct *) data;
        struct file * cons_filp = NULL;
@@ -918,9 +919,11 @@ void disassociate_ctty(int on_exit)
 
        lock_kernel();
 
+       down(&tty_sem);
        tty = current->signal->tty;
        if (tty) {
                tty_pgrp = tty->pgrp;
+               up(&tty_sem);
                if (on_exit && tty->driver->type != TTY_DRIVER_TYPE_PTY)
                        tty_vhangup(tty);
        } else {
@@ -928,6 +931,7 @@ void disassociate_ctty(int on_exit)
                        kill_pg(current->signal->tty_old_pgrp, SIGHUP, on_exit);
                        kill_pg(current->signal->tty_old_pgrp, SIGCONT, on_exit);
                }
+               up(&tty_sem);
                unlock_kernel();        
                return;
        }
@@ -937,15 +941,19 @@ void disassociate_ctty(int on_exit)
                        kill_pg(tty_pgrp, SIGCONT, on_exit);
        }
 
+       /* Must lock changes to tty_old_pgrp */
+       down(&tty_sem);
        current->signal->tty_old_pgrp = 0;
        tty->session = 0;
        tty->pgrp = -1;
 
+       /* Now clear signal->tty under the lock */
        read_lock(&tasklist_lock);
        do_each_task_pid(current->signal->session, PIDTYPE_SID, p) {
                p->signal->tty = NULL;
        } while_each_task_pid(current->signal->session, PIDTYPE_SID, p);
        read_unlock(&tasklist_lock);
+       up(&tty_sem);
        unlock_kernel();
 }
 
@@ -1011,7 +1019,7 @@ static ssize_t tty_read(struct file * file, char __user * buf, size_t count,
        tty_ldisc_deref(ld);
        unlock_kernel();
        if (i > 0)
-               inode->i_atime = CURRENT_TIME;
+               inode->i_atime = current_fs_time(inode->i_sb);
        return i;
 }
 
@@ -1020,44 +1028,81 @@ static ssize_t tty_read(struct file * file, char __user * buf, size_t count,
  * denial-of-service type attacks
  */
 static inline ssize_t do_tty_write(
-       ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char __user *, size_t),
+       ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
        struct tty_struct *tty,
        struct file *file,
-       const unsigned char __user *buf,
+       const char __user *buf,
        size_t count)
 {
        ssize_t ret = 0, written = 0;
+       unsigned int chunk;
        
        if (down_interruptible(&tty->atomic_write)) {
                return -ERESTARTSYS;
        }
-       if ( test_bit(TTY_NO_WRITE_SPLIT, &tty->flags) ) {
+
+       /*
+        * We chunk up writes into a temporary buffer. This
+        * simplifies low-level drivers immensely, since they
+        * don't have locking issues and user mode accesses.
+        *
+        * But if TTY_NO_WRITE_SPLIT is set, we should use a
+        * big chunk-size..
+        *
+        * The default chunk-size is 2kB, because the NTTY
+        * layer has problems with bigger chunks. It will
+        * claim to be able to handle more characters than
+        * it actually does.
+        */
+       chunk = 2048;
+       if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
+               chunk = 65536;
+       if (count < chunk)
+               chunk = count;
+
+       /* write_buf/write_cnt is protected by the atomic_write semaphore */
+       if (tty->write_cnt < chunk) {
+               unsigned char *buf;
+
+               if (chunk < 1024)
+                       chunk = 1024;
+
+               buf = kmalloc(chunk, GFP_KERNEL);
+               if (!buf) {
+                       up(&tty->atomic_write);
+                       return -ENOMEM;
+               }
+               kfree(tty->write_buf);
+               tty->write_cnt = chunk;
+               tty->write_buf = buf;
+       }
+
+       /* Do the write .. */
+       for (;;) {
+               size_t size = count;
+               if (size > chunk)
+                       size = chunk;
+               ret = -EFAULT;
+               if (copy_from_user(tty->write_buf, buf, size))
+                       break;
                lock_kernel();
-               written = write(tty, file, buf, count);
+               ret = write(tty, file, tty->write_buf, size);
                unlock_kernel();
-       } else {
-               for (;;) {
-                       unsigned long size = max((unsigned long)PAGE_SIZE*2, 16384UL);
-                       if (size > count)
-                               size = count;
-                       lock_kernel();
-                       ret = write(tty, file, buf, size);
-                       unlock_kernel();
-                       if (ret <= 0)
-                               break;
-                       written += ret;
-                       buf += ret;
-                       count -= ret;
-                       if (!count)
-                               break;
-                       ret = -ERESTARTSYS;
-                       if (signal_pending(current))
-                               break;
-                       cond_resched();
-               }
+               if (ret <= 0)
+                       break;
+               written += ret;
+               buf += ret;
+               count -= ret;
+               if (!count)
+                       break;
+               ret = -ERESTARTSYS;
+               if (signal_pending(current))
+                       break;
+               cond_resched();
        }
        if (written) {
-               file->f_dentry->d_inode->i_mtime = CURRENT_TIME;
+               struct inode *inode = file->f_dentry->d_inode;
+               inode->i_mtime = current_fs_time(inode->i_sb);
                ret = written;
        }
        up(&tty->atomic_write);
@@ -1083,8 +1128,7 @@ static ssize_t tty_write(struct file * file, const char __user * buf, size_t cou
        if (!ld->write)
                ret = -EIO;
        else
-               ret = do_tty_write(ld->write, tty, file,
-                           (const unsigned char __user *)buf, count);
+               ret = do_tty_write(ld->write, tty, file, buf, count);
        tty_ldisc_deref(ld);
        return ret;
 }
@@ -1141,12 +1185,6 @@ static int init_dev(struct tty_driver *driver, int idx,
        struct termios *ltp, **ltp_loc, *o_ltp, **o_ltp_loc;
        int retval=0;
 
-       /* 
-        * Check whether we need to acquire the tty semaphore to avoid
-        * race conditions.  For now, play it safe.
-        */
-       down(&tty_sem);
-
        /* check whether we're reopening an existing tty */
        if (driver->flags & TTY_DRIVER_DEVPTS_MEM) {
                tty = devpts_get_tty(idx);
@@ -1335,7 +1373,6 @@ success:
        
        /* All paths come through here to release the semaphore */
 end_init:
-       up(&tty_sem);
        return retval;
 
        /* Release locally allocated memory ... nothing placed in slots */
@@ -1531,9 +1568,14 @@ static void release_dev(struct file * filp)
         * each iteration we avoid any problems.
         */
        while (1) {
+               /* Guard against races with tty->count changes elsewhere and
+                  opens on /dev/tty */
+                  
+               down(&tty_sem);
                tty_closing = tty->count <= 1;
                o_tty_closing = o_tty &&
                        (o_tty->count <= (pty_master ? 1 : 0));
+               up(&tty_sem);
                do_sleep = 0;
 
                if (tty_closing) {
@@ -1569,6 +1611,8 @@ static void release_dev(struct file * filp)
         * both sides, and we've completed the last operation that could 
         * block, so it's safe to proceed with closing.
         */
+        
+       down(&tty_sem);
        if (pty_master) {
                if (--o_tty->count < 0) {
                        printk(KERN_WARNING "release_dev: bad pty slave count "
@@ -1582,7 +1626,8 @@ static void release_dev(struct file * filp)
                       tty->count, tty_name(tty, buf));
                tty->count = 0;
        }
-
+       up(&tty_sem);
+       
        /*
         * We've decremented tty->count, so we need to remove this file
         * descriptor off the tty->tty_files list; this serves two
@@ -1729,10 +1774,14 @@ retry_open:
        noctty = filp->f_flags & O_NOCTTY;
        index  = -1;
        retval = 0;
+       
+       down(&tty_sem);
 
        if (device == MKDEV(TTYAUX_MAJOR,0)) {
-               if (!current->signal->tty)
+               if (!current->signal->tty) {
+                       up(&tty_sem);
                        return -ENXIO;
+               }
                driver = current->signal->tty->driver;
                index = current->signal->tty->index;
                filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
@@ -1757,14 +1806,18 @@ retry_open:
                        noctty = 1;
                        goto got_driver;
                }
+               up(&tty_sem);
                return -ENODEV;
        }
 
        driver = get_tty_driver(device, &index);
-       if (!driver)
+       if (!driver) {
+               up(&tty_sem);
                return -ENODEV;
+       }
 got_driver:
        retval = init_dev(driver, index, &tty);
+       up(&tty_sem);
        if (retval)
                return retval;
 
@@ -1850,7 +1903,10 @@ static int ptmx_open(struct inode * inode, struct file * filp)
        }
        up(&allocated_ptys_lock);
 
+       down(&tty_sem);
        retval = init_dev(ptm_driver, index, &tty);
+       up(&tty_sem);
+       
        if (retval)
                goto out;
 
@@ -1982,10 +2038,10 @@ static int tiocswinsz(struct tty_struct *tty, struct tty_struct *real_tty,
 
 static int tioccons(struct file *file)
 {
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
        if (file->f_op->write == redirected_tty_write) {
                struct file *f;
-               if (!capable(CAP_SYS_ADMIN))
-                       return -EPERM;
                spin_lock(&redirect_lock);
                f = redirect;
                redirect = NULL;
@@ -2123,11 +2179,11 @@ static int tiocsetd(struct tty_struct *tty, int __user *p)
 
 static int send_break(struct tty_struct *tty, int duration)
 {
-       set_current_state(TASK_INTERRUPTIBLE);
-
        tty->driver->break_ctl(tty, -1);
-       if (!signal_pending(current))
+       if (!signal_pending(current)) {
+               set_current_state(TASK_INTERRUPTIBLE);
                schedule_timeout(duration);
+       }
        tty->driver->break_ctl(tty, 0);
        if (signal_pending(current))
                return -EINTR;
@@ -2478,28 +2534,6 @@ out:
        tty_ldisc_deref(disc);
 }
 
-/*
- *     Call the ldisc flush directly from a driver. This function may
- *     return an error and need retrying by the user.
- */
-
-int tty_push_data(struct tty_struct *tty, unsigned char *cp, unsigned char *fp, int count)
-{
-       int ret = 0;
-       struct tty_ldisc *disc;
-       
-       disc = tty_ldisc_ref(tty);
-       if(test_bit(TTY_DONT_FLIP, &tty->flags))
-               ret = -EAGAIN;
-       else if(disc == NULL)
-               ret = -EIO;
-       else
-               disc->receive_buf(tty, cp, fp, count);
-       tty_ldisc_deref(disc);
-       return ret;
-       
-}
-
 /*
  * Routine which returns the baud rate of the tty
  *
@@ -2624,7 +2658,7 @@ static void initialize_tty_struct(struct tty_struct *tty)
  */
 static void tty_default_put_char(struct tty_struct *tty, unsigned char ch)
 {
-       tty->driver->write(tty, 0, &ch, 1);
+       tty->driver->write(tty, &ch, 1);
 }
 
 static struct class_simple *tty_class;
@@ -2878,8 +2912,8 @@ void __init console_init(void)
           So I haven't moved it. dwmw2 */
         rs_360_init();
 #endif
-       call = &__con_initcall_start;
-       while (call < &__con_initcall_end) {
+       call = __con_initcall_start;
+       while (call < __con_initcall_end) {
                (*call)();
                call++;
        }