From: Hannes Reinecke <hare@suse.de>
Date: Tue, 10 Nov 2020 10:41:55 +0100
Subject: virtio-scsi: translate SG_IO host status

References: bsc#1178049

when running with an SG_IO backend we might be getting a SCSI host
status back, which should be translated into a virtio scsi status
to avoid having a silent data corruption if the status isn't
translated properly.

Signed-off-by: Hannes Reinecke <hare@suse.de>
Signed-off-by: Bruce Rogers <brogers@suse.com>
---
 hw/scsi/scsi-generic.c |  8 +++++---
 hw/scsi/trace-events   |  2 +-
 hw/scsi/virtio-scsi.c  | 43 +++++++++++++++++++++++++++++++++++++++---
 include/scsi/utils.h   | 12 +++++++++---
 scsi/qemu-pr-helper.c  |  6 +++---
 scsi/utils.c           | 23 +++++++++-------------
 6 files changed, 67 insertions(+), 27 deletions(-)

diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c
index 32875bedaedf25e7b0cea8363887..b3ea492beedc2a075157957e0595 100644
--- a/hw/scsi/scsi-generic.c
+++ b/hw/scsi/scsi-generic.c
@@ -72,7 +72,7 @@ static void scsi_free_request(SCSIRequest *req)
 /* Helper function for command completion.  */
 static void scsi_command_complete_noio(SCSIGenericReq *r, int ret)
 {
-    int status;
+    uint32_t status;
     SCSISense sense;
 
     assert(r->req.aiocb == NULL);
@@ -82,7 +82,7 @@ static void scsi_command_complete_noio(SCSIGenericReq *r, int ret)
         goto done;
     }
     status = sg_io_sense_from_errno(-ret, &r->io_header, &sense);
-    if (status == CHECK_CONDITION) {
+    if ((status & 0xff) == CHECK_CONDITION) {
         if (r->io_header.driver_status & SG_ERR_DRIVER_SENSE) {
             r->req.sense_len = r->io_header.sb_len_wr;
         } else {
@@ -90,7 +90,8 @@ static void scsi_command_complete_noio(SCSIGenericReq *r, int ret)
         }
     }
 
-    trace_scsi_generic_command_complete_noio(r, r->req.tag, status);
+    trace_scsi_generic_command_complete_noio(r, r->req.tag, status & 0xff,
+                                             (status >> 8) & 0xff);
 
     scsi_req_complete(&r->req, status);
 done:
@@ -235,6 +236,7 @@ static int scsi_generic_emulate_block_limits(SCSIGenericReq *r, SCSIDevice *s)
     * the hardware in scsi_command_complete_noio. Clean
     * up the io_header to avoid reporting it.
     */
+    r->io_header.host_status = 0;
     r->io_header.driver_status = 0;
     r->io_header.status = 0;
 
diff --git a/hw/scsi/trace-events b/hw/scsi/trace-events
index 13babd26dff43d5052886cf955a5..bce865c2222b0ece52d16ab1d90a 100644
--- a/hw/scsi/trace-events
+++ b/hw/scsi/trace-events
@@ -330,7 +330,7 @@ scsi_disk_new_request(uint32_t lun, uint32_t tag, const char *line) "Command: lu
 scsi_disk_aio_sgio_command(uint32_t tag, uint8_t cmd, uint64_t lba, int len, uint32_t timeout) "disk aio sgio: tag=0x%x cmd 0x%x (sector %" PRId64 ", count %d) timeout %u"
 
 # scsi-generic.c
-scsi_generic_command_complete_noio(void *req, uint32_t tag, int statuc) "Command complete %p tag=0x%x status=%d"
+scsi_generic_command_complete_noio(void *req, uint32_t tag, uint8_t status, uint8_t host_status) "Command complete %p tag=0x%x status=0x%x host_status=0x%x"
 scsi_generic_read_complete(uint32_t tag, int len) "Data ready tag=0x%x len=%d"
 scsi_generic_read_data(uint32_t tag, uint32_t timeout) "scsi_read_data tag=0x%x timeout %u"
 scsi_generic_write_complete(int ret) "scsi_write_complete() ret = %d"
diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c
index de25a1c21d84f38eca9aaf1114d4..3155658db33f95a572a4c7ff495e 100644
--- a/hw/scsi/virtio-scsi.c
+++ b/hw/scsi/virtio-scsi.c
@@ -496,9 +496,46 @@ static void virtio_scsi_command_complete(SCSIRequest *r, uint32_t status,
         return;
     }
 
-    req->resp.cmd.response = VIRTIO_SCSI_S_OK;
-    req->resp.cmd.status = status;
-    if (req->resp.cmd.status == GOOD) {
+    switch ((status >> 8) & 0xff) {
+    case SG_ERR_DID_OK:
+        req->resp.cmd.response = VIRTIO_SCSI_S_OK;
+        break;
+    case SG_ERR_DID_ERROR:
+        req->resp.cmd.response = VIRTIO_SCSI_S_OVERRUN;
+        break;
+    case SG_ERR_DID_NO_CONNECT:
+        req->resp.cmd.response = VIRTIO_SCSI_S_INCORRECT_LUN;
+        break;
+    case SG_ERR_DID_ABORT:
+    case SG_ERR_DID_TIME_OUT:
+        req->resp.cmd.response = VIRTIO_SCSI_S_ABORTED;
+        break;
+    case SG_ERR_DID_BAD_TARGET:
+        req->resp.cmd.response = VIRTIO_SCSI_S_BAD_TARGET;
+        break;
+    case SG_ERR_DID_RESET:
+        req->resp.cmd.response = VIRTIO_SCSI_S_RESET;
+        break;
+    case SG_ERR_DID_BUS_BUSY:
+        req->resp.cmd.response = VIRTIO_SCSI_S_BUSY;
+        break;
+    case SG_ERR_DID_TRANSPORT_DISRUPTED:
+        req->resp.cmd.response = VIRTIO_SCSI_S_TRANSPORT_FAILURE;
+        break;
+    case SG_ERR_DID_TARGET_FAILURE:
+        req->resp.cmd.response = VIRTIO_SCSI_S_TARGET_FAILURE;
+        break;
+    case SG_ERR_DID_NEXUS_FAILURE:
+        req->resp.cmd.response = VIRTIO_SCSI_S_NEXUS_FAILURE;
+        break;
+    default:
+        req->resp.cmd.response = VIRTIO_SCSI_S_FAILURE;
+        break;
+    }
+
+    req->resp.cmd.status = (status & 0xff);
+    if (req->resp.cmd.status == GOOD &&
+        req->resp.cmd.response == VIRTIO_SCSI_S_OK) {
         req->resp.cmd.resid = virtio_tswap32(vdev, resid);
     } else {
         req->resp.cmd.resid = 0;
diff --git a/include/scsi/utils.h b/include/scsi/utils.h
index fbc5588279939d70a5e31627bd2a..92f6c47944cfd1fb6284b4e2b210 100644
--- a/include/scsi/utils.h
+++ b/include/scsi/utils.h
@@ -126,11 +126,17 @@ int scsi_cdb_length(uint8_t *buf);
 #define SG_ERR_DID_NO_CONNECT  0x01
 #define SG_ERR_DID_BUS_BUSY    0x02
 #define SG_ERR_DID_TIME_OUT    0x03
-
+#define SG_ERR_DID_BAD_TARGET  0x04
+#define SG_ERR_DID_ABORT       0x05
+#define SG_ERR_DID_ERROR       0x07
+#define SG_ERR_DID_RESET       0x08
+#define SG_ERR_DID_TRANSPORT_DISRUPTED 0x0e
+#define SG_ERR_DID_TARGET_FAILURE 0x10
+#define SG_ERR_DID_NEXUS_FAILURE 0x11
 #define SG_ERR_DRIVER_SENSE    0x08
 
-int sg_io_sense_from_errno(int errno_value, struct sg_io_hdr *io_hdr,
-                           SCSISense *sense);
+uint32_t sg_io_sense_from_errno(int errno_value, struct sg_io_hdr *io_hdr,
+                                SCSISense *sense);
 #endif
 
 #endif
diff --git a/scsi/qemu-pr-helper.c b/scsi/qemu-pr-helper.c
index 38c273de19573ad8421da6439153..3c474bdd5688fe9d6e2b64e53637 100644
--- a/scsi/qemu-pr-helper.c
+++ b/scsi/qemu-pr-helper.c
@@ -134,7 +134,7 @@ static int do_sgio_worker(void *opaque)
     PRHelperSGIOData *data = opaque;
     struct sg_io_hdr io_hdr;
     int ret;
-    int status;
+    uint32_t status;
     SCSISense sense_code;
 
     memset(data->sense, 0, PR_HELPER_SENSE_SIZE);
@@ -151,13 +151,13 @@ static int do_sgio_worker(void *opaque)
     ret = ioctl(data->fd, SG_IO, &io_hdr);
     status = sg_io_sense_from_errno(ret < 0 ? errno : 0, &io_hdr,
                                     &sense_code);
-    if (status == GOOD) {
+    if ((status & 0xff) == GOOD) {
         data->sz -= io_hdr.resid;
     } else {
         data->sz = 0;
     }
 
-    if (status == CHECK_CONDITION &&
+    if ((status & 0xff) == CHECK_CONDITION &&
         !(io_hdr.driver_status & SG_ERR_DRIVER_SENSE)) {
         scsi_build_sense(data->sense, sense_code);
     }
diff --git a/scsi/utils.c b/scsi/utils.c
index c50e81fdb87f535e6f49dd31699e..c09f4aff21e34860c1b41612cd0d 100644
--- a/scsi/utils.c
+++ b/scsi/utils.c
@@ -565,7 +565,7 @@ const char *scsi_command_name(uint8_t cmd)
 }
 
 #ifdef CONFIG_LINUX
-int sg_io_sense_from_errno(int errno_value, struct sg_io_hdr *io_hdr,
+uint32_t sg_io_sense_from_errno(int errno_value, struct sg_io_hdr *io_hdr,
                            SCSISense *sense)
 {
     if (errno_value != 0) {
@@ -580,21 +580,16 @@ int sg_io_sense_from_errno(int errno_value, struct sg_io_hdr *io_hdr,
             return CHECK_CONDITION;
         }
     } else {
-        if (io_hdr->host_status == SG_ERR_DID_NO_CONNECT ||
-            io_hdr->host_status == SG_ERR_DID_BUS_BUSY ||
-            io_hdr->host_status == SG_ERR_DID_TIME_OUT ||
-            (io_hdr->driver_status & SG_ERR_DRIVER_TIMEOUT)) {
-            return BUSY;
-        } else if (io_hdr->host_status) {
-            *sense = SENSE_CODE(I_T_NEXUS_LOSS);
-            return CHECK_CONDITION;
-        } else if (io_hdr->status) {
-            return io_hdr->status;
+        uint32_t status = GOOD;
+
+        if (io_hdr->status) {
+            status = io_hdr->status;
         } else if (io_hdr->driver_status & SG_ERR_DRIVER_SENSE) {
-            return CHECK_CONDITION;
-        } else {
-            return GOOD;
+            status = CHECK_CONDITION;
         }
+        if (io_hdr->host_status)
+            status |= (io_hdr->host_status << 8);
+        return status;
     }
 }
 #endif
