tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

race condition between (specfs) device open and close



hi folks.


while trying to fix any midi/sequencer issues i've seen in the
last year, noticed that any attempt to call 'midiplay' on more
than one midi device at the same time, while not allowed, would
leave the sequencer device always busy.

i tracked this down to an ugly race condition inside the
specfs code.  the problem is that:

	t1		t2
	opens dev
	sd_opencnt++
			tries to open dev
			sd_opencnt++
	closes dev
	since sd_opencnt is != 1, does not call close
	sd_opencnt--
			gets EBUSY
		sd_opencnt--

in this case, the device close routine is never called, and in
at least the midi case, all future opens return EBUSY due to
there already being an active open.

i've spent some time reviewing the specfs code, and i think that
the below patch fixes these specific problems.  it certainly
fixes the problem i have with trying to open /dev/music
concurrently.  it may not be entirely correct or have problems
i've not seen with it, but atf works fine, as does the last
couple of days of random testing.

the main changes are:
	- introduce a "dying" flag to the specnode struture,
	  and use it appropriately
	- add a condvar between open/close and revoke, to
	  ensure any revoke succeeds properly.

i'd like to get this into netbsd 7, so any additional testing
against that branch or against -current would be greatly
appreciated.

thanks.


.mrg.


Index: miscfs/specfs/spec_vnops.c
===================================================================
RCS file: /cvsroot/src/sys/miscfs/specfs/spec_vnops.c,v
retrieving revision 1.145
diff -p -u -r1.145 spec_vnops.c
--- miscfs/specfs/spec_vnops.c	25 Jul 2014 08:20:53 -0000	1.145
+++ miscfs/specfs/spec_vnops.c	30 Dec 2014 08:37:57 -0000
@@ -160,6 +160,12 @@ const struct vnodeopv_entry_desc spec_vn
 const struct vnodeopv_desc spec_vnodeop_opv_desc =
 	{ &spec_vnodeop_p, spec_vnodeop_entries };
 
+/*
+ * This tracks concurrent-open/close with revoke(), and is attached to
+ * device_lock.
+ */
+static kcondvar_t specopenclose_cv;
+
 static kauth_listener_t rawio_listener;
 
 /* Returns true if vnode is /dev/mem or /dev/kmem. */
@@ -206,6 +212,7 @@ spec_init(void)
 
 	rawio_listener = kauth_listen_scope(KAUTH_SCOPE_DEVICE,
 	    rawio_listener_cb, NULL);
+	cv_init(&specopenclose_cv, "specopcl");
 }
 
 /*
@@ -265,6 +272,7 @@ spec_node_init(vnode_t *vp, dev_t rdev)
 	sn->sn_opencnt = 0;
 	sn->sn_rdev = rdev;
 	sn->sn_gone = false;
+	sn->sn_dying = false;
 	vp->v_specnode = sn;
 	vp->v_specnext = *vpp;
 	*vpp = vp;
@@ -397,7 +405,14 @@ spec_node_revoke(vnode_t *vp)
 	KASSERT(sn->sn_gone == false);
 
 	mutex_enter(&device_lock);
-	KASSERT(sn->sn_opencnt <= sd->sd_opencnt);
+	sn->sn_dying = true;
+	while (sn->sn_opencnt > sd->sd_opencnt) {
+		/*
+		 * Open is in progress, but not complete, must wait
+		 * for it to complete.
+		 */
+		cv_wait(&specopenclose_cv, &device_lock);
+	}
 	if (sn->sn_opencnt != 0) {
 		sd->sd_opencnt -= (sn->sn_opencnt - 1);
 		sn->sn_opencnt = 1;
@@ -540,11 +555,10 @@ spec_open(void *v)
 		 * vnodes.
 		 */
 		mutex_enter(&device_lock);
-		if (sn->sn_gone) {
+		if (sn->sn_gone || sn->sn_dying) {
 			mutex_exit(&device_lock);
 			return (EBADF);
 		}
-		sd->sd_opencnt++;
 		sn->sn_opencnt++;
 		mutex_exit(&device_lock);
 		if (cdev_type(dev) == D_TTY)
@@ -573,6 +587,9 @@ spec_open(void *v)
 			(void) module_autoload(name, MODULE_CLASS_DRIVER);
 		} while (gen != module_gen);
 
+		if (error == 0)
+			sd->sd_opencnt++;
+
 		vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
 		break;
 
@@ -587,7 +604,7 @@ spec_open(void *v)
 		 * vnodes holding a block device open.
 		 */
 		mutex_enter(&device_lock);
-		if (sn->sn_gone) {
+		if (sn->sn_gone || sn->sn_dying) {
 			mutex_exit(&device_lock);
 			return (EBADF);
 		}
@@ -596,7 +613,6 @@ spec_open(void *v)
 			return EBUSY;
 		}
 		sn->sn_opencnt = 1;
-		sd->sd_opencnt = 1;
 		sd->sd_bdevvp = vp;
 		mutex_exit(&device_lock);
 		do {
@@ -626,6 +642,9 @@ spec_open(void *v)
 			vn_lock(vp, LK_EXCLUSIVE | LK_RETRY);
 		} while (gen != module_gen);
 
+		if (!error)
+			sd->sd_opencnt = 1;
+
 		break;
 
 	case VNON:
@@ -640,16 +659,17 @@ spec_open(void *v)
 	}
 
 	mutex_enter(&device_lock);
-	if (sn->sn_gone) {
+	if (sn->sn_gone || sn->sn_dying) {
 		if (error == 0)
 			error = EBADF;
 	} else if (error != 0) {
-		sd->sd_opencnt--;
 		sn->sn_opencnt--;
 		if (vp->v_type == VBLK)
 			sd->sd_bdevvp = NULL;
 
 	}
+	if (sn->sn_dying)
+		cv_broadcast(&specopenclose_cv);
 	mutex_exit(&device_lock);
 
 	if (cdev_type(dev) != D_DISK || error != 0)
Index: miscfs/specfs/specdev.h
===================================================================
RCS file: /cvsroot/src/sys/miscfs/specfs/specdev.h,v
retrieving revision 1.43
diff -p -u -r1.43 specdev.h
--- miscfs/specfs/specdev.h	25 Jul 2014 08:19:19 -0000	1.43
+++ miscfs/specfs/specdev.h	30 Dec 2014 08:37:57 -0000
@@ -69,6 +69,7 @@ typedef struct specnode {
 	u_int		sn_opencnt;
 	dev_t		sn_rdev;
 	bool		sn_gone;
+	bool		sn_dying;
 } specnode_t;
 
 typedef struct specdev {


Home | Main Index | Thread Index | Old Index