<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">
From: Peter Osterlund &lt;petero2@telia.com&gt;

The handling of the pd-&gt;refcnt variable is racy in a number of places.  For
example, running:

while true ; do usleep 10 ; pktsetup /dev/pktcdvd0 /dev/hdc ; done &amp;
while true ; do pktsetup -d /dev/pktcdvd0 ; done

makes a pktsetup process get stuck in D state after a while.

This patch fixes it by introducing a mutex to protect the refcnt variable
and critical sections in the open/release/setup/tear-down functions.

Signed-off-by: Peter Osterlund &lt;petero2@telia.com&gt;
Signed-off-by: Andrew Morton &lt;akpm@osdl.org&gt;
---

 25-akpm/drivers/block/pktcdvd.c |   88 ++++++++++++++++++++++++----------------
 25-akpm/include/linux/pktcdvd.h |    3 -
 2 files changed, 55 insertions(+), 36 deletions(-)

diff -puN drivers/block/pktcdvd.c~fix-open-close-races-in-pktcdvd drivers/block/pktcdvd.c
--- 25/drivers/block/pktcdvd.c~fix-open-close-races-in-pktcdvd	2004-07-31 22:48:30.019429144 -0700
+++ 25-akpm/drivers/block/pktcdvd.c	2004-07-31 22:48:30.026428080 -0700
@@ -2002,10 +2002,6 @@ static void pkt_release_dev(struct pktcd
 {
 	struct block_device *bdev;
 
-	atomic_dec(&amp;pd-&gt;refcnt);
-	if (atomic_read(&amp;pd-&gt;refcnt) &gt; 0)
-		return;
-
 	bdev = bdget(pd-&gt;pkt_dev);
 	if (bdev) {
 		fsync_bdev(bdev);
@@ -2033,6 +2029,7 @@ static int pkt_open(struct inode *inode,
 	struct pktcdvd_device *pd = NULL;
 	struct block_device *pkt_bdev;
 	int ret;
+	int special_open, exclusive;
 
 	VPRINTK("pktcdvd: entering open\n");
 
@@ -2042,21 +2039,30 @@ static int pkt_open(struct inode *inode,
 		goto out;
 	}
 
+	special_open = 0;
+	if (((file-&gt;f_flags &amp; O_ACCMODE) == O_RDONLY) &amp;&amp; (file-&gt;f_flags &amp; O_CREAT))
+		special_open = 1;
+
+	exclusive = 0;
+	if ((file-&gt;f_mode &amp; FMODE_WRITE) || special_open)
+		exclusive = 1;
+
 	/*
 	 * either device is not configured, or pktsetup is old and doesn't
 	 * use O_CREAT to create device
 	 */
 	pd = &amp;pkt_devs[iminor(inode)];
-	if (!pd-&gt;dev &amp;&amp; !(file-&gt;f_flags &amp; O_CREAT)) {
+	if (!pd-&gt;dev &amp;&amp; !special_open) {
 		VPRINTK("pktcdvd: not configured and O_CREAT not set\n");
 		ret = -ENXIO;
 		goto out;
 	}
 
-	atomic_inc(&amp;pd-&gt;refcnt);
-	if (atomic_read(&amp;pd-&gt;refcnt) &gt; 1) {
-		if (file-&gt;f_mode &amp; FMODE_WRITE) {
-			VPRINTK("pktcdvd: busy open for write\n");
+	down(&amp;pd-&gt;ctl_mutex);
+	pd-&gt;refcnt++;
+	if (pd-&gt;refcnt &gt; 1) {
+		if (exclusive) {
+			VPRINTK("pktcdvd: busy open\n");
 			ret = -EBUSY;
 			goto out_dec;
 		}
@@ -2064,10 +2070,10 @@ static int pkt_open(struct inode *inode,
 		/*
 		 * Not first open, everything is already set up
 		 */
-		return 0;
+		goto done;
 	}
 
-	if (((file-&gt;f_flags &amp; O_ACCMODE) != O_RDONLY) || !(file-&gt;f_flags &amp; O_CREAT)) {
+	if (!special_open) {
 		if (pkt_open_dev(pd, file-&gt;f_mode &amp; FMODE_WRITE)) {
 			ret = -EIO;
 			goto out_dec;
@@ -2083,16 +2089,20 @@ static int pkt_open(struct inode *inode,
 		set_blocksize(pkt_bdev, CD_FRAMESIZE);
 		bdput(pkt_bdev);
 	}
+
+done:
+	up(&amp;pd-&gt;ctl_mutex);
 	return 0;
 
 out_dec:
-	atomic_dec(&amp;pd-&gt;refcnt);
-	if (atomic_read(&amp;pd-&gt;refcnt) == 0) {
+	pd-&gt;refcnt--;
+	if (pd-&gt;refcnt == 0) {
 		if (pd-&gt;bdev) {
 			blkdev_put(pd-&gt;bdev);
 			pd-&gt;bdev = NULL;
 		}
 	}
+	up(&amp;pd-&gt;ctl_mutex);
 out:
 	VPRINTK("pktcdvd: failed open (%d)\n", ret);
 	return ret;
@@ -2103,11 +2113,17 @@ static int pkt_close(struct inode *inode
 	struct pktcdvd_device *pd = &amp;pkt_devs[iminor(inode)];
 	int ret = 0;
 
+	down(&amp;pd-&gt;ctl_mutex);
+	pd-&gt;refcnt--;
+	BUG_ON(pd-&gt;refcnt &lt; 0);
+	if (pd-&gt;refcnt &gt; 0)
+		goto out;
 	if (pd-&gt;dev) {
 		int flush = test_bit(PACKET_WRITABLE, &amp;pd-&gt;flags);
 		pkt_release_dev(pd, flush);
 	}
-
+out:
+	up(&amp;pd-&gt;ctl_mutex);
 	return ret;
 }
 
@@ -2336,7 +2352,7 @@ static int pkt_proc_device(struct pktcdv
 	b += sprintf(b, "\tread:\t\t\t%lukB\n", pd-&gt;stats.secs_r &gt;&gt; 1);
 
 	b += sprintf(b, "\nMisc:\n");
-	b += sprintf(b, "\treference count:\t%d\n", atomic_read(&amp;pd-&gt;refcnt));
+	b += sprintf(b, "\treference count:\t%d\n", pd-&gt;refcnt);
 	b += sprintf(b, "\tflags:\t\t\t0x%lx\n", pd-&gt;flags);
 	b += sprintf(b, "\tread speed:\t\t%ukB/s\n", pd-&gt;read_speed * 150);
 	b += sprintf(b, "\twrite speed:\t\t%ukB/s\n", pd-&gt;write_speed * 150);
@@ -2375,8 +2391,6 @@ static int pkt_read_proc(char *page, cha
 	return len;
 }
 
-extern struct block_device_operations pktcdvd_ops;
-
 static int pkt_new_dev(struct pktcdvd_device *pd, struct block_device *bdev)
 {
 	dev_t dev = bdev-&gt;bd_dev;
@@ -2396,10 +2410,8 @@ static int pkt_new_dev(struct pktcdvd_de
 	/* This is safe, since we have a reference from open(). */
 	__module_get(THIS_MODULE);
 
-	memset(pd, 0, sizeof(struct pktcdvd_device));
+	memset(&amp;pd-&gt;stats, 0, sizeof(struct packet_stats));
 
-	spin_lock_init(&amp;pd-&gt;lock);
-	spin_lock_init(&amp;pd-&gt;iosched.lock);
 	if (!pkt_grow_pktlist(pd, CONFIG_CDROM_PKTCDVD_BUFFERS)) {
 		printk("pktcdvd: not enough memory for buffers\n");
 		ret = -ENOMEM;
@@ -2408,12 +2420,7 @@ static int pkt_new_dev(struct pktcdvd_de
 
 	set_blocksize(bdev, CD_FRAMESIZE);
 	pd-&gt;dev = dev;
-	pd-&gt;bdev = NULL;
-	pd-&gt;pkt_dev = MKDEV(PACKET_MAJOR, minor);
-	sprintf(pd-&gt;name, "pktcdvd%d", minor);
-	atomic_set(&amp;pd-&gt;refcnt, 0);
-	init_waitqueue_head(&amp;pd-&gt;wqueue);
-	init_MUTEX_LOCKED(&amp;pd-&gt;cdrw.thr_sem);
+	BUG_ON(pd-&gt;bdev);
 
 	pkt_init_queue(pd);
 
@@ -2432,7 +2439,6 @@ static int pkt_new_dev(struct pktcdvd_de
 
 out_thread:
 	pkt_shrink_pktlist(pd);
-	memset(pd, 0, sizeof(*pd));
 out_mem:
 	/* This is safe: open() is still holding a reference. */
 	module_put(THIS_MODULE);
@@ -2468,10 +2474,7 @@ static int pkt_setup_dev(struct pktcdvd_
 		printk("pktcdvd: Can't write to read-only dev\n");
 		goto out;
 	}
-	if ((ret = pkt_new_dev(pd, inode-&gt;i_bdev)))
-		goto out;
-
-	atomic_inc(&amp;pd-&gt;refcnt);
+	ret = pkt_new_dev(pd, inode-&gt;i_bdev);
 
 out:
 	fput(file);
@@ -2506,8 +2509,8 @@ static int pkt_remove_dev(struct pktcdvd
 	pkt_shrink_pktlist(pd);
 
 	remove_proc_entry(pd-&gt;name, pkt_proc);
+	pd-&gt;dev = 0;
 	DPRINTK("pktcdvd: writer %d unmapped\n", pkt_get_minor(pd));
-	memset(pd, 0, sizeof(struct pktcdvd_device));
 	/* This is safe: open() is still holding a reference. */
 	module_put(THIS_MODULE);
 	return 0;
@@ -2531,6 +2534,7 @@ static int pkt_media_changed(struct gend
 static int pkt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
 {
 	struct pktcdvd_device *pd = &amp;pkt_devs[iminor(inode)];
+	int ret;
 
 	VPRINTK("pkt_ioctl: cmd %x, dev %d:%d\n", cmd, imajor(inode), iminor(inode));
 
@@ -2557,9 +2561,14 @@ static int pkt_ioctl(struct inode *inode
 	case PACKET_TEARDOWN_DEV:
 		if (!capable(CAP_SYS_ADMIN))
 			return -EPERM;
-		if (atomic_read(&amp;pd-&gt;refcnt) != 1)
-			return -EBUSY;
-		return pkt_remove_dev(pd);
+		down(&amp;pd-&gt;ctl_mutex);
+		BUG_ON(pd-&gt;refcnt &lt; 1);
+		if (pd-&gt;refcnt != 1)
+			ret = -EBUSY;
+		else
+			ret = pkt_remove_dev(pd);
+		up(&amp;pd-&gt;ctl_mutex);
+		return ret;
 
 	case BLKROSET:
 		if (capable(CAP_SYS_ADMIN))
@@ -2633,6 +2642,15 @@ int pkt_init(void)
 	for (i = 0; i &lt; MAX_WRITERS; i++) {
 		struct pktcdvd_device *pd = &amp;pkt_devs[i];
 		struct gendisk *disk = disks[i];
+
+		spin_lock_init(&amp;pd-&gt;lock);
+		spin_lock_init(&amp;pd-&gt;iosched.lock);
+		pd-&gt;pkt_dev = MKDEV(PACKET_MAJOR, i);
+		sprintf(pd-&gt;name, "pktcdvd%d", i);
+		init_waitqueue_head(&amp;pd-&gt;wqueue);
+		init_MUTEX_LOCKED(&amp;pd-&gt;cdrw.thr_sem);
+		init_MUTEX(&amp;pd-&gt;ctl_mutex);
+
 		disk-&gt;major = PACKET_MAJOR;
 		disk-&gt;first_minor = i;
 		disk-&gt;fops = &amp;pktcdvd_ops;
diff -puN include/linux/pktcdvd.h~fix-open-close-races-in-pktcdvd include/linux/pktcdvd.h
--- 25/include/linux/pktcdvd.h~fix-open-close-races-in-pktcdvd	2004-07-31 22:48:30.022428688 -0700
+++ 25-akpm/include/linux/pktcdvd.h	2004-07-31 22:48:30.027427928 -0700
@@ -235,7 +235,8 @@ struct pktcdvd_device
 	char			name[20];
 	struct packet_settings	settings;
 	struct packet_stats	stats;
-	atomic_t		refcnt;
+	struct semaphore	ctl_mutex;	/* Serialize access to refcnt */
+	int			refcnt;		/* Open count */
 	__u8			write_speed;	/* current write speed */
 	__u8			read_speed;	/* current read speed */
 	unsigned long		offset;		/* start offset */
_
</pre></body></html>