[PATCH] SCSI core: fix leakage of scsi_cmnd's

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

 



[This message is completely separate from the concurrent discussion of 
earlier patches, as542-as546; this patch fixes a different problem.]

James:

The SCSI core has a problem with leakage of scsi_cmnd structs.  It occurs
when a request is requeued; the request keeps its associated scsi_cmnd so
that the core doesn't need to assign a new one.  However, the routines
that read the device queue sometimes delete entries without bothering to
free the associated scsi_cmnd.  Among other things, this bug manifests as
error-handler processes remaining alive when a hot-pluggable device is
unplugged in the middle of executing a command.

This patch (as559) fixes the problem by calling scsi_put_command and 
scsi_release_buffers at the appropriate times.  It also reorganizes a 
couple of bits of code, adding a new subroutine to find the scsi_cmnd 
associated with a requeued request.

This addresses Bugzilla entry #5195.

Alan Stern



Signed-off-by: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>

Index: 2613mm1/drivers/scsi/scsi_lib.c
===================================================================
--- 2613mm1.orig/drivers/scsi/scsi_lib.c
+++ 2613mm1/drivers/scsi/scsi_lib.c
@@ -1103,11 +1103,30 @@ static void scsi_generic_done(struct scs
 	scsi_io_completion(cmd, cmd->result == 0 ? cmd->bufflen : 0, 0);
 }
 
+static struct scsi_cmnd *scsi_cmd_from_req(struct request *req)
+{
+	struct scsi_cmnd *cmd;
+
+	/*
+	 * If this request was requeued, it may already have an associated
+	 * scsi_cmnd.  Find out if this is so and return the cmd.
+	 */
+	cmd = req->special;
+	if ((req->flags & REQ_SPECIAL) && req->special) {
+		struct scsi_request *sreq = req->special;
+
+		if (sreq->sr_magic == SCSI_REQ_MAGIC)
+			cmd = NULL;
+	}
+	return cmd;
+}
+
 static int scsi_prep_fn(struct request_queue *q, struct request *req)
 {
 	struct scsi_device *sdev = q->queuedata;
-	struct scsi_cmnd *cmd;
+	struct scsi_cmnd *cmd = scsi_cmd_from_req(req);
 	int specials_only = 0;
+	struct scsi_request *sreq = NULL;
 
 	/*
 	 * Just check to see if the device is online.  If it isn't, we
@@ -1117,6 +1136,8 @@ static int scsi_prep_fn(struct request_q
 	if (unlikely(!scsi_device_online(sdev))) {
 		printk(KERN_ERR "scsi%d (%d:%d): rejecting I/O to offline device\n",
 		       sdev->host->host_no, sdev->id, sdev->lun);
+		if (cmd)
+			scsi_put_command(cmd);
 		return BLKPREP_KILL;
 	}
 	if (unlikely(sdev->sdev_state != SDEV_RUNNING)) {
@@ -1127,6 +1148,8 @@ static int scsi_prep_fn(struct request_q
 			 * at all allowed down */
 			printk(KERN_ERR "scsi%d (%d:%d): rejecting I/O to dead device\n",
 			       sdev->host->host_no, sdev->id, sdev->lun);
+			if (cmd)
+				scsi_put_command(cmd);
 			return BLKPREP_KILL;
 		}
 		/* OK, we only allow special commands (i.e. not
@@ -1142,53 +1165,54 @@ static int scsi_prep_fn(struct request_q
 	 * the remainder of a partially fulfilled request that can 
 	 * come up when there is a medium error.  We have to treat
 	 * these two cases differently.  We differentiate by looking
-	 * at request->cmd, as this tells us the real story.
+	 * at req->special, as this tells us the real story.
 	 */
-	if (req->flags & REQ_SPECIAL && req->special) {
-		struct scsi_request *sreq = req->special;
+	if ((req->flags & REQ_SPECIAL) && req->special) {
+		sreq = req->special;
+		if (sreq->sr_magic != SCSI_REQ_MAGIC)
+			sreq = NULL;
 
-		if (sreq->sr_magic == SCSI_REQ_MAGIC) {
-			cmd = scsi_get_command(sreq->sr_device, GFP_ATOMIC);
-			if (unlikely(!cmd))
-				goto defer;
-			scsi_init_cmd_from_req(cmd, sreq);
-		} else
-			cmd = req->special;
 	} else if (req->flags & (REQ_CMD | REQ_BLOCK_PC)) {
-
-		if(unlikely(specials_only) && !(req->flags & REQ_SPECIAL)) {
-			if(specials_only == SDEV_QUIESCE ||
+		if (unlikely(specials_only) && !(req->flags & REQ_SPECIAL)) {
+			if (specials_only == SDEV_QUIESCE ||
 					specials_only == SDEV_BLOCK)
 				return BLKPREP_DEFER;
 			
 			printk(KERN_ERR "scsi%d (%d:%d): rejecting I/O to device being removed\n",
 			       sdev->host->host_no, sdev->id, sdev->lun);
+			if (cmd)
+				scsi_put_command(cmd);
 			return BLKPREP_KILL;
 		}
-			
-			
-		/*
-		 * Now try and find a command block that we can use.
-		 */
-		if (!req->special) {
-			cmd = scsi_get_command(sdev, GFP_ATOMIC);
-			if (unlikely(!cmd))
-				goto defer;
-		} else
-			cmd = req->special;
-		
-		/* pull a tag out of the request if we have one */
-		cmd->tag = req->tag;
+
 	} else {
 		blk_dump_rq_flags(req, "SCSI bad req");
+		if (cmd)		/* Paranoia */
+			scsi_put_command(cmd);
 		return BLKPREP_KILL;
 	}
+			
+	/*
+	 * Now try and find a command block that we can use.
+	 */
+	if (!cmd) {
+		cmd = scsi_get_command(sdev, GFP_ATOMIC);
+		if (unlikely(!cmd))
+			goto defer;
+
+		if (sreq)
+			scsi_init_cmd_from_req(cmd, sreq);
+		else {
+			/* pull a tag out of the request if we have one */
+			cmd->tag = req->tag;
+		}
 	
-	/* note the overloading of req->special.  When the tag
-	 * is active it always means cmd.  If the tag goes
-	 * back for re-queueing, it may be reset */
-	req->special = cmd;
-	cmd->request = req;
+		/* note the overloading of req->special.  When the tag
+		 * is active it always means cmd.  If the tag goes
+		 * back for re-queueing, it may be reset */
+		req->special = cmd;
+		cmd->request = req;
+	}
 	
 	/*
 	 * FIXME: drop the lock here because the functions below
@@ -1337,16 +1361,21 @@ static inline int scsi_host_queue_ready(
 /*
  * Kill requests for a dead device
  */
-static void scsi_kill_requests(request_queue_t *q)
+static void scsi_kill_request(struct request *req, struct request_queue *q)
 {
-	struct request *req;
+	struct scsi_cmnd *cmd = scsi_cmd_from_req(req);
+
+	blkdev_dequeue_request(req);
+	req->flags |= REQ_QUIET;
+	while (end_that_request_first(req, 0, req->nr_sectors))
+		;
+	end_that_request_last(req);
 
-	while ((req = elv_next_request(q)) != NULL) {
-		blkdev_dequeue_request(req);
-		req->flags |= REQ_QUIET;
-		while (end_that_request_first(req, 0, req->nr_sectors))
-			;
-		end_that_request_last(req);
+	if (cmd) {
+		spin_unlock(q->queue_lock);
+		scsi_release_buffers(cmd);
+		scsi_put_command(cmd);
+		spin_lock(q->queue_lock);
 	}
 }
 
@@ -1370,7 +1399,8 @@ static void scsi_request_fn(struct reque
 
 	if (!sdev) {
 		printk("scsi: killing requests for dead queue\n");
-		scsi_kill_requests(q);
+		while ((req = elv_next_request(q)) != NULL)
+			scsi_kill_request(req, q);
 		return;
 	}
 
@@ -1397,11 +1427,7 @@ static void scsi_request_fn(struct reque
 		if (unlikely(!scsi_device_online(sdev))) {
 			printk(KERN_ERR "scsi%d (%d:%d): rejecting I/O to offline device\n",
 			       sdev->host->host_no, sdev->id, sdev->lun);
-			blkdev_dequeue_request(req);
-			req->flags |= REQ_QUIET;
-			while (end_that_request_first(req, 0, req->nr_sectors))
-				;
-			end_that_request_last(req);
+			scsi_kill_request(req, q);
 			continue;
 		}
 

-
: send the line "unsubscribe linux-scsi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [SCSI Target Devel]     [Linux SCSI Target Infrastructure]     [Kernel Newbies]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Linux IIO]     [Samba]     [Device Mapper]
  Powered by Linux