⬢ DragonFlyBSD Kernel Audit
DF-5026 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/dev/raid/ips/ips_disk.c b/sys/dev/raid/ips/ips_disk.c
--- a/sys/dev/raid/ips/ips_disk.c
+++ b/sys/dev/raid/ips/ips_disk.c
@@ -129,10 +129,28 @@
 {
 	cdev_t dev = ap->a_head.a_dev;
 	struct bio *bio = ap->a_bio;
+	struct buf *bp = bio->bio_buf;
 	ipsdisk_softc_t *dsc;
 
 	dsc = dev->si_drv1;
 	DEVICE_PRINTF(8, dsc->dev, "in strategy\n");
+	/*
+	 * Fail fast when the adapter has been declared dead: once
+	 * IPS_OFFLINE is set (ips_timeout() / ips_cmdqueue_free()),
+	 * ips_get_free_cmd() returns EIO and ips_start_io_request()
+	 * silently drops the bio on the floor -- no biodone() ever
+	 * happens, wedging every issuer in an unkillable D-state and
+	 * growing sc->bio_queue without bound.  (This check is advisory
+	 * and racy by design; the authoritative drain lives in
+	 * ips_start_io_request(), see the companion ips_commands.c fix.)
+	 */
+	if (dsc->sc->state & IPS_OFFLINE) {
+		bp->b_flags |= B_ERROR;
+		bp->b_error = EIO;
+		bp->b_resid = bp->b_bcount;
+		biodone(bio);
+		return (0);
+	}
 	bio->bio_driver_info = dsc;
 	devstat_start_transaction(&dsc->stats);
 	lockmgr(&dsc->sc->queue_lock, LK_EXCLUSIVE|LK_RETRY);
diff --git a/sys/dev/raid/ips/ips_commands.c b/sys/dev/raid/ips/ips_commands.c
--- a/sys/dev/raid/ips/ips_commands.c
+++ b/sys/dev/raid/ips/ips_commands.c
@@ -239,12 +239,38 @@
 {
 	ips_command_t *command;
 	struct bio *bio;
+	int error;
 
 	bio = bioq_first(&sc->bio_queue);
 	if (bio == NULL)
 		return;
-	if (ips_get_free_cmd(sc, &command, 0) != 0)
+	error = ips_get_free_cmd(sc, &command, 0);
+	if (error == EBUSY)
+		/*
+		 * Transient (all command slots busy, or a timeout-driven
+		 * reset in progress): the bio stays queued and is retried
+		 * on the next completion.  This behavior is correct.
+		 */
 		return;
+	if (error != 0) {
+		/*
+		 * Terminal: the adapter is IPS_OFFLINE and will never
+		 * complete anything again.  Fail every queued bio now --
+		 * previously they were parked in bio_queue forever with
+		 * no biodone(), wedging the issuers permanently and
+		 * growing the queue without bound.
+		 */
+		while ((bio = bioq_first(&sc->bio_queue)) != NULL) {
+			struct buf *bp;
+			bioq_remove(&sc->bio_queue, bio);
+			bp = bio->bio_buf;
+			bp->b_flags |= B_ERROR;
+			bp->b_error = error;
+			bp->b_resid = bp->b_bcount;
+			biodone(bio);
+		}
+		return;
+	}
 	bioq_remove(&sc->bio_queue, bio);
 	if (bio->bio_buf->b_cmd == BUF_CMD_FLUSH)
 		ips_send_flush_request(command, bio);