Module Name:    src
Committed By:   riastradh
Date:           Mon Mar 28 12:34:59 UTC 2022

Modified Files:
        src/sys/miscfs/specfs: spec_vnops.c

Log Message:
specfs: Split spec_open switch into three sections.

The sections are now:

1. Acquire open reference.

1a (intermezzo). Set VV_ISTTY.

2. Drop the vnode lock to call .d_open and autoload modules if
   necessary.

3. Handle concurrent revoke if it happenend, or release open reference
   if .d_open failed.

No functional change.  Sprinkle comments about problems.


To generate a diff of this commit:
cvs rdiff -u -r1.189 -r1.190 src/sys/miscfs/specfs/spec_vnops.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/miscfs/specfs/spec_vnops.c
diff -u src/sys/miscfs/specfs/spec_vnops.c:1.189 src/sys/miscfs/specfs/spec_vnops.c:1.190
--- src/sys/miscfs/specfs/spec_vnops.c:1.189	Mon Mar 28 12:34:51 2022
+++ src/sys/miscfs/specfs/spec_vnops.c	Mon Mar 28 12:34:59 2022
@@ -1,4 +1,4 @@
-/*	$NetBSD: spec_vnops.c,v 1.189 2022/03/28 12:34:51 riastradh Exp $	*/
+/*	$NetBSD: spec_vnops.c,v 1.190 2022/03/28 12:34:59 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2008 The NetBSD Foundation, Inc.
@@ -58,7 +58,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: spec_vnops.c,v 1.189 2022/03/28 12:34:51 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: spec_vnops.c,v 1.190 2022/03/28 12:34:59 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/proc.h>
@@ -550,6 +550,20 @@ spec_open(void *v)
 	if (error != 0)
 		return (error);
 
+	/*
+	 * Acquire an open reference -- as long as we hold onto it, and
+	 * the vnode isn't revoked, it can't be closed.
+	 *
+	 * But first check whether it has been revoked -- if so, we
+	 * can't acquire more open references and we must fail
+	 * immediately with EBADF.
+	 *
+	 * XXX This races with revoke: once we release the vnode lock,
+	 * the vnode may be revoked, and the .d_close callback run, at
+	 * the same time as we're calling .d_open here.  Drivers
+	 * shouldn't have to contemplate this scenario; .d_open and
+	 * .d_close should be prevented from running concurrently.
+	 */
 	switch (vp->v_type) {
 	case VCHR:
 		/*
@@ -564,8 +578,68 @@ spec_open(void *v)
 		sd->sd_opencnt++;
 		sn->sn_opencnt++;
 		mutex_exit(&device_lock);
+		break;
+	case VBLK:
+		/*
+		 * For block devices, permit only one open.  The buffer
+		 * cache cannot remain self-consistent with multiple
+		 * vnodes holding a block device open.
+		 *
+		 * Treat zero opencnt with non-NULL mountpoint as open.
+		 * This may happen after forced detach of a mounted device.
+		 */
+		mutex_enter(&device_lock);
+		if (sn->sn_gone) {
+			mutex_exit(&device_lock);
+			return (EBADF);
+		}
+		if (sd->sd_opencnt != 0 || sd->sd_mountpoint != NULL) {
+			mutex_exit(&device_lock);
+			return EBUSY;
+		}
+		sn->sn_opencnt = 1;
+		sd->sd_opencnt = 1;
+		sd->sd_bdevvp = vp;
+		mutex_exit(&device_lock);
+		break;
+	default:
+		panic("invalid specfs vnode type: %d", vp->v_type);
+	}
+
+	/*
+	 * Set VV_ISTTY if this is a tty cdev.
+	 *
+	 * XXX This does the wrong thing if the module has to be
+	 * autoloaded.  We should maybe set this after autoloading
+	 * modules and calling .d_open successfully, except (a) we need
+	 * the vnode lock to touch it, and (b) once we acquire the
+	 * vnode lock again, the vnode may have been revoked, and
+	 * deadfs's dead_read needs VV_ISTTY to be already set in order
+	 * to return the right answer.  So this needs some additional
+	 * synchronization to be made to work correctly with tty driver
+	 * module autoload.  For now, let's just hope it doesn't cause
+	 * too much trouble for a tty from an autoloaded driver module
+	 * to fail with EIO instead of returning EOF.
+	 */
+	if (vp->v_type == VCHR) {
 		if (cdev_type(dev) == D_TTY)
 			vp->v_vflag |= VV_ISTTY;
+	}
+
+	/*
+	 * Open the device.  If .d_open returns ENXIO (device not
+	 * configured), the driver may not be loaded, so try
+	 * autoloading a module and then try .d_open again if anything
+	 * got loaded.
+	 *
+	 * Because opening the device may block indefinitely, e.g. when
+	 * opening a tty, and loading a module may cross into many
+	 * other subsystems, we must not hold the vnode lock while
+	 * calling .d_open, so release it now and reacquire it when
+	 * done.
+	 */
+	switch (vp->v_type) {
+	case VCHR:
 		VOP_UNLOCK(vp);
 		do {
 			const struct cdevsw *cdev;
@@ -594,27 +668,6 @@ spec_open(void *v)
 		break;
 
 	case VBLK:
-		/*
-		 * For block devices, permit only one open.  The buffer
-		 * cache cannot remain self-consistent with multiple
-		 * vnodes holding a block device open.
-		 *
-		 * Treat zero opencnt with non-NULL mountpoint as open.
-		 * This may happen after forced detach of a mounted device.
-		 */
-		mutex_enter(&device_lock);
-		if (sn->sn_gone) {
-			mutex_exit(&device_lock);
-			return (EBADF);
-		}
-		if (sd->sd_opencnt != 0 || sd->sd_mountpoint != NULL) {
-			mutex_exit(&device_lock);
-			return EBUSY;
-		}
-		sn->sn_opencnt = 1;
-		sd->sd_opencnt = 1;
-		sd->sd_bdevvp = vp;
-		mutex_exit(&device_lock);
 		VOP_UNLOCK(vp);
 		do {
 			const struct bdevsw *bdev;
@@ -643,9 +696,39 @@ spec_open(void *v)
 		break;
 
 	default:
-		panic("invalid specfs vnode type: %d", vp->v_type);
+		__unreachable();
 	}
 
+	/*
+	 * If it has been revoked since we released the vnode lock and
+	 * reacquired it, then spec_node_revoke has closed it, and we
+	 * must fail with EBADF.
+	 *
+	 * Otherwise, if opening it failed, back out and release the
+	 * open reference.
+	 *
+	 * XXX This is wrong -- we might release the last open
+	 * reference here, but we don't close the device.  If only this
+	 * thread's call to open failed, that's fine, but we might
+	 * have:
+	 *
+	 *	Thread 1		Thread 2
+	 *	VOP_OPEN
+	 *	  ...
+	 *	  .d_open -> 0 (success)
+	 *	  acquire vnode lock
+	 *	  do stuff		VOP_OPEN
+	 *	  release vnode lock	...
+	 *				  .d_open -> EBUSY
+	 *	VOP_CLOSE
+	 *	  acquire vnode lock
+	 *	  --sd_opencnt != 0
+	 *	  => no .d_close
+	 *	  release vnode lock
+	 *				  acquire vnode lock
+	 *				  --sd_opencnt == 0
+	 *				  but no .d_close (***)
+	 */
 	mutex_enter(&device_lock);
 	if (sn->sn_gone) {
 		if (error == 0)

Reply via email to