Sophie

Sophie

distrib > Scientific%20Linux > 5x > x86_64 > by-pkgid > 27922b4260f65d317aabda37e42bbbff > files > 425

kernel-2.6.18-238.el5.src.rpm

From: Mauro Carvalho Chehab <mchehab@redhat.com>
Date: Sun, 21 Jun 2009 16:02:54 -0300
Subject: [char] tty: prevent an O_NDELAY writer from blocking
Message-id: 20090621160254.493c568e@pedra.chehab.org
O-Subject: [PATCH RHEL5.4 and 5.3.z BZ:506806] Prevent an O_NDELAY writer from blocking when a tty write is blocked
Bugzilla: 506806
RH-Acked-by: Aristeu Rozanski <aris@redhat.com>

Backport upstream patch that prevents an O_NDELAY writer from blocking when a
tty write is blocked by the tty atomic writer mutex.

Upstream changeset: 9c1729db3e6d738f872bcb090212af00473bf666

The write operation with O_NONBLOCK flag to TTY is blocked under some conditions:

  - The targeted TTY terminal is opened by 2 or more users.
     For example , one application opened it as /dev/console,
     the other did it as /dev/ttyS0.
  - One of the users opened the targeted TTY without O_NONBLOCK flag.
  - the write operation without O_NONBLOCK is suspended
    because the output buffer is full or ctrl+S char is pushed.
  - the write operation with O_NONBLOCK flag is issued after that.

This happens because the mutex using tty->atomic_write_lock is held
at the top of do_tty_write(tty_io.c).

Once the proces is suspended, all the write operation after that is suspended
because it tries to get mutex even if it has O_NONBLOCK flag.

Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>

diff --git a/drivers/char/n_hdlc.c b/drivers/char/n_hdlc.c
index 337a87f..37f7d34 100644
--- a/drivers/char/n_hdlc.c
+++ b/drivers/char/n_hdlc.c
@@ -780,13 +780,14 @@ static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,
 		poll_wait(filp, &tty->write_wait, wait);
 
 		/* set bits for operations that won't block */
-		if(n_hdlc->rx_buf_list.head)
+		if (n_hdlc->rx_buf_list.head)
 			mask |= POLLIN | POLLRDNORM;	/* readable */
 		if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
 			mask |= POLLHUP;
-		if(tty_hung_up_p(filp))
+		if (tty_hung_up_p(filp))
 			mask |= POLLHUP;
-		if(n_hdlc->tx_free_buf_list.head)
+		if (!tty_is_writelocked(tty) &&
+				n_hdlc->tx_free_buf_list.head)
 			mask |= POLLOUT | POLLWRNORM;	/* writable */
 	}
 	return mask;
@@ -861,7 +862,7 @@ static void n_hdlc_buf_put(struct n_hdlc_buf_list *list,
 	spin_lock_irqsave(&list->spinlock,flags);
 	
 	buf->link=NULL;
-	if(list->tail)
+	if (list->tail)
 		list->tail->link = buf;
 	else
 		list->head = buf;
diff --git a/drivers/char/n_tty.c b/drivers/char/n_tty.c
index 4f9030e..78fa479 100644
--- a/drivers/char/n_tty.c
+++ b/drivers/char/n_tty.c
@@ -1551,7 +1551,8 @@ static unsigned int normal_poll(struct tty_struct * tty, struct file * file, pol
 		else
 			tty->minimum_to_wake = 1;
 	}
-	if (tty->driver->chars_in_buffer(tty) < WAKEUP_CHARS &&
+	if (!tty_is_writelocked(tty) &&
+			tty->driver->chars_in_buffer(tty) < WAKEUP_CHARS &&
 			tty->driver->write_room(tty) > 0)
 		mask |= POLLOUT | POLLWRNORM;
 	return mask;
diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c
index f2e58b9..a4ee730 100644
--- a/drivers/char/tty_io.c
+++ b/drivers/char/tty_io.c
@@ -1651,6 +1651,23 @@ static ssize_t tty_read(struct file * file, char __user * buf, size_t count,
 	return i;
 }
 
+void tty_write_unlock(struct tty_struct *tty)
+{
+	mutex_unlock(&tty->atomic_write_lock);
+	wake_up_interruptible(&tty->write_wait);
+}
+
+int tty_write_lock(struct tty_struct *tty, int ndelay)
+{
+	if (!mutex_trylock(&tty->atomic_write_lock)) {
+		if (ndelay)
+			return -EAGAIN;
+		if (mutex_lock_interruptible(&tty->atomic_write_lock))
+			return -ERESTARTSYS;
+	}
+	return 0;
+}
+
 /*
  * Split writes up in sane blocksizes to avoid
  * denial-of-service type attacks
@@ -1662,13 +1679,12 @@ static inline ssize_t do_tty_write(
 	const char __user *buf,
 	size_t count)
 {
-	ssize_t ret = 0, written = 0;
+	ssize_t ret, written = 0;
 	unsigned int chunk;
 	
-	/* FIXME: O_NDELAY ... */
-	if (mutex_lock_interruptible(&tty->atomic_write_lock)) {
-		return -ERESTARTSYS;
-	}
+	ret = tty_write_lock(tty, file->f_flags & O_NDELAY);
+	if (ret < 0)
+		return ret;
 
 	/*
 	 * We chunk up writes into a temporary buffer. This
@@ -1701,8 +1717,8 @@ static inline ssize_t do_tty_write(
 
 		buf = kmalloc(chunk, GFP_KERNEL);
 		if (!buf) {
-			mutex_unlock(&tty->atomic_write_lock);
-			return -ENOMEM;
+			ret = -ENOMEM;
+			goto out;
 		}
 		kfree(tty->write_buf);
 		tty->write_cnt = chunk;
@@ -1737,7 +1753,8 @@ static inline ssize_t do_tty_write(
 		inode->i_mtime = current_fs_time(inode->i_sb);
 		ret = written;
 	}
-	mutex_unlock(&tty->atomic_write_lock);
+out:
+	tty_write_unlock(tty);
 	return ret;
 }
 
@@ -3095,14 +3112,13 @@ static int tiocsetd(struct tty_struct *tty, int __user *p)
 
 static int send_break(struct tty_struct *tty, unsigned int duration)
 {
-	if (mutex_lock_interruptible(&tty->atomic_write_lock))
+	if (tty_write_lock(tty, 0) < 0)
 		return -EINTR;
 	tty->driver->break_ctl(tty, -1);
-	if (!signal_pending(current)) {
+	if (!signal_pending(current))
 		msleep_interruptible(duration);
-	}
 	tty->driver->break_ctl(tty, 0);
-	mutex_unlock(&tty->atomic_write_lock);
+	tty_write_unlock(tty);
 	if (signal_pending(current))
 		return -EINTR;
 	return 0;
diff --git a/drivers/char/tty_ioctl.c b/drivers/char/tty_ioctl.c
index cb882ed..af285cd 100644
--- a/drivers/char/tty_ioctl.c
+++ b/drivers/char/tty_ioctl.c
@@ -491,7 +491,7 @@ static int send_prio_char(struct tty_struct *tty, char ch)
 		return 0;
 	}
 
-	if (mutex_lock_interruptible(&tty->atomic_write_lock))
+	if (tty_write_lock(tty, 0) < 0)
 		return -ERESTARTSYS;
 
 	if (was_stopped)
@@ -499,7 +499,7 @@ static int send_prio_char(struct tty_struct *tty, char ch)
 	tty->driver->write(tty, &ch, 1);
 	if (was_stopped)
 		stop_tty(tty);
-	mutex_unlock(&tty->atomic_write_lock);
+	tty_write_unlock(tty);
 	return 0;
 }
 
diff --git a/include/linux/tty.h b/include/linux/tty.h
index eaf1f44..8f97f23 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -310,6 +310,12 @@ extern void tty_ldisc_flush(struct tty_struct *tty);
 
 extern struct mutex tty_mutex;
 
+extern void tty_write_unlock(struct tty_struct *tty);
+extern int tty_write_lock(struct tty_struct *tty, int ndelay);
+#define tty_is_writelocked(tty)  (mutex_is_locked(&tty->atomic_write_lock))
+
+
+
 /* n_tty.c */
 extern struct tty_ldisc tty_ldisc_N_TTY;