⬢ DragonFlyBSD Kernel Audit
DF-5071 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/raid/amr/amr_disk.c
+++ b/sys/dev/raid/amr/amr_disk.c
@@ -176,6 +176,15 @@
 	bp->b_error = EINVAL;
 	goto bad;
     }
+
+    /* controller shutting down?  fail fast instead of parking the bio on
+     * amr_bioq, which is never drained once AMR_STATE_SHUTDOWN is set
+     * (amrd_open already refuses new opens in that state). */
+    if (sc->amrd_controller->amr_state & AMR_STATE_SHUTDOWN) {
+	bp->b_error = ENXIO;
+	goto bad;
+    }
+
     bio->bio_driver_info = sc;
 
     devstat_start_transaction(&sc->amrd_stats);
@@ -213,6 +222,31 @@
     biodone(bio);
 }
 
+/*
+ * Fail every bio parked on the controller queue.  Must be called once
+ * AMR_STATE_SHUTDOWN is set: nothing will ever complete them (amr has no
+ * timeout/watchdog), and their issuers would otherwise sleep in biowait
+ * forever.  Caller holds amr_list_lock.
+ */
+void
+amrd_fail_queued_bios(struct amr_softc *sc)
+{
+    struct bio *bio;
+    struct buf *bp;
+
+    for (;;) {
+	bio = bioq_first(&sc->amr_bioq);
+	if (bio == NULL)
+	    break;
+	bioq_remove(&sc->amr_bioq, bio);
+	bp = bio->bio_buf;
+	bp->b_flags |= B_ERROR;
+	bp->b_error = ENXIO;
+	bp->b_resid = bp->b_bcount;
+	biodone(bio);
+    }
+}
+
 static int
 amrd_probe(device_t dev)
 {
--- a/sys/dev/raid/amr/amrvar.h
+++ b/sys/dev/raid/amr/amrvar.h
@@ -299,6 +299,7 @@
  * Interface between driver core and disk driver (should be using a bus?)
  */
 extern int	amr_submit_bio(struct amr_softc *sc, struct bio *bio);
+extern void	amrd_fail_queued_bios(struct amr_softc *sc);
 extern int 	amr_dump_blocks(struct amr_softc *sc, int unit, u_int32_t lba, void *data, int blks);
 extern void	amrd_intr(void *data);
 
--- a/sys/dev/raid/amr/amr.c
+++ b/sys/dev/raid/amr/amr.c
@@ -465,6 +465,11 @@
     /* mark ourselves as in-shutdown */
     sc->amr_state |= AMR_STATE_SHUTDOWN;
 
+    /* fail bios already queued: nothing will complete them now */
+    lockmgr(&sc->amr_list_lock, LK_EXCLUSIVE);
+    amrd_fail_queued_bios(sc);
+    lockmgr(&sc->amr_list_lock, LK_RELEASE);
+
     /* flush controller */
     device_printf(sc->amr_dev, "flushing cache...");
     kprintf("%s\n", amr_flush(sc) ? "failed" : "done");
--- a/sys/dev/raid/amr/amr_pci.c
+++ b/sys/dev/raid/amr/amr_pci.c
@@ -394,6 +394,12 @@
     /* mark ourselves as in-shutdown */
     sc->amr_state |= AMR_STATE_SHUTDOWN;
 
+    /* fail bios already queued: nothing will complete them now
+     * (the "bioq is empty" assumption in the comment above does not hold:
+     * only the control device sets AMR_STATE_OPEN, amrd_open sets nothing) */
+    lockmgr(&sc->amr_list_lock, LK_EXCLUSIVE);
+    amrd_fail_queued_bios(sc);
+    lockmgr(&sc->amr_list_lock, LK_RELEASE);
 
     /* flush controller */
     device_printf(sc->amr_dev, "flushing cache...");