From 87abbecfdccae27a2fef912bb55416ac5113bbd8 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Sep 22 2017 14:48:17 +0000 Subject: Backport persistent reservation manager in preparation for SELinux work --- diff --git a/1001-io-add-new-qio_channel_-readv-writev-read-write-_all.patch b/1001-io-add-new-qio_channel_-readv-writev-read-write-_all.patch new file mode 100644 index 0000000..f4d92ab --- /dev/null +++ b/1001-io-add-new-qio_channel_-readv-writev-read-write-_all.patch @@ -0,0 +1,384 @@ +From 758d848ef10835108a75187d1a1a0418167f04b2 Mon Sep 17 00:00:00 2001 +From: "Daniel P. Berrange" +Date: Wed, 30 Aug 2017 14:53:59 +0100 +Subject: [PATCH 01/15] io: add new qio_channel_{readv, writev, read, + write}_all functions + +These functions wait until they are able to read / write the full +requested data buffer(s). + +Reviewed-by: Eric Blake +Signed-off-by: Daniel P. Berrange +--- + include/io/channel.h | 90 +++++++++++++++++++++++++++++++++++++++ + io/channel.c | 94 +++++++++++++++++++++++++++++++++++++++++ + tests/io-channel-helpers.c | 102 ++++----------------------------------------- + 3 files changed, 193 insertions(+), 93 deletions(-) + +diff --git a/include/io/channel.h b/include/io/channel.h +index db9bb022a1..e11a62ea50 100644 +--- a/include/io/channel.h ++++ b/include/io/channel.h +@@ -269,6 +269,58 @@ ssize_t qio_channel_writev_full(QIOChannel *ioc, + Error **errp); + + /** ++ * qio_channel_readv_all: ++ * @ioc: the channel object ++ * @iov: the array of memory regions to read data into ++ * @niov: the length of the @iov array ++ * @errp: pointer to a NULL-initialized error object ++ * ++ * Read data from the IO channel, storing it in the ++ * memory regions referenced by @iov. Each element ++ * in the @iov will be fully populated with data ++ * before the next one is used. The @niov parameter ++ * specifies the total number of elements in @iov. ++ * ++ * The function will wait for all requested data ++ * to be read, yielding from the current coroutine ++ * if required. ++ * ++ * If end-of-file occurs before all requested data ++ * has been read, an error will be reported. ++ * ++ * Returns: 0 if all bytes were read, or -1 on error ++ */ ++int qio_channel_readv_all(QIOChannel *ioc, ++ const struct iovec *iov, ++ size_t niov, ++ Error **errp); ++ ++ ++/** ++ * qio_channel_writev_all: ++ * @ioc: the channel object ++ * @iov: the array of memory regions to write data from ++ * @niov: the length of the @iov array ++ * @errp: pointer to a NULL-initialized error object ++ * ++ * Write data to the IO channel, reading it from the ++ * memory regions referenced by @iov. Each element ++ * in the @iov will be fully sent, before the next ++ * one is used. The @niov parameter specifies the ++ * total number of elements in @iov. ++ * ++ * The function will wait for all requested data ++ * to be written, yielding from the current coroutine ++ * if required. ++ * ++ * Returns: 0 if all bytes were written, or -1 on error ++ */ ++int qio_channel_writev_all(QIOChannel *ioc, ++ const struct iovec *iov, ++ size_t niov, ++ Error **erp); ++ ++/** + * qio_channel_readv: + * @ioc: the channel object + * @iov: the array of memory regions to read data into +@@ -331,6 +383,44 @@ ssize_t qio_channel_write(QIOChannel *ioc, + Error **errp); + + /** ++ * qio_channel_read_all: ++ * @ioc: the channel object ++ * @buf: the memory region to read data into ++ * @buflen: the number of bytes to @buf ++ * @errp: pointer to a NULL-initialized error object ++ * ++ * Reads @buflen bytes into @buf, possibly blocking or (if the ++ * channel is non-blocking) yielding from the current coroutine ++ * multiple times until the entire content is read. If end-of-file ++ * occurs it will return an error rather than a short-read. Otherwise ++ * behaves as qio_channel_read(). ++ * ++ * Returns: 0 if all bytes were read, or -1 on error ++ */ ++int qio_channel_read_all(QIOChannel *ioc, ++ char *buf, ++ size_t buflen, ++ Error **errp); ++/** ++ * qio_channel_write_all: ++ * @ioc: the channel object ++ * @buf: the memory region to write data into ++ * @buflen: the number of bytes to @buf ++ * @errp: pointer to a NULL-initialized error object ++ * ++ * Writes @buflen bytes from @buf, possibly blocking or (if the ++ * channel is non-blocking) yielding from the current coroutine ++ * multiple times until the entire content is written. Otherwise ++ * behaves as qio_channel_write(). ++ * ++ * Returns: 0 if all bytes were written, or -1 on error ++ */ ++int qio_channel_write_all(QIOChannel *ioc, ++ const char *buf, ++ size_t buflen, ++ Error **errp); ++ ++/** + * qio_channel_set_blocking: + * @ioc: the channel object + * @enabled: the blocking flag state +diff --git a/io/channel.c b/io/channel.c +index 1cfb8b33a2..5e8c2f0a91 100644 +--- a/io/channel.c ++++ b/io/channel.c +@@ -22,6 +22,7 @@ + #include "io/channel.h" + #include "qapi/error.h" + #include "qemu/main-loop.h" ++#include "qemu/iov.h" + + bool qio_channel_has_feature(QIOChannel *ioc, + QIOChannelFeature feature) +@@ -85,6 +86,79 @@ ssize_t qio_channel_writev_full(QIOChannel *ioc, + } + + ++ ++int qio_channel_readv_all(QIOChannel *ioc, ++ const struct iovec *iov, ++ size_t niov, ++ Error **errp) ++{ ++ int ret = -1; ++ struct iovec *local_iov = g_new(struct iovec, niov); ++ struct iovec *local_iov_head = local_iov; ++ unsigned int nlocal_iov = niov; ++ ++ nlocal_iov = iov_copy(local_iov, nlocal_iov, ++ iov, niov, ++ 0, iov_size(iov, niov)); ++ ++ while (nlocal_iov > 0) { ++ ssize_t len; ++ len = qio_channel_readv(ioc, local_iov, nlocal_iov, errp); ++ if (len == QIO_CHANNEL_ERR_BLOCK) { ++ qio_channel_wait(ioc, G_IO_IN); ++ continue; ++ } else if (len < 0) { ++ goto cleanup; ++ } else if (len == 0) { ++ error_setg(errp, ++ "Unexpected end-of-file before all bytes were read"); ++ goto cleanup; ++ } ++ ++ iov_discard_front(&local_iov, &nlocal_iov, len); ++ } ++ ++ ret = 0; ++ ++ cleanup: ++ g_free(local_iov_head); ++ return ret; ++} ++ ++int qio_channel_writev_all(QIOChannel *ioc, ++ const struct iovec *iov, ++ size_t niov, ++ Error **errp) ++{ ++ int ret = -1; ++ struct iovec *local_iov = g_new(struct iovec, niov); ++ struct iovec *local_iov_head = local_iov; ++ unsigned int nlocal_iov = niov; ++ ++ nlocal_iov = iov_copy(local_iov, nlocal_iov, ++ iov, niov, ++ 0, iov_size(iov, niov)); ++ ++ while (nlocal_iov > 0) { ++ ssize_t len; ++ len = qio_channel_writev(ioc, local_iov, nlocal_iov, errp); ++ if (len == QIO_CHANNEL_ERR_BLOCK) { ++ qio_channel_wait(ioc, G_IO_OUT); ++ continue; ++ } ++ if (len < 0) { ++ goto cleanup; ++ } ++ ++ iov_discard_front(&local_iov, &nlocal_iov, len); ++ } ++ ++ ret = 0; ++ cleanup: ++ g_free(local_iov_head); ++ return ret; ++} ++ + ssize_t qio_channel_readv(QIOChannel *ioc, + const struct iovec *iov, + size_t niov, +@@ -123,6 +197,26 @@ ssize_t qio_channel_write(QIOChannel *ioc, + } + + ++int qio_channel_read_all(QIOChannel *ioc, ++ char *buf, ++ size_t buflen, ++ Error **errp) ++{ ++ struct iovec iov = { .iov_base = buf, .iov_len = buflen }; ++ return qio_channel_readv_all(ioc, &iov, 1, errp); ++} ++ ++ ++int qio_channel_write_all(QIOChannel *ioc, ++ const char *buf, ++ size_t buflen, ++ Error **errp) ++{ ++ struct iovec iov = { .iov_base = (char *)buf, .iov_len = buflen }; ++ return qio_channel_writev_all(ioc, &iov, 1, errp); ++} ++ ++ + int qio_channel_set_blocking(QIOChannel *ioc, + bool enabled, + Error **errp) +diff --git a/tests/io-channel-helpers.c b/tests/io-channel-helpers.c +index 05e5579cf8..5430e1389d 100644 +--- a/tests/io-channel-helpers.c ++++ b/tests/io-channel-helpers.c +@@ -21,6 +21,7 @@ + #include "qemu/osdep.h" + #include "io-channel-helpers.h" + #include "qapi/error.h" ++#include "qemu/iov.h" + + struct QIOChannelTest { + QIOChannel *src; +@@ -37,77 +38,17 @@ struct QIOChannelTest { + }; + + +-static void test_skip_iovec(struct iovec **iov, +- size_t *niov, +- size_t skip, +- struct iovec *old) +-{ +- size_t offset = 0; +- size_t i; +- +- for (i = 0; i < *niov; i++) { +- if (skip < (*iov)[i].iov_len) { +- old->iov_len = (*iov)[i].iov_len; +- old->iov_base = (*iov)[i].iov_base; +- +- (*iov)[i].iov_len -= skip; +- (*iov)[i].iov_base += skip; +- break; +- } else { +- skip -= (*iov)[i].iov_len; +- +- if (i == 0 && old->iov_base) { +- (*iov)[i].iov_len = old->iov_len; +- (*iov)[i].iov_base = old->iov_base; +- old->iov_len = 0; +- old->iov_base = NULL; +- } +- +- offset++; +- } +- } +- +- *iov = *iov + offset; +- *niov -= offset; +-} +- +- + /* This thread sends all data using iovecs */ + static gpointer test_io_thread_writer(gpointer opaque) + { + QIOChannelTest *data = opaque; +- struct iovec *iov = data->inputv; +- size_t niov = data->niov; +- struct iovec old = { 0 }; + + qio_channel_set_blocking(data->src, data->blocking, NULL); + +- while (niov) { +- ssize_t ret; +- ret = qio_channel_writev(data->src, +- iov, +- niov, +- &data->writeerr); +- if (ret == QIO_CHANNEL_ERR_BLOCK) { +- if (data->blocking) { +- error_setg(&data->writeerr, +- "Unexpected I/O blocking"); +- break; +- } else { +- qio_channel_wait(data->src, +- G_IO_OUT); +- continue; +- } +- } else if (ret < 0) { +- break; +- } else if (ret == 0) { +- error_setg(&data->writeerr, +- "Unexpected zero length write"); +- break; +- } +- +- test_skip_iovec(&iov, &niov, ret, &old); +- } ++ qio_channel_writev_all(data->src, ++ data->inputv, ++ data->niov, ++ &data->writeerr); + + return NULL; + } +@@ -117,38 +58,13 @@ static gpointer test_io_thread_writer(gpointer opaque) + static gpointer test_io_thread_reader(gpointer opaque) + { + QIOChannelTest *data = opaque; +- struct iovec *iov = data->outputv; +- size_t niov = data->niov; +- struct iovec old = { 0 }; + + qio_channel_set_blocking(data->dst, data->blocking, NULL); + +- while (niov) { +- ssize_t ret; +- +- ret = qio_channel_readv(data->dst, +- iov, +- niov, +- &data->readerr); +- +- if (ret == QIO_CHANNEL_ERR_BLOCK) { +- if (data->blocking) { +- error_setg(&data->readerr, +- "Unexpected I/O blocking"); +- break; +- } else { +- qio_channel_wait(data->dst, +- G_IO_IN); +- continue; +- } +- } else if (ret < 0) { +- break; +- } else if (ret == 0) { +- break; +- } +- +- test_skip_iovec(&iov, &niov, ret, &old); +- } ++ qio_channel_readv_all(data->dst, ++ data->outputv, ++ data->niov, ++ &data->readerr); + + return NULL; + } +-- +2.13.5 + diff --git a/1002-io-Yield-rather-than-wait-when-already-in-coroutine.patch b/1002-io-Yield-rather-than-wait-when-already-in-coroutine.patch new file mode 100644 index 0000000..db73177 --- /dev/null +++ b/1002-io-Yield-rather-than-wait-when-already-in-coroutine.patch @@ -0,0 +1,56 @@ +From 5171f2cd3612ce2772b272e4dd8119fdb0c06124 Mon Sep 17 00:00:00 2001 +From: Eric Blake +Date: Tue, 5 Sep 2017 14:11:12 -0500 +Subject: [PATCH 02/15] io: Yield rather than wait when already in coroutine + +The new qio_channel_{read,write}{,v}_all functions are documented +as yielding until data is available. When used on a blocking +channel, this yield is done via qio_channel_wait() which spawns +a nested event loop under the hood (so it is that secondary loop +which yields as needed); but if we are already in a coroutine (at +which point QIO_CHANNEL_ERR_BLOCK is only possible if we are a +non-blocking channel), we want to yield the current coroutine +instead of spawning a nested event loop. + +Signed-off-by: Eric Blake +Message-Id: <20170905191114.5959-2-eblake@redhat.com> +Acked-by: Daniel P. Berrange +[commit message updated] +Signed-off-by: Eric Blake +--- + io/channel.c | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/io/channel.c b/io/channel.c +index 5e8c2f0a91..9e62794cab 100644 +--- a/io/channel.c ++++ b/io/channel.c +@@ -105,7 +105,11 @@ int qio_channel_readv_all(QIOChannel *ioc, + ssize_t len; + len = qio_channel_readv(ioc, local_iov, nlocal_iov, errp); + if (len == QIO_CHANNEL_ERR_BLOCK) { +- qio_channel_wait(ioc, G_IO_IN); ++ if (qemu_in_coroutine()) { ++ qio_channel_yield(ioc, G_IO_IN); ++ } else { ++ qio_channel_wait(ioc, G_IO_IN); ++ } + continue; + } else if (len < 0) { + goto cleanup; +@@ -143,7 +147,11 @@ int qio_channel_writev_all(QIOChannel *ioc, + ssize_t len; + len = qio_channel_writev(ioc, local_iov, nlocal_iov, errp); + if (len == QIO_CHANNEL_ERR_BLOCK) { +- qio_channel_wait(ioc, G_IO_OUT); ++ if (qemu_in_coroutine()) { ++ qio_channel_yield(ioc, G_IO_OUT); ++ } else { ++ qio_channel_wait(ioc, G_IO_OUT); ++ } + continue; + } + if (len < 0) { +-- +2.13.5 + diff --git a/1003-scsi-bus-correct-responses-for-INQUIRY-and-REQUEST-S.patch b/1003-scsi-bus-correct-responses-for-INQUIRY-and-REQUEST-S.patch new file mode 100644 index 0000000..a2af733 --- /dev/null +++ b/1003-scsi-bus-correct-responses-for-INQUIRY-and-REQUEST-S.patch @@ -0,0 +1,71 @@ +From b07725e3e5e05610691815ee921a6b3307685815 Mon Sep 17 00:00:00 2001 +From: Hannes Reinecke +Date: Fri, 18 Aug 2017 11:37:02 +0200 +Subject: [PATCH 03/15] scsi-bus: correct responses for INQUIRY and REQUEST + SENSE + +According to SPC-3 INQUIRY and REQUEST SENSE should return GOOD +even on unsupported LUNS. + +Signed-off-by: Hannes Reinecke +Message-Id: <1503049022-14749-1-git-send-email-hare@suse.de> +Reported-by: Laszlo Ersek +Fixes: ded6ddc5a7b95217557fa360913d1213e12d4a6d +Cc: qemu-stable@nongnu.org +Signed-off-by: Paolo Bonzini +Signed-off-by: Hannes Reinecke +--- + hw/scsi/scsi-bus.c | 29 +++++++++++++++++++++++++---- + 1 file changed, 25 insertions(+), 4 deletions(-) + +diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c +index e364410a23..ade31c11f5 100644 +--- a/hw/scsi/scsi-bus.c ++++ b/hw/scsi/scsi-bus.c +@@ -516,8 +516,10 @@ static size_t scsi_sense_len(SCSIRequest *req) + static int32_t scsi_target_send_command(SCSIRequest *req, uint8_t *buf) + { + SCSITargetReq *r = DO_UPCAST(SCSITargetReq, req, req); ++ int fixed_sense = (req->cmd.buf[1] & 1) == 0; + +- if (req->lun != 0) { ++ if (req->lun != 0 && ++ buf[0] != INQUIRY && buf[0] != REQUEST_SENSE) { + scsi_req_build_sense(req, SENSE_CODE(LUN_NOT_SUPPORTED)); + scsi_req_complete(req, CHECK_CONDITION); + return 0; +@@ -535,9 +537,28 @@ static int32_t scsi_target_send_command(SCSIRequest *req, uint8_t *buf) + break; + case REQUEST_SENSE: + scsi_target_alloc_buf(&r->req, scsi_sense_len(req)); +- r->len = scsi_device_get_sense(r->req.dev, r->buf, +- MIN(req->cmd.xfer, r->buf_len), +- (req->cmd.buf[1] & 1) == 0); ++ if (req->lun != 0) { ++ const struct SCSISense sense = SENSE_CODE(LUN_NOT_SUPPORTED); ++ ++ if (fixed_sense) { ++ r->buf[0] = 0x70; ++ r->buf[2] = sense.key; ++ r->buf[10] = 10; ++ r->buf[12] = sense.asc; ++ r->buf[13] = sense.ascq; ++ r->len = MIN(req->cmd.xfer, SCSI_SENSE_LEN); ++ } else { ++ r->buf[0] = 0x72; ++ r->buf[1] = sense.key; ++ r->buf[2] = sense.asc; ++ r->buf[3] = sense.ascq; ++ r->len = 8; ++ } ++ } else { ++ r->len = scsi_device_get_sense(r->req.dev, r->buf, ++ MIN(req->cmd.xfer, r->buf_len), ++ fixed_sense); ++ } + if (r->req.dev->sense_is_ua) { + scsi_device_unit_attention_reported(req->dev); + r->req.dev->sense_len = 0; +-- +2.13.5 + diff --git a/1004-scsi-Refactor-scsi-sense-interpreting-code.patch b/1004-scsi-Refactor-scsi-sense-interpreting-code.patch new file mode 100644 index 0000000..f1e02f5 --- /dev/null +++ b/1004-scsi-Refactor-scsi-sense-interpreting-code.patch @@ -0,0 +1,194 @@ +From f35ef58b2bac932bbd379602fe23e7a190530075 Mon Sep 17 00:00:00 2001 +From: Fam Zheng +Date: Mon, 21 Aug 2017 22:10:05 +0800 +Subject: [PATCH 04/15] scsi: Refactor scsi sense interpreting code + +So that it can be reused outside of iscsi.c. + +Also update MAINTAINERS to include the new files in SCSI section. + +Signed-off-by: Fam Zheng +Message-Id: <20170821141008.19383-2-famz@redhat.com> +Signed-off-by: Paolo Bonzini +--- + MAINTAINERS | 2 ++ + block/iscsi.c | 45 ++++----------------------------------------- + include/scsi/scsi.h | 19 +++++++++++++++++++ + util/Makefile.objs | 1 + + util/scsi.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + 5 files changed, 78 insertions(+), 41 deletions(-) + create mode 100644 include/scsi/scsi.h + create mode 100644 util/scsi.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index ccee28b12d..2a4e5036ae 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -969,7 +969,9 @@ SCSI + M: Paolo Bonzini + S: Supported + F: include/hw/scsi/* ++F: include/scsi/* + F: hw/scsi/* ++F: util/scsi* + F: tests/virtio-scsi-test.c + T: git git://github.com/bonzini/qemu.git scsi-next + +diff --git a/block/iscsi.c b/block/iscsi.c +index d557c99668..4bed63cd6d 100644 +--- a/block/iscsi.c ++++ b/block/iscsi.c +@@ -40,6 +40,7 @@ + #include "qmp-commands.h" + #include "qapi/qmp/qstring.h" + #include "crypto/secret.h" ++#include "scsi/scsi.h" + + #include + #include +@@ -209,47 +210,9 @@ static inline unsigned exp_random(double mean) + + static int iscsi_translate_sense(struct scsi_sense *sense) + { +- int ret; +- +- switch (sense->key) { +- case SCSI_SENSE_NOT_READY: +- return -EBUSY; +- case SCSI_SENSE_DATA_PROTECTION: +- return -EACCES; +- case SCSI_SENSE_COMMAND_ABORTED: +- return -ECANCELED; +- case SCSI_SENSE_ILLEGAL_REQUEST: +- /* Parse ASCQ */ +- break; +- default: +- return -EIO; +- } +- switch (sense->ascq) { +- case SCSI_SENSE_ASCQ_PARAMETER_LIST_LENGTH_ERROR: +- case SCSI_SENSE_ASCQ_INVALID_OPERATION_CODE: +- case SCSI_SENSE_ASCQ_INVALID_FIELD_IN_CDB: +- case SCSI_SENSE_ASCQ_INVALID_FIELD_IN_PARAMETER_LIST: +- ret = -EINVAL; +- break; +- case SCSI_SENSE_ASCQ_LBA_OUT_OF_RANGE: +- ret = -ENOSPC; +- break; +- case SCSI_SENSE_ASCQ_LOGICAL_UNIT_NOT_SUPPORTED: +- ret = -ENOTSUP; +- break; +- case SCSI_SENSE_ASCQ_MEDIUM_NOT_PRESENT: +- case SCSI_SENSE_ASCQ_MEDIUM_NOT_PRESENT_TRAY_CLOSED: +- case SCSI_SENSE_ASCQ_MEDIUM_NOT_PRESENT_TRAY_OPEN: +- ret = -ENOMEDIUM; +- break; +- case SCSI_SENSE_ASCQ_WRITE_PROTECTED: +- ret = -EACCES; +- break; +- default: +- ret = -EIO; +- break; +- } +- return ret; ++ return - scsi_sense_to_errno(sense->key, ++ (sense->ascq & 0xFF00) >> 8, ++ sense->ascq & 0xFF); + } + + /* Called (via iscsi_service) with QemuMutex held. */ +diff --git a/include/scsi/scsi.h b/include/scsi/scsi.h +new file mode 100644 +index 0000000000..f894ace4bf +--- /dev/null ++++ b/include/scsi/scsi.h +@@ -0,0 +1,19 @@ ++/* ++ * SCSI helpers ++ * ++ * Copyright 2017 Red Hat, Inc. ++ * ++ * Authors: ++ * Fam Zheng ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the Free ++ * Software Foundation; either version 2 of the License, or (at your option) ++ * any later version. ++ */ ++#ifndef QEMU_SCSI_H ++#define QEMU_SCSI_H ++ ++int scsi_sense_to_errno(int key, int asc, int ascq); ++ ++#endif +diff --git a/util/Makefile.objs b/util/Makefile.objs +index 50a55ecc75..c9e6c493d3 100644 +--- a/util/Makefile.objs ++++ b/util/Makefile.objs +@@ -45,3 +45,4 @@ util-obj-y += qht.o + util-obj-y += range.o + util-obj-y += stats64.o + util-obj-y += systemd.o ++util-obj-y += scsi.o +diff --git a/util/scsi.c b/util/scsi.c +new file mode 100644 +index 0000000000..a6710799fc +--- /dev/null ++++ b/util/scsi.c +@@ -0,0 +1,52 @@ ++/* ++ * SCSI helpers ++ * ++ * Copyright 2017 Red Hat, Inc. ++ * ++ * Authors: ++ * Fam Zheng ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the Free ++ * Software Foundation; either version 2 of the License, or (at your option) ++ * any later version. ++ */ ++ ++#include "qemu/osdep.h" ++#include "scsi/scsi.h" ++ ++int scsi_sense_to_errno(int key, int asc, int ascq) ++{ ++ switch (key) { ++ case 0x02: /* NOT READY */ ++ return EBUSY; ++ case 0x07: /* DATA PROTECTION */ ++ return EACCES; ++ case 0x0b: /* COMMAND ABORTED */ ++ return ECANCELED; ++ case 0x05: /* ILLEGAL REQUEST */ ++ /* Parse ASCQ */ ++ break; ++ default: ++ return EIO; ++ } ++ switch ((asc << 8) | ascq) { ++ case 0x1a00: /* PARAMETER LIST LENGTH ERROR */ ++ case 0x2000: /* INVALID OPERATION CODE */ ++ case 0x2400: /* INVALID FIELD IN CDB */ ++ case 0x2600: /* INVALID FIELD IN PARAMETER LIST */ ++ return EINVAL; ++ case 0x2100: /* LBA OUT OF RANGE */ ++ return ENOSPC; ++ case 0x2500: /* LOGICAL UNIT NOT SUPPORTED */ ++ return ENOTSUP; ++ case 0x3a00: /* MEDIUM NOT PRESENT */ ++ case 0x3a01: /* MEDIUM NOT PRESENT TRAY CLOSED */ ++ case 0x3a02: /* MEDIUM NOT PRESENT TRAY OPEN */ ++ return ENOMEDIUM; ++ case 0x2700: /* WRITE PROTECTED */ ++ return EACCES; ++ default: ++ return EIO; ++ } ++} +-- +2.13.5 + diff --git a/1005-scsi-Improve-scsi_sense_to_errno.patch b/1005-scsi-Improve-scsi_sense_to_errno.patch new file mode 100644 index 0000000..07f9cb5 --- /dev/null +++ b/1005-scsi-Improve-scsi_sense_to_errno.patch @@ -0,0 +1,61 @@ +From a7dc92dac7cedb3ba6b6d724c7579f05399e2f2e Mon Sep 17 00:00:00 2001 +From: Fam Zheng +Date: Mon, 21 Aug 2017 22:10:06 +0800 +Subject: [PATCH 05/15] scsi: Improve scsi_sense_to_errno + +Tweak the errno mapping to return more accurate/appropriate values. + +Signed-off-by: Fam Zheng +Message-Id: <20170821141008.19383-3-famz@redhat.com> +Signed-off-by: Paolo Bonzini +--- + util/scsi.c | 16 ++++++++++++---- + 1 file changed, 12 insertions(+), 4 deletions(-) + +diff --git a/util/scsi.c b/util/scsi.c +index a6710799fc..472eb5bea5 100644 +--- a/util/scsi.c ++++ b/util/scsi.c +@@ -18,13 +18,16 @@ + int scsi_sense_to_errno(int key, int asc, int ascq) + { + switch (key) { +- case 0x02: /* NOT READY */ +- return EBUSY; +- case 0x07: /* DATA PROTECTION */ +- return EACCES; ++ case 0x00: /* NO SENSE */ ++ case 0x01: /* RECOVERED ERROR */ ++ case 0x06: /* UNIT ATTENTION */ ++ /* These sense keys are not errors */ ++ return 0; + case 0x0b: /* COMMAND ABORTED */ + return ECANCELED; ++ case 0x02: /* NOT READY */ + case 0x05: /* ILLEGAL REQUEST */ ++ case 0x07: /* DATA PROTECTION */ + /* Parse ASCQ */ + break; + default: +@@ -37,6 +40,7 @@ int scsi_sense_to_errno(int key, int asc, int ascq) + case 0x2600: /* INVALID FIELD IN PARAMETER LIST */ + return EINVAL; + case 0x2100: /* LBA OUT OF RANGE */ ++ case 0x2707: /* SPACE ALLOC FAILED */ + return ENOSPC; + case 0x2500: /* LOGICAL UNIT NOT SUPPORTED */ + return ENOTSUP; +@@ -46,6 +50,10 @@ int scsi_sense_to_errno(int key, int asc, int ascq) + return ENOMEDIUM; + case 0x2700: /* WRITE PROTECTED */ + return EACCES; ++ case 0x0401: /* NOT READY, IN PROGRESS OF BECOMING READY */ ++ return EAGAIN; ++ case 0x0402: /* NOT READY, INITIALIZING COMMAND REQUIRED */ ++ return ENOTCONN; + default: + return EIO; + } +-- +2.13.5 + diff --git a/1006-scsi-Introduce-scsi_sense_buf_to_errno.patch b/1006-scsi-Introduce-scsi_sense_buf_to_errno.patch new file mode 100644 index 0000000..4aa499c --- /dev/null +++ b/1006-scsi-Introduce-scsi_sense_buf_to_errno.patch @@ -0,0 +1,68 @@ +From eadabcbc81d44ee0bc0d0d80697cc1142df61178 Mon Sep 17 00:00:00 2001 +From: Fam Zheng +Date: Mon, 21 Aug 2017 22:10:07 +0800 +Subject: [PATCH 06/15] scsi: Introduce scsi_sense_buf_to_errno + +This recognizes the "fixed" and "descriptor" format sense data, extracts +the sense key/asc/ascq fields then converts them to an errno. + +Signed-off-by: Fam Zheng +Message-Id: <20170821141008.19383-4-famz@redhat.com> +Signed-off-by: Paolo Bonzini +--- + include/scsi/scsi.h | 1 + + util/scsi.c | 30 ++++++++++++++++++++++++++++++ + 2 files changed, 31 insertions(+) + +diff --git a/include/scsi/scsi.h b/include/scsi/scsi.h +index f894ace4bf..fe330385d8 100644 +--- a/include/scsi/scsi.h ++++ b/include/scsi/scsi.h +@@ -15,5 +15,6 @@ + #define QEMU_SCSI_H + + int scsi_sense_to_errno(int key, int asc, int ascq); ++int scsi_sense_buf_to_errno(const uint8_t *sense, size_t sense_size); + + #endif +diff --git a/util/scsi.c b/util/scsi.c +index 472eb5bea5..472293d59b 100644 +--- a/util/scsi.c ++++ b/util/scsi.c +@@ -58,3 +58,33 @@ int scsi_sense_to_errno(int key, int asc, int ascq) + return EIO; + } + } ++ ++int scsi_sense_buf_to_errno(const uint8_t *sense, size_t sense_size) ++{ ++ int key, asc, ascq; ++ if (sense_size < 1) { ++ return EIO; ++ } ++ switch (sense[0]) { ++ case 0x70: /* Fixed format sense data. */ ++ if (sense_size < 14) { ++ return EIO; ++ } ++ key = sense[2] & 0xF; ++ asc = sense[12]; ++ ascq = sense[13]; ++ break; ++ case 0x72: /* Descriptor format sense data. */ ++ if (sense_size < 4) { ++ return EIO; ++ } ++ key = sense[1] & 0xF; ++ asc = sense[2]; ++ ascq = sense[3]; ++ break; ++ default: ++ return EIO; ++ break; ++ } ++ return scsi_sense_to_errno(key, asc, ascq); ++} +-- +2.13.5 + diff --git a/1007-scsi-rename-scsi_build_sense-to-scsi_convert_sense.patch b/1007-scsi-rename-scsi_build_sense-to-scsi_convert_sense.patch new file mode 100644 index 0000000..3607e2f --- /dev/null +++ b/1007-scsi-rename-scsi_build_sense-to-scsi_convert_sense.patch @@ -0,0 +1,92 @@ +From ba83805030e07cade8d17b5d1b4bd1d296caff1b Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +Date: Tue, 22 Aug 2017 09:31:36 +0200 +Subject: [PATCH 07/15] scsi: rename scsi_build_sense to scsi_convert_sense +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +After introducing the scsi/ subdirectory, there will be a scsi_build_sense +function that is the same as scsi_req_build_sense but without needing +a SCSIRequest. The existing scsi_build_sense function gets in the way, +remove it. + +Reviewed-by: Philippe Mathieu-Daudé +Signed-off-by: Paolo Bonzini +--- + hw/scsi/scsi-bus.c | 10 +++++----- + hw/scsi/scsi-disk.c | 4 ++-- + include/hw/scsi/scsi.h | 4 ++-- + 3 files changed, 9 insertions(+), 9 deletions(-) + +diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c +index ade31c11f5..fac360e20f 100644 +--- a/hw/scsi/scsi-bus.c ++++ b/hw/scsi/scsi-bus.c +@@ -790,7 +790,7 @@ int scsi_req_get_sense(SCSIRequest *req, uint8_t *buf, int len) + return 0; + } + +- ret = scsi_build_sense(req->sense, req->sense_len, buf, len, true); ++ ret = scsi_convert_sense(req->sense, req->sense_len, buf, len, true); + + /* + * FIXME: clearing unit attention conditions upon autosense should be done +@@ -811,7 +811,7 @@ int scsi_req_get_sense(SCSIRequest *req, uint8_t *buf, int len) + + int scsi_device_get_sense(SCSIDevice *dev, uint8_t *buf, int len, bool fixed) + { +- return scsi_build_sense(dev->sense, dev->sense_len, buf, len, fixed); ++ return scsi_convert_sense(dev->sense, dev->sense_len, buf, len, fixed); + } + + void scsi_req_build_sense(SCSIRequest *req, SCSISense sense) +@@ -1531,12 +1531,12 @@ const struct SCSISense sense_code_SPACE_ALLOC_FAILED = { + }; + + /* +- * scsi_build_sense ++ * scsi_convert_sense + * + * Convert between fixed and descriptor sense buffers + */ +-int scsi_build_sense(uint8_t *in_buf, int in_len, +- uint8_t *buf, int len, bool fixed) ++int scsi_convert_sense(uint8_t *in_buf, int in_len, ++ uint8_t *buf, int len, bool fixed) + { + bool fixed_in; + SCSISense sense; +diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c +index 5f1e5e8070..0a1f4ef0c7 100644 +--- a/hw/scsi/scsi-disk.c ++++ b/hw/scsi/scsi-disk.c +@@ -1978,8 +1978,8 @@ static int32_t scsi_disk_emulate_command(SCSIRequest *req, uint8_t *buf) + break; + case REQUEST_SENSE: + /* Just return "NO SENSE". */ +- buflen = scsi_build_sense(NULL, 0, outbuf, r->buflen, +- (req->cmd.buf[1] & 1) == 0); ++ buflen = scsi_convert_sense(NULL, 0, outbuf, r->buflen, ++ (req->cmd.buf[1] & 1) == 0); + if (buflen < 0) { + goto illegal_request; + } +diff --git a/include/hw/scsi/scsi.h b/include/hw/scsi/scsi.h +index 6b85786dbf..6ef67fb504 100644 +--- a/include/hw/scsi/scsi.h ++++ b/include/hw/scsi/scsi.h +@@ -244,8 +244,8 @@ extern const struct SCSISense sense_code_SPACE_ALLOC_FAILED; + uint32_t scsi_data_cdb_xfer(uint8_t *buf); + uint32_t scsi_cdb_xfer(uint8_t *buf); + int scsi_cdb_length(uint8_t *buf); +-int scsi_build_sense(uint8_t *in_buf, int in_len, +- uint8_t *buf, int len, bool fixed); ++int scsi_convert_sense(uint8_t *in_buf, int in_len, ++ uint8_t *buf, int len, bool fixed); + + SCSIRequest *scsi_req_alloc(const SCSIReqOps *reqops, SCSIDevice *d, + uint32_t tag, uint32_t lun, void *hba_private); +-- +2.13.5 + diff --git a/1008-scsi-move-non-emulation-specific-code-to-scsi.patch b/1008-scsi-move-non-emulation-specific-code-to-scsi.patch new file mode 100644 index 0000000..c1bb72b --- /dev/null +++ b/1008-scsi-move-non-emulation-specific-code-to-scsi.patch @@ -0,0 +1,1434 @@ +From 6949ca76b864b54a0ef1d2aa321c3df4dc102c90 Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +Date: Tue, 22 Aug 2017 07:08:27 +0200 +Subject: [PATCH 08/15] scsi: move non-emulation specific code to scsi/ +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +util/scsi.c includes some SCSI code that is shared by block/iscsi.c and +hw/scsi, but the introduction of the persistent reservation helper +will add many more instances of this. There is also include/block/scsi.h, +which actually is not part of the core block layer. + +The persistent reservation manager will also need a home. A scsi/ +directory provides one for both the aforementioned shared code and +the PR manager code. + +Reviewed-by: Philippe Mathieu-Daudé +Signed-off-by: Paolo Bonzini +--- + MAINTAINERS | 7 + + Makefile.objs | 2 +- + block/iscsi.c | 6 +- + hw/scsi/scsi-bus.c | 397 --------------------------------------- + hw/scsi/scsi-generic.c | 8 - + include/block/scsi.h | 2 - + include/hw/scsi/scsi.h | 94 +--------- + include/scsi/scsi.h | 20 -- + include/scsi/utils.h | 119 ++++++++++++ + scsi/Makefile.objs | 1 + + scsi/utils.c | 492 +++++++++++++++++++++++++++++++++++++++++++++++++ + util/Makefile.objs | 1 - + util/scsi.c | 90 --------- + 13 files changed, 626 insertions(+), 613 deletions(-) + delete mode 100644 include/scsi/scsi.h + create mode 100644 include/scsi/utils.h + create mode 100644 scsi/Makefile.objs + create mode 100644 scsi/utils.c + delete mode 100644 util/scsi.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index 2a4e5036ae..074da8c976 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -1215,6 +1215,13 @@ F: migration/block* + F: include/block/aio.h + T: git git://github.com/stefanha/qemu.git block + ++Block SCSI subsystem ++M: Paolo Bonzini ++L: qemu-block@nongnu.org ++S: Supported ++F: include/scsi/* ++F: scsi/* ++ + Block Jobs + M: Jeff Cody + L: qemu-block@nongnu.org +diff --git a/Makefile.objs b/Makefile.objs +index 24a4ea08b8..f68aa3b60d 100644 +--- a/Makefile.objs ++++ b/Makefile.objs +@@ -11,7 +11,7 @@ chardev-obj-y = chardev/ + + block-obj-y += nbd/ + block-obj-y += block.o blockjob.o +-block-obj-y += block/ ++block-obj-y += block/ scsi/ + block-obj-y += qemu-io-cmds.o + block-obj-$(CONFIG_REPLICATION) += replication.o + +diff --git a/block/iscsi.c b/block/iscsi.c +index 4bed63cd6d..40adc3c493 100644 +--- a/block/iscsi.c ++++ b/block/iscsi.c +@@ -40,10 +40,14 @@ + #include "qmp-commands.h" + #include "qapi/qmp/qstring.h" + #include "crypto/secret.h" +-#include "scsi/scsi.h" ++#include "scsi/utils.h" + ++/* Conflict between scsi/utils.h and libiscsi! :( */ ++#define SCSI_XFER_NONE ISCSI_XFER_NONE + #include + #include ++#undef SCSI_XFER_NONE ++QEMU_BUILD_BUG_ON((int)SCSI_XFER_NONE != (int)ISCSI_XFER_NONE); + + #ifdef __linux__ + #include +diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c +index fac360e20f..42920d5422 100644 +--- a/hw/scsi/scsi-bus.c ++++ b/hw/scsi/scsi-bus.c +@@ -956,36 +956,6 @@ static int ata_passthrough_16_xfer(SCSIDevice *dev, uint8_t *buf) + return xfer * unit; + } + +-uint32_t scsi_data_cdb_xfer(uint8_t *buf) +-{ +- if ((buf[0] >> 5) == 0 && buf[4] == 0) { +- return 256; +- } else { +- return scsi_cdb_xfer(buf); +- } +-} +- +-uint32_t scsi_cdb_xfer(uint8_t *buf) +-{ +- switch (buf[0] >> 5) { +- case 0: +- return buf[4]; +- break; +- case 1: +- case 2: +- return lduw_be_p(&buf[7]); +- break; +- case 4: +- return ldl_be_p(&buf[10]) & 0xffffffffULL; +- break; +- case 5: +- return ldl_be_p(&buf[6]) & 0xffffffffULL; +- break; +- default: +- return -1; +- } +-} +- + static int scsi_req_xfer(SCSICommand *cmd, SCSIDevice *dev, uint8_t *buf) + { + cmd->xfer = scsi_cdb_xfer(buf); +@@ -1298,53 +1268,6 @@ static void scsi_cmd_xfer_mode(SCSICommand *cmd) + } + } + +-static uint64_t scsi_cmd_lba(SCSICommand *cmd) +-{ +- uint8_t *buf = cmd->buf; +- uint64_t lba; +- +- switch (buf[0] >> 5) { +- case 0: +- lba = ldl_be_p(&buf[0]) & 0x1fffff; +- break; +- case 1: +- case 2: +- case 5: +- lba = ldl_be_p(&buf[2]) & 0xffffffffULL; +- break; +- case 4: +- lba = ldq_be_p(&buf[2]); +- break; +- default: +- lba = -1; +- +- } +- return lba; +-} +- +-int scsi_cdb_length(uint8_t *buf) { +- int cdb_len; +- +- switch (buf[0] >> 5) { +- case 0: +- cdb_len = 6; +- break; +- case 1: +- case 2: +- cdb_len = 10; +- break; +- case 4: +- cdb_len = 16; +- break; +- case 5: +- cdb_len = 12; +- break; +- default: +- cdb_len = -1; +- } +- return cdb_len; +-} +- + int scsi_req_parse_cdb(SCSIDevice *dev, SCSICommand *cmd, uint8_t *buf) + { + int rc; +@@ -1391,326 +1314,6 @@ void scsi_device_report_change(SCSIDevice *dev, SCSISense sense) + } + } + +-/* +- * Predefined sense codes +- */ +- +-/* No sense data available */ +-const struct SCSISense sense_code_NO_SENSE = { +- .key = NO_SENSE , .asc = 0x00 , .ascq = 0x00 +-}; +- +-/* LUN not ready, Manual intervention required */ +-const struct SCSISense sense_code_LUN_NOT_READY = { +- .key = NOT_READY, .asc = 0x04, .ascq = 0x03 +-}; +- +-/* LUN not ready, Medium not present */ +-const struct SCSISense sense_code_NO_MEDIUM = { +- .key = NOT_READY, .asc = 0x3a, .ascq = 0x00 +-}; +- +-/* LUN not ready, medium removal prevented */ +-const struct SCSISense sense_code_NOT_READY_REMOVAL_PREVENTED = { +- .key = NOT_READY, .asc = 0x53, .ascq = 0x02 +-}; +- +-/* Hardware error, internal target failure */ +-const struct SCSISense sense_code_TARGET_FAILURE = { +- .key = HARDWARE_ERROR, .asc = 0x44, .ascq = 0x00 +-}; +- +-/* Illegal request, invalid command operation code */ +-const struct SCSISense sense_code_INVALID_OPCODE = { +- .key = ILLEGAL_REQUEST, .asc = 0x20, .ascq = 0x00 +-}; +- +-/* Illegal request, LBA out of range */ +-const struct SCSISense sense_code_LBA_OUT_OF_RANGE = { +- .key = ILLEGAL_REQUEST, .asc = 0x21, .ascq = 0x00 +-}; +- +-/* Illegal request, Invalid field in CDB */ +-const struct SCSISense sense_code_INVALID_FIELD = { +- .key = ILLEGAL_REQUEST, .asc = 0x24, .ascq = 0x00 +-}; +- +-/* Illegal request, Invalid field in parameter list */ +-const struct SCSISense sense_code_INVALID_PARAM = { +- .key = ILLEGAL_REQUEST, .asc = 0x26, .ascq = 0x00 +-}; +- +-/* Illegal request, Parameter list length error */ +-const struct SCSISense sense_code_INVALID_PARAM_LEN = { +- .key = ILLEGAL_REQUEST, .asc = 0x1a, .ascq = 0x00 +-}; +- +-/* Illegal request, LUN not supported */ +-const struct SCSISense sense_code_LUN_NOT_SUPPORTED = { +- .key = ILLEGAL_REQUEST, .asc = 0x25, .ascq = 0x00 +-}; +- +-/* Illegal request, Saving parameters not supported */ +-const struct SCSISense sense_code_SAVING_PARAMS_NOT_SUPPORTED = { +- .key = ILLEGAL_REQUEST, .asc = 0x39, .ascq = 0x00 +-}; +- +-/* Illegal request, Incompatible medium installed */ +-const struct SCSISense sense_code_INCOMPATIBLE_FORMAT = { +- .key = ILLEGAL_REQUEST, .asc = 0x30, .ascq = 0x00 +-}; +- +-/* Illegal request, medium removal prevented */ +-const struct SCSISense sense_code_ILLEGAL_REQ_REMOVAL_PREVENTED = { +- .key = ILLEGAL_REQUEST, .asc = 0x53, .ascq = 0x02 +-}; +- +-/* Illegal request, Invalid Transfer Tag */ +-const struct SCSISense sense_code_INVALID_TAG = { +- .key = ILLEGAL_REQUEST, .asc = 0x4b, .ascq = 0x01 +-}; +- +-/* Command aborted, I/O process terminated */ +-const struct SCSISense sense_code_IO_ERROR = { +- .key = ABORTED_COMMAND, .asc = 0x00, .ascq = 0x06 +-}; +- +-/* Command aborted, I_T Nexus loss occurred */ +-const struct SCSISense sense_code_I_T_NEXUS_LOSS = { +- .key = ABORTED_COMMAND, .asc = 0x29, .ascq = 0x07 +-}; +- +-/* Command aborted, Logical Unit failure */ +-const struct SCSISense sense_code_LUN_FAILURE = { +- .key = ABORTED_COMMAND, .asc = 0x3e, .ascq = 0x01 +-}; +- +-/* Command aborted, Overlapped Commands Attempted */ +-const struct SCSISense sense_code_OVERLAPPED_COMMANDS = { +- .key = ABORTED_COMMAND, .asc = 0x4e, .ascq = 0x00 +-}; +- +-/* Unit attention, Capacity data has changed */ +-const struct SCSISense sense_code_CAPACITY_CHANGED = { +- .key = UNIT_ATTENTION, .asc = 0x2a, .ascq = 0x09 +-}; +- +-/* Unit attention, Power on, reset or bus device reset occurred */ +-const struct SCSISense sense_code_RESET = { +- .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x00 +-}; +- +-/* Unit attention, No medium */ +-const struct SCSISense sense_code_UNIT_ATTENTION_NO_MEDIUM = { +- .key = UNIT_ATTENTION, .asc = 0x3a, .ascq = 0x00 +-}; +- +-/* Unit attention, Medium may have changed */ +-const struct SCSISense sense_code_MEDIUM_CHANGED = { +- .key = UNIT_ATTENTION, .asc = 0x28, .ascq = 0x00 +-}; +- +-/* Unit attention, Reported LUNs data has changed */ +-const struct SCSISense sense_code_REPORTED_LUNS_CHANGED = { +- .key = UNIT_ATTENTION, .asc = 0x3f, .ascq = 0x0e +-}; +- +-/* Unit attention, Device internal reset */ +-const struct SCSISense sense_code_DEVICE_INTERNAL_RESET = { +- .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x04 +-}; +- +-/* Data Protection, Write Protected */ +-const struct SCSISense sense_code_WRITE_PROTECTED = { +- .key = DATA_PROTECT, .asc = 0x27, .ascq = 0x00 +-}; +- +-/* Data Protection, Space Allocation Failed Write Protect */ +-const struct SCSISense sense_code_SPACE_ALLOC_FAILED = { +- .key = DATA_PROTECT, .asc = 0x27, .ascq = 0x07 +-}; +- +-/* +- * scsi_convert_sense +- * +- * Convert between fixed and descriptor sense buffers +- */ +-int scsi_convert_sense(uint8_t *in_buf, int in_len, +- uint8_t *buf, int len, bool fixed) +-{ +- bool fixed_in; +- SCSISense sense; +- if (!fixed && len < 8) { +- return 0; +- } +- +- if (in_len == 0) { +- sense.key = NO_SENSE; +- sense.asc = 0; +- sense.ascq = 0; +- } else { +- fixed_in = (in_buf[0] & 2) == 0; +- +- if (fixed == fixed_in) { +- memcpy(buf, in_buf, MIN(len, in_len)); +- return MIN(len, in_len); +- } +- +- if (fixed_in) { +- sense.key = in_buf[2]; +- sense.asc = in_buf[12]; +- sense.ascq = in_buf[13]; +- } else { +- sense.key = in_buf[1]; +- sense.asc = in_buf[2]; +- sense.ascq = in_buf[3]; +- } +- } +- +- memset(buf, 0, len); +- if (fixed) { +- /* Return fixed format sense buffer */ +- buf[0] = 0x70; +- buf[2] = sense.key; +- buf[7] = 10; +- buf[12] = sense.asc; +- buf[13] = sense.ascq; +- return MIN(len, SCSI_SENSE_LEN); +- } else { +- /* Return descriptor format sense buffer */ +- buf[0] = 0x72; +- buf[1] = sense.key; +- buf[2] = sense.asc; +- buf[3] = sense.ascq; +- return 8; +- } +-} +- +-const char *scsi_command_name(uint8_t cmd) +-{ +- static const char *names[] = { +- [ TEST_UNIT_READY ] = "TEST_UNIT_READY", +- [ REWIND ] = "REWIND", +- [ REQUEST_SENSE ] = "REQUEST_SENSE", +- [ FORMAT_UNIT ] = "FORMAT_UNIT", +- [ READ_BLOCK_LIMITS ] = "READ_BLOCK_LIMITS", +- [ REASSIGN_BLOCKS ] = "REASSIGN_BLOCKS/INITIALIZE ELEMENT STATUS", +- /* LOAD_UNLOAD and INITIALIZE_ELEMENT_STATUS use the same operation code */ +- [ READ_6 ] = "READ_6", +- [ WRITE_6 ] = "WRITE_6", +- [ SET_CAPACITY ] = "SET_CAPACITY", +- [ READ_REVERSE ] = "READ_REVERSE", +- [ WRITE_FILEMARKS ] = "WRITE_FILEMARKS", +- [ SPACE ] = "SPACE", +- [ INQUIRY ] = "INQUIRY", +- [ RECOVER_BUFFERED_DATA ] = "RECOVER_BUFFERED_DATA", +- [ MAINTENANCE_IN ] = "MAINTENANCE_IN", +- [ MAINTENANCE_OUT ] = "MAINTENANCE_OUT", +- [ MODE_SELECT ] = "MODE_SELECT", +- [ RESERVE ] = "RESERVE", +- [ RELEASE ] = "RELEASE", +- [ COPY ] = "COPY", +- [ ERASE ] = "ERASE", +- [ MODE_SENSE ] = "MODE_SENSE", +- [ START_STOP ] = "START_STOP/LOAD_UNLOAD", +- /* LOAD_UNLOAD and START_STOP use the same operation code */ +- [ RECEIVE_DIAGNOSTIC ] = "RECEIVE_DIAGNOSTIC", +- [ SEND_DIAGNOSTIC ] = "SEND_DIAGNOSTIC", +- [ ALLOW_MEDIUM_REMOVAL ] = "ALLOW_MEDIUM_REMOVAL", +- [ READ_CAPACITY_10 ] = "READ_CAPACITY_10", +- [ READ_10 ] = "READ_10", +- [ WRITE_10 ] = "WRITE_10", +- [ SEEK_10 ] = "SEEK_10/POSITION_TO_ELEMENT", +- /* SEEK_10 and POSITION_TO_ELEMENT use the same operation code */ +- [ WRITE_VERIFY_10 ] = "WRITE_VERIFY_10", +- [ VERIFY_10 ] = "VERIFY_10", +- [ SEARCH_HIGH ] = "SEARCH_HIGH", +- [ SEARCH_EQUAL ] = "SEARCH_EQUAL", +- [ SEARCH_LOW ] = "SEARCH_LOW", +- [ SET_LIMITS ] = "SET_LIMITS", +- [ PRE_FETCH ] = "PRE_FETCH/READ_POSITION", +- /* READ_POSITION and PRE_FETCH use the same operation code */ +- [ SYNCHRONIZE_CACHE ] = "SYNCHRONIZE_CACHE", +- [ LOCK_UNLOCK_CACHE ] = "LOCK_UNLOCK_CACHE", +- [ READ_DEFECT_DATA ] = "READ_DEFECT_DATA/INITIALIZE_ELEMENT_STATUS_WITH_RANGE", +- /* READ_DEFECT_DATA and INITIALIZE_ELEMENT_STATUS_WITH_RANGE use the same operation code */ +- [ MEDIUM_SCAN ] = "MEDIUM_SCAN", +- [ COMPARE ] = "COMPARE", +- [ COPY_VERIFY ] = "COPY_VERIFY", +- [ WRITE_BUFFER ] = "WRITE_BUFFER", +- [ READ_BUFFER ] = "READ_BUFFER", +- [ UPDATE_BLOCK ] = "UPDATE_BLOCK", +- [ READ_LONG_10 ] = "READ_LONG_10", +- [ WRITE_LONG_10 ] = "WRITE_LONG_10", +- [ CHANGE_DEFINITION ] = "CHANGE_DEFINITION", +- [ WRITE_SAME_10 ] = "WRITE_SAME_10", +- [ UNMAP ] = "UNMAP", +- [ READ_TOC ] = "READ_TOC", +- [ REPORT_DENSITY_SUPPORT ] = "REPORT_DENSITY_SUPPORT", +- [ SANITIZE ] = "SANITIZE", +- [ GET_CONFIGURATION ] = "GET_CONFIGURATION", +- [ LOG_SELECT ] = "LOG_SELECT", +- [ LOG_SENSE ] = "LOG_SENSE", +- [ MODE_SELECT_10 ] = "MODE_SELECT_10", +- [ RESERVE_10 ] = "RESERVE_10", +- [ RELEASE_10 ] = "RELEASE_10", +- [ MODE_SENSE_10 ] = "MODE_SENSE_10", +- [ PERSISTENT_RESERVE_IN ] = "PERSISTENT_RESERVE_IN", +- [ PERSISTENT_RESERVE_OUT ] = "PERSISTENT_RESERVE_OUT", +- [ WRITE_FILEMARKS_16 ] = "WRITE_FILEMARKS_16", +- [ EXTENDED_COPY ] = "EXTENDED_COPY", +- [ ATA_PASSTHROUGH_16 ] = "ATA_PASSTHROUGH_16", +- [ ACCESS_CONTROL_IN ] = "ACCESS_CONTROL_IN", +- [ ACCESS_CONTROL_OUT ] = "ACCESS_CONTROL_OUT", +- [ READ_16 ] = "READ_16", +- [ COMPARE_AND_WRITE ] = "COMPARE_AND_WRITE", +- [ WRITE_16 ] = "WRITE_16", +- [ WRITE_VERIFY_16 ] = "WRITE_VERIFY_16", +- [ VERIFY_16 ] = "VERIFY_16", +- [ PRE_FETCH_16 ] = "PRE_FETCH_16", +- [ SYNCHRONIZE_CACHE_16 ] = "SPACE_16/SYNCHRONIZE_CACHE_16", +- /* SPACE_16 and SYNCHRONIZE_CACHE_16 use the same operation code */ +- [ LOCATE_16 ] = "LOCATE_16", +- [ WRITE_SAME_16 ] = "ERASE_16/WRITE_SAME_16", +- /* ERASE_16 and WRITE_SAME_16 use the same operation code */ +- [ SERVICE_ACTION_IN_16 ] = "SERVICE_ACTION_IN_16", +- [ WRITE_LONG_16 ] = "WRITE_LONG_16", +- [ REPORT_LUNS ] = "REPORT_LUNS", +- [ ATA_PASSTHROUGH_12 ] = "BLANK/ATA_PASSTHROUGH_12", +- [ MOVE_MEDIUM ] = "MOVE_MEDIUM", +- [ EXCHANGE_MEDIUM ] = "EXCHANGE MEDIUM", +- [ READ_12 ] = "READ_12", +- [ WRITE_12 ] = "WRITE_12", +- [ ERASE_12 ] = "ERASE_12/GET_PERFORMANCE", +- /* ERASE_12 and GET_PERFORMANCE use the same operation code */ +- [ SERVICE_ACTION_IN_12 ] = "SERVICE_ACTION_IN_12", +- [ WRITE_VERIFY_12 ] = "WRITE_VERIFY_12", +- [ VERIFY_12 ] = "VERIFY_12", +- [ SEARCH_HIGH_12 ] = "SEARCH_HIGH_12", +- [ SEARCH_EQUAL_12 ] = "SEARCH_EQUAL_12", +- [ SEARCH_LOW_12 ] = "SEARCH_LOW_12", +- [ READ_ELEMENT_STATUS ] = "READ_ELEMENT_STATUS", +- [ SEND_VOLUME_TAG ] = "SEND_VOLUME_TAG/SET_STREAMING", +- /* SEND_VOLUME_TAG and SET_STREAMING use the same operation code */ +- [ READ_CD ] = "READ_CD", +- [ READ_DEFECT_DATA_12 ] = "READ_DEFECT_DATA_12", +- [ READ_DVD_STRUCTURE ] = "READ_DVD_STRUCTURE", +- [ RESERVE_TRACK ] = "RESERVE_TRACK", +- [ SEND_CUE_SHEET ] = "SEND_CUE_SHEET", +- [ SEND_DVD_STRUCTURE ] = "SEND_DVD_STRUCTURE", +- [ SET_CD_SPEED ] = "SET_CD_SPEED", +- [ SET_READ_AHEAD ] = "SET_READ_AHEAD", +- [ ALLOW_OVERWRITE ] = "ALLOW_OVERWRITE", +- [ MECHANISM_STATUS ] = "MECHANISM_STATUS", +- [ GET_EVENT_STATUS_NOTIFICATION ] = "GET_EVENT_STATUS_NOTIFICATION", +- [ READ_DISC_INFORMATION ] = "READ_DISC_INFORMATION", +- }; +- +- if (cmd >= ARRAY_SIZE(names) || names[cmd] == NULL) +- return "*UNKNOWN*"; +- return names[cmd]; +-} +- + SCSIRequest *scsi_req_ref(SCSIRequest *req) + { + assert(req->refcount > 0); +diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c +index 7e1cbab77e..7a8f500934 100644 +--- a/hw/scsi/scsi-generic.c ++++ b/hw/scsi/scsi-generic.c +@@ -36,14 +36,6 @@ do { fprintf(stderr, "scsi-generic: " fmt , ## __VA_ARGS__); } while (0) + #include + #include "block/scsi.h" + +-#define SG_ERR_DRIVER_TIMEOUT 0x06 +-#define SG_ERR_DRIVER_SENSE 0x08 +- +-#define SG_ERR_DID_OK 0x00 +-#define SG_ERR_DID_NO_CONNECT 0x01 +-#define SG_ERR_DID_BUS_BUSY 0x02 +-#define SG_ERR_DID_TIME_OUT 0x03 +- + #ifndef MAX_UINT + #define MAX_UINT ((unsigned int)-1) + #endif +diff --git a/include/block/scsi.h b/include/block/scsi.h +index cdf0a58a07..a141dd71f8 100644 +--- a/include/block/scsi.h ++++ b/include/block/scsi.h +@@ -150,8 +150,6 @@ + #define READ_CD 0xbe + #define SEND_DVD_STRUCTURE 0xbf + +-const char *scsi_command_name(uint8_t cmd); +- + /* + * SERVICE ACTION IN subcodes + */ +diff --git a/include/hw/scsi/scsi.h b/include/hw/scsi/scsi.h +index 6ef67fb504..23a8ee6a7d 100644 +--- a/include/hw/scsi/scsi.h ++++ b/include/hw/scsi/scsi.h +@@ -4,45 +4,20 @@ + #include "hw/qdev.h" + #include "hw/block/block.h" + #include "sysemu/sysemu.h" ++#include "scsi/utils.h" + #include "qemu/notify.h" + + #define MAX_SCSI_DEVS 255 + +-#define SCSI_CMD_BUF_SIZE 16 +-#define SCSI_SENSE_LEN 18 +-#define SCSI_SENSE_LEN_SCANNER 32 +-#define SCSI_INQUIRY_LEN 36 +- + typedef struct SCSIBus SCSIBus; + typedef struct SCSIBusInfo SCSIBusInfo; +-typedef struct SCSICommand SCSICommand; + typedef struct SCSIDevice SCSIDevice; + typedef struct SCSIRequest SCSIRequest; + typedef struct SCSIReqOps SCSIReqOps; + +-enum SCSIXferMode { +- SCSI_XFER_NONE, /* TEST_UNIT_READY, ... */ +- SCSI_XFER_FROM_DEV, /* READ, INQUIRY, MODE_SENSE, ... */ +- SCSI_XFER_TO_DEV, /* WRITE, MODE_SELECT, ... */ +-}; +- +-typedef struct SCSISense { +- uint8_t key; +- uint8_t asc; +- uint8_t ascq; +-} SCSISense; +- + #define SCSI_SENSE_BUF_SIZE_OLD 96 + #define SCSI_SENSE_BUF_SIZE 252 + +-struct SCSICommand { +- uint8_t buf[SCSI_CMD_BUF_SIZE]; +- int len; +- size_t xfer; +- uint64_t lba; +- enum SCSIXferMode mode; +-}; +- + struct SCSIRequest { + SCSIBus *bus; + SCSIDevice *dev; +@@ -180,73 +155,6 @@ SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, BlockBackend *blk, + void scsi_bus_legacy_handle_cmdline(SCSIBus *bus, bool deprecated); + void scsi_legacy_handle_cmdline(void); + +-/* +- * Predefined sense codes +- */ +- +-/* No sense data available */ +-extern const struct SCSISense sense_code_NO_SENSE; +-/* LUN not ready, Manual intervention required */ +-extern const struct SCSISense sense_code_LUN_NOT_READY; +-/* LUN not ready, Medium not present */ +-extern const struct SCSISense sense_code_NO_MEDIUM; +-/* LUN not ready, medium removal prevented */ +-extern const struct SCSISense sense_code_NOT_READY_REMOVAL_PREVENTED; +-/* Hardware error, internal target failure */ +-extern const struct SCSISense sense_code_TARGET_FAILURE; +-/* Illegal request, invalid command operation code */ +-extern const struct SCSISense sense_code_INVALID_OPCODE; +-/* Illegal request, LBA out of range */ +-extern const struct SCSISense sense_code_LBA_OUT_OF_RANGE; +-/* Illegal request, Invalid field in CDB */ +-extern const struct SCSISense sense_code_INVALID_FIELD; +-/* Illegal request, Invalid field in parameter list */ +-extern const struct SCSISense sense_code_INVALID_PARAM; +-/* Illegal request, Parameter list length error */ +-extern const struct SCSISense sense_code_INVALID_PARAM_LEN; +-/* Illegal request, LUN not supported */ +-extern const struct SCSISense sense_code_LUN_NOT_SUPPORTED; +-/* Illegal request, Saving parameters not supported */ +-extern const struct SCSISense sense_code_SAVING_PARAMS_NOT_SUPPORTED; +-/* Illegal request, Incompatible format */ +-extern const struct SCSISense sense_code_INCOMPATIBLE_FORMAT; +-/* Illegal request, medium removal prevented */ +-extern const struct SCSISense sense_code_ILLEGAL_REQ_REMOVAL_PREVENTED; +-/* Illegal request, Invalid Transfer Tag */ +-extern const struct SCSISense sense_code_INVALID_TAG; +-/* Command aborted, I/O process terminated */ +-extern const struct SCSISense sense_code_IO_ERROR; +-/* Command aborted, I_T Nexus loss occurred */ +-extern const struct SCSISense sense_code_I_T_NEXUS_LOSS; +-/* Command aborted, Logical Unit failure */ +-extern const struct SCSISense sense_code_LUN_FAILURE; +-/* Command aborted, Overlapped Commands Attempted */ +-extern const struct SCSISense sense_code_OVERLAPPED_COMMANDS; +-/* LUN not ready, Capacity data has changed */ +-extern const struct SCSISense sense_code_CAPACITY_CHANGED; +-/* LUN not ready, Medium not present */ +-extern const struct SCSISense sense_code_UNIT_ATTENTION_NO_MEDIUM; +-/* Unit attention, Power on, reset or bus device reset occurred */ +-extern const struct SCSISense sense_code_RESET; +-/* Unit attention, Medium may have changed*/ +-extern const struct SCSISense sense_code_MEDIUM_CHANGED; +-/* Unit attention, Reported LUNs data has changed */ +-extern const struct SCSISense sense_code_REPORTED_LUNS_CHANGED; +-/* Unit attention, Device internal reset */ +-extern const struct SCSISense sense_code_DEVICE_INTERNAL_RESET; +-/* Data Protection, Write Protected */ +-extern const struct SCSISense sense_code_WRITE_PROTECTED; +-/* Data Protection, Space Allocation Failed Write Protect */ +-extern const struct SCSISense sense_code_SPACE_ALLOC_FAILED; +- +-#define SENSE_CODE(x) sense_code_ ## x +- +-uint32_t scsi_data_cdb_xfer(uint8_t *buf); +-uint32_t scsi_cdb_xfer(uint8_t *buf); +-int scsi_cdb_length(uint8_t *buf); +-int scsi_convert_sense(uint8_t *in_buf, int in_len, +- uint8_t *buf, int len, bool fixed); +- + SCSIRequest *scsi_req_alloc(const SCSIReqOps *reqops, SCSIDevice *d, + uint32_t tag, uint32_t lun, void *hba_private); + SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun, +diff --git a/include/scsi/scsi.h b/include/scsi/scsi.h +deleted file mode 100644 +index fe330385d8..0000000000 +--- a/include/scsi/scsi.h ++++ /dev/null +@@ -1,20 +0,0 @@ +-/* +- * SCSI helpers +- * +- * Copyright 2017 Red Hat, Inc. +- * +- * Authors: +- * Fam Zheng +- * +- * This program is free software; you can redistribute it and/or modify it +- * under the terms of the GNU General Public License as published by the Free +- * Software Foundation; either version 2 of the License, or (at your option) +- * any later version. +- */ +-#ifndef QEMU_SCSI_H +-#define QEMU_SCSI_H +- +-int scsi_sense_to_errno(int key, int asc, int ascq); +-int scsi_sense_buf_to_errno(const uint8_t *sense, size_t sense_size); +- +-#endif +diff --git a/include/scsi/utils.h b/include/scsi/utils.h +new file mode 100644 +index 0000000000..90bf4dce6e +--- /dev/null ++++ b/include/scsi/utils.h +@@ -0,0 +1,119 @@ ++#ifndef SCSI_UTILS_H ++#define SCSI_UTILS_H 1 ++ ++#ifdef CONFIG_LINUX ++#include ++#endif ++ ++#define SCSI_CMD_BUF_SIZE 16 ++#define SCSI_SENSE_LEN 18 ++#define SCSI_SENSE_LEN_SCANNER 32 ++#define SCSI_INQUIRY_LEN 36 ++ ++enum SCSIXferMode { ++ SCSI_XFER_NONE, /* TEST_UNIT_READY, ... */ ++ SCSI_XFER_FROM_DEV, /* READ, INQUIRY, MODE_SENSE, ... */ ++ SCSI_XFER_TO_DEV, /* WRITE, MODE_SELECT, ... */ ++}; ++ ++typedef struct SCSICommand { ++ uint8_t buf[SCSI_CMD_BUF_SIZE]; ++ int len; ++ size_t xfer; ++ uint64_t lba; ++ enum SCSIXferMode mode; ++} SCSICommand; ++ ++typedef struct SCSISense { ++ uint8_t key; ++ uint8_t asc; ++ uint8_t ascq; ++} SCSISense; ++ ++/* ++ * Predefined sense codes ++ */ ++ ++/* No sense data available */ ++extern const struct SCSISense sense_code_NO_SENSE; ++/* LUN not ready, Manual intervention required */ ++extern const struct SCSISense sense_code_LUN_NOT_READY; ++/* LUN not ready, Medium not present */ ++extern const struct SCSISense sense_code_NO_MEDIUM; ++/* LUN not ready, medium removal prevented */ ++extern const struct SCSISense sense_code_NOT_READY_REMOVAL_PREVENTED; ++/* Hardware error, internal target failure */ ++extern const struct SCSISense sense_code_TARGET_FAILURE; ++/* Illegal request, invalid command operation code */ ++extern const struct SCSISense sense_code_INVALID_OPCODE; ++/* Illegal request, LBA out of range */ ++extern const struct SCSISense sense_code_LBA_OUT_OF_RANGE; ++/* Illegal request, Invalid field in CDB */ ++extern const struct SCSISense sense_code_INVALID_FIELD; ++/* Illegal request, Invalid field in parameter list */ ++extern const struct SCSISense sense_code_INVALID_PARAM; ++/* Illegal request, Parameter list length error */ ++extern const struct SCSISense sense_code_INVALID_PARAM_LEN; ++/* Illegal request, LUN not supported */ ++extern const struct SCSISense sense_code_LUN_NOT_SUPPORTED; ++/* Illegal request, Saving parameters not supported */ ++extern const struct SCSISense sense_code_SAVING_PARAMS_NOT_SUPPORTED; ++/* Illegal request, Incompatible format */ ++extern const struct SCSISense sense_code_INCOMPATIBLE_FORMAT; ++/* Illegal request, medium removal prevented */ ++extern const struct SCSISense sense_code_ILLEGAL_REQ_REMOVAL_PREVENTED; ++/* Illegal request, Invalid Transfer Tag */ ++extern const struct SCSISense sense_code_INVALID_TAG; ++/* Command aborted, I/O process terminated */ ++extern const struct SCSISense sense_code_IO_ERROR; ++/* Command aborted, I_T Nexus loss occurred */ ++extern const struct SCSISense sense_code_I_T_NEXUS_LOSS; ++/* Command aborted, Logical Unit failure */ ++extern const struct SCSISense sense_code_LUN_FAILURE; ++/* Command aborted, Overlapped Commands Attempted */ ++extern const struct SCSISense sense_code_OVERLAPPED_COMMANDS; ++/* LUN not ready, Capacity data has changed */ ++extern const struct SCSISense sense_code_CAPACITY_CHANGED; ++/* LUN not ready, Medium not present */ ++extern const struct SCSISense sense_code_UNIT_ATTENTION_NO_MEDIUM; ++/* Unit attention, Power on, reset or bus device reset occurred */ ++extern const struct SCSISense sense_code_RESET; ++/* Unit attention, Medium may have changed*/ ++extern const struct SCSISense sense_code_MEDIUM_CHANGED; ++/* Unit attention, Reported LUNs data has changed */ ++extern const struct SCSISense sense_code_REPORTED_LUNS_CHANGED; ++/* Unit attention, Device internal reset */ ++extern const struct SCSISense sense_code_DEVICE_INTERNAL_RESET; ++/* Data Protection, Write Protected */ ++extern const struct SCSISense sense_code_WRITE_PROTECTED; ++/* Data Protection, Space Allocation Failed Write Protect */ ++extern const struct SCSISense sense_code_SPACE_ALLOC_FAILED; ++ ++#define SENSE_CODE(x) sense_code_ ## x ++ ++int scsi_sense_to_errno(int key, int asc, int ascq); ++int scsi_sense_buf_to_errno(const uint8_t *sense, size_t sense_size); ++ ++int scsi_convert_sense(uint8_t *in_buf, int in_len, ++ uint8_t *buf, int len, bool fixed); ++const char *scsi_command_name(uint8_t cmd); ++ ++uint64_t scsi_cmd_lba(SCSICommand *cmd); ++uint32_t scsi_data_cdb_xfer(uint8_t *buf); ++uint32_t scsi_cdb_xfer(uint8_t *buf); ++int scsi_cdb_length(uint8_t *buf); ++ ++/* Linux SG_IO interface. */ ++#ifdef CONFIG_LINUX ++#define SG_ERR_DRIVER_TIMEOUT 0x06 ++#define SG_ERR_DRIVER_SENSE 0x08 ++ ++#define SG_ERR_DID_OK 0x00 ++#define SG_ERR_DID_NO_CONNECT 0x01 ++#define SG_ERR_DID_BUS_BUSY 0x02 ++#define SG_ERR_DID_TIME_OUT 0x03 ++ ++#define SG_ERR_DRIVER_SENSE 0x08 ++#endif ++ ++#endif +diff --git a/scsi/Makefile.objs b/scsi/Makefile.objs +new file mode 100644 +index 0000000000..31b82a5a36 +--- /dev/null ++++ b/scsi/Makefile.objs +@@ -0,0 +1 @@ ++block-obj-y += utils.o +diff --git a/scsi/utils.c b/scsi/utils.c +new file mode 100644 +index 0000000000..2327e06da0 +--- /dev/null ++++ b/scsi/utils.c +@@ -0,0 +1,492 @@ ++/* ++ * SCSI helpers ++ * ++ * Copyright 2017 Red Hat, Inc. ++ * ++ * Authors: ++ * Fam Zheng ++ * Paolo Bonzini ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms of the GNU General Public License as published by the Free ++ * Software Foundation; either version 2 of the License, or (at your option) ++ * any later version. ++ */ ++ ++#include "qemu/osdep.h" ++#include "block/scsi.h" ++#include "scsi/utils.h" ++#include "qemu/bswap.h" ++ ++uint32_t scsi_data_cdb_xfer(uint8_t *buf) ++{ ++ if ((buf[0] >> 5) == 0 && buf[4] == 0) { ++ return 256; ++ } else { ++ return scsi_cdb_xfer(buf); ++ } ++} ++ ++uint32_t scsi_cdb_xfer(uint8_t *buf) ++{ ++ switch (buf[0] >> 5) { ++ case 0: ++ return buf[4]; ++ break; ++ case 1: ++ case 2: ++ return lduw_be_p(&buf[7]); ++ break; ++ case 4: ++ return ldl_be_p(&buf[10]) & 0xffffffffULL; ++ break; ++ case 5: ++ return ldl_be_p(&buf[6]) & 0xffffffffULL; ++ break; ++ default: ++ return -1; ++ } ++} ++ ++uint64_t scsi_cmd_lba(SCSICommand *cmd) ++{ ++ uint8_t *buf = cmd->buf; ++ uint64_t lba; ++ ++ switch (buf[0] >> 5) { ++ case 0: ++ lba = ldl_be_p(&buf[0]) & 0x1fffff; ++ break; ++ case 1: ++ case 2: ++ case 5: ++ lba = ldl_be_p(&buf[2]) & 0xffffffffULL; ++ break; ++ case 4: ++ lba = ldq_be_p(&buf[2]); ++ break; ++ default: ++ lba = -1; ++ ++ } ++ return lba; ++} ++ ++int scsi_cdb_length(uint8_t *buf) ++{ ++ int cdb_len; ++ ++ switch (buf[0] >> 5) { ++ case 0: ++ cdb_len = 6; ++ break; ++ case 1: ++ case 2: ++ cdb_len = 10; ++ break; ++ case 4: ++ cdb_len = 16; ++ break; ++ case 5: ++ cdb_len = 12; ++ break; ++ default: ++ cdb_len = -1; ++ } ++ return cdb_len; ++} ++ ++/* ++ * Predefined sense codes ++ */ ++ ++/* No sense data available */ ++const struct SCSISense sense_code_NO_SENSE = { ++ .key = NO_SENSE , .asc = 0x00 , .ascq = 0x00 ++}; ++ ++/* LUN not ready, Manual intervention required */ ++const struct SCSISense sense_code_LUN_NOT_READY = { ++ .key = NOT_READY, .asc = 0x04, .ascq = 0x03 ++}; ++ ++/* LUN not ready, Medium not present */ ++const struct SCSISense sense_code_NO_MEDIUM = { ++ .key = NOT_READY, .asc = 0x3a, .ascq = 0x00 ++}; ++ ++/* LUN not ready, medium removal prevented */ ++const struct SCSISense sense_code_NOT_READY_REMOVAL_PREVENTED = { ++ .key = NOT_READY, .asc = 0x53, .ascq = 0x02 ++}; ++ ++/* Hardware error, internal target failure */ ++const struct SCSISense sense_code_TARGET_FAILURE = { ++ .key = HARDWARE_ERROR, .asc = 0x44, .ascq = 0x00 ++}; ++ ++/* Illegal request, invalid command operation code */ ++const struct SCSISense sense_code_INVALID_OPCODE = { ++ .key = ILLEGAL_REQUEST, .asc = 0x20, .ascq = 0x00 ++}; ++ ++/* Illegal request, LBA out of range */ ++const struct SCSISense sense_code_LBA_OUT_OF_RANGE = { ++ .key = ILLEGAL_REQUEST, .asc = 0x21, .ascq = 0x00 ++}; ++ ++/* Illegal request, Invalid field in CDB */ ++const struct SCSISense sense_code_INVALID_FIELD = { ++ .key = ILLEGAL_REQUEST, .asc = 0x24, .ascq = 0x00 ++}; ++ ++/* Illegal request, Invalid field in parameter list */ ++const struct SCSISense sense_code_INVALID_PARAM = { ++ .key = ILLEGAL_REQUEST, .asc = 0x26, .ascq = 0x00 ++}; ++ ++/* Illegal request, Parameter list length error */ ++const struct SCSISense sense_code_INVALID_PARAM_LEN = { ++ .key = ILLEGAL_REQUEST, .asc = 0x1a, .ascq = 0x00 ++}; ++ ++/* Illegal request, LUN not supported */ ++const struct SCSISense sense_code_LUN_NOT_SUPPORTED = { ++ .key = ILLEGAL_REQUEST, .asc = 0x25, .ascq = 0x00 ++}; ++ ++/* Illegal request, Saving parameters not supported */ ++const struct SCSISense sense_code_SAVING_PARAMS_NOT_SUPPORTED = { ++ .key = ILLEGAL_REQUEST, .asc = 0x39, .ascq = 0x00 ++}; ++ ++/* Illegal request, Incompatible medium installed */ ++const struct SCSISense sense_code_INCOMPATIBLE_FORMAT = { ++ .key = ILLEGAL_REQUEST, .asc = 0x30, .ascq = 0x00 ++}; ++ ++/* Illegal request, medium removal prevented */ ++const struct SCSISense sense_code_ILLEGAL_REQ_REMOVAL_PREVENTED = { ++ .key = ILLEGAL_REQUEST, .asc = 0x53, .ascq = 0x02 ++}; ++ ++/* Illegal request, Invalid Transfer Tag */ ++const struct SCSISense sense_code_INVALID_TAG = { ++ .key = ILLEGAL_REQUEST, .asc = 0x4b, .ascq = 0x01 ++}; ++ ++/* Command aborted, I/O process terminated */ ++const struct SCSISense sense_code_IO_ERROR = { ++ .key = ABORTED_COMMAND, .asc = 0x00, .ascq = 0x06 ++}; ++ ++/* Command aborted, I_T Nexus loss occurred */ ++const struct SCSISense sense_code_I_T_NEXUS_LOSS = { ++ .key = ABORTED_COMMAND, .asc = 0x29, .ascq = 0x07 ++}; ++ ++/* Command aborted, Logical Unit failure */ ++const struct SCSISense sense_code_LUN_FAILURE = { ++ .key = ABORTED_COMMAND, .asc = 0x3e, .ascq = 0x01 ++}; ++ ++/* Command aborted, Overlapped Commands Attempted */ ++const struct SCSISense sense_code_OVERLAPPED_COMMANDS = { ++ .key = ABORTED_COMMAND, .asc = 0x4e, .ascq = 0x00 ++}; ++ ++/* Unit attention, Capacity data has changed */ ++const struct SCSISense sense_code_CAPACITY_CHANGED = { ++ .key = UNIT_ATTENTION, .asc = 0x2a, .ascq = 0x09 ++}; ++ ++/* Unit attention, Power on, reset or bus device reset occurred */ ++const struct SCSISense sense_code_RESET = { ++ .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x00 ++}; ++ ++/* Unit attention, No medium */ ++const struct SCSISense sense_code_UNIT_ATTENTION_NO_MEDIUM = { ++ .key = UNIT_ATTENTION, .asc = 0x3a, .ascq = 0x00 ++}; ++ ++/* Unit attention, Medium may have changed */ ++const struct SCSISense sense_code_MEDIUM_CHANGED = { ++ .key = UNIT_ATTENTION, .asc = 0x28, .ascq = 0x00 ++}; ++ ++/* Unit attention, Reported LUNs data has changed */ ++const struct SCSISense sense_code_REPORTED_LUNS_CHANGED = { ++ .key = UNIT_ATTENTION, .asc = 0x3f, .ascq = 0x0e ++}; ++ ++/* Unit attention, Device internal reset */ ++const struct SCSISense sense_code_DEVICE_INTERNAL_RESET = { ++ .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x04 ++}; ++ ++/* Data Protection, Write Protected */ ++const struct SCSISense sense_code_WRITE_PROTECTED = { ++ .key = DATA_PROTECT, .asc = 0x27, .ascq = 0x00 ++}; ++ ++/* Data Protection, Space Allocation Failed Write Protect */ ++const struct SCSISense sense_code_SPACE_ALLOC_FAILED = { ++ .key = DATA_PROTECT, .asc = 0x27, .ascq = 0x07 ++}; ++ ++/* ++ * scsi_convert_sense ++ * ++ * Convert between fixed and descriptor sense buffers ++ */ ++int scsi_convert_sense(uint8_t *in_buf, int in_len, ++ uint8_t *buf, int len, bool fixed) ++{ ++ bool fixed_in; ++ SCSISense sense; ++ if (!fixed && len < 8) { ++ return 0; ++ } ++ ++ if (in_len == 0) { ++ sense.key = NO_SENSE; ++ sense.asc = 0; ++ sense.ascq = 0; ++ } else { ++ fixed_in = (in_buf[0] & 2) == 0; ++ ++ if (fixed == fixed_in) { ++ memcpy(buf, in_buf, MIN(len, in_len)); ++ return MIN(len, in_len); ++ } ++ ++ if (fixed_in) { ++ sense.key = in_buf[2]; ++ sense.asc = in_buf[12]; ++ sense.ascq = in_buf[13]; ++ } else { ++ sense.key = in_buf[1]; ++ sense.asc = in_buf[2]; ++ sense.ascq = in_buf[3]; ++ } ++ } ++ ++ memset(buf, 0, len); ++ if (fixed) { ++ /* Return fixed format sense buffer */ ++ buf[0] = 0x70; ++ buf[2] = sense.key; ++ buf[7] = 10; ++ buf[12] = sense.asc; ++ buf[13] = sense.ascq; ++ return MIN(len, SCSI_SENSE_LEN); ++ } else { ++ /* Return descriptor format sense buffer */ ++ buf[0] = 0x72; ++ buf[1] = sense.key; ++ buf[2] = sense.asc; ++ buf[3] = sense.ascq; ++ return 8; ++ } ++} ++ ++int scsi_sense_to_errno(int key, int asc, int ascq) ++{ ++ switch (key) { ++ case 0x00: /* NO SENSE */ ++ case 0x01: /* RECOVERED ERROR */ ++ case 0x06: /* UNIT ATTENTION */ ++ /* These sense keys are not errors */ ++ return 0; ++ case 0x0b: /* COMMAND ABORTED */ ++ return ECANCELED; ++ case 0x02: /* NOT READY */ ++ case 0x05: /* ILLEGAL REQUEST */ ++ case 0x07: /* DATA PROTECTION */ ++ /* Parse ASCQ */ ++ break; ++ default: ++ return EIO; ++ } ++ switch ((asc << 8) | ascq) { ++ case 0x1a00: /* PARAMETER LIST LENGTH ERROR */ ++ case 0x2000: /* INVALID OPERATION CODE */ ++ case 0x2400: /* INVALID FIELD IN CDB */ ++ case 0x2600: /* INVALID FIELD IN PARAMETER LIST */ ++ return EINVAL; ++ case 0x2100: /* LBA OUT OF RANGE */ ++ case 0x2707: /* SPACE ALLOC FAILED */ ++ return ENOSPC; ++ case 0x2500: /* LOGICAL UNIT NOT SUPPORTED */ ++ return ENOTSUP; ++ case 0x3a00: /* MEDIUM NOT PRESENT */ ++ case 0x3a01: /* MEDIUM NOT PRESENT TRAY CLOSED */ ++ case 0x3a02: /* MEDIUM NOT PRESENT TRAY OPEN */ ++ return ENOMEDIUM; ++ case 0x2700: /* WRITE PROTECTED */ ++ return EACCES; ++ case 0x0401: /* NOT READY, IN PROGRESS OF BECOMING READY */ ++ return EAGAIN; ++ case 0x0402: /* NOT READY, INITIALIZING COMMAND REQUIRED */ ++ return ENOTCONN; ++ default: ++ return EIO; ++ } ++} ++ ++int scsi_sense_buf_to_errno(const uint8_t *sense, size_t sense_size) ++{ ++ int key, asc, ascq; ++ if (sense_size < 1) { ++ return EIO; ++ } ++ switch (sense[0]) { ++ case 0x70: /* Fixed format sense data. */ ++ if (sense_size < 14) { ++ return EIO; ++ } ++ key = sense[2] & 0xF; ++ asc = sense[12]; ++ ascq = sense[13]; ++ break; ++ case 0x72: /* Descriptor format sense data. */ ++ if (sense_size < 4) { ++ return EIO; ++ } ++ key = sense[1] & 0xF; ++ asc = sense[2]; ++ ascq = sense[3]; ++ break; ++ default: ++ return EIO; ++ break; ++ } ++ return scsi_sense_to_errno(key, asc, ascq); ++} ++ ++const char *scsi_command_name(uint8_t cmd) ++{ ++ static const char *names[] = { ++ [ TEST_UNIT_READY ] = "TEST_UNIT_READY", ++ [ REWIND ] = "REWIND", ++ [ REQUEST_SENSE ] = "REQUEST_SENSE", ++ [ FORMAT_UNIT ] = "FORMAT_UNIT", ++ [ READ_BLOCK_LIMITS ] = "READ_BLOCK_LIMITS", ++ [ REASSIGN_BLOCKS ] = "REASSIGN_BLOCKS/INITIALIZE ELEMENT STATUS", ++ /* LOAD_UNLOAD and INITIALIZE_ELEMENT_STATUS use the same operation code */ ++ [ READ_6 ] = "READ_6", ++ [ WRITE_6 ] = "WRITE_6", ++ [ SET_CAPACITY ] = "SET_CAPACITY", ++ [ READ_REVERSE ] = "READ_REVERSE", ++ [ WRITE_FILEMARKS ] = "WRITE_FILEMARKS", ++ [ SPACE ] = "SPACE", ++ [ INQUIRY ] = "INQUIRY", ++ [ RECOVER_BUFFERED_DATA ] = "RECOVER_BUFFERED_DATA", ++ [ MAINTENANCE_IN ] = "MAINTENANCE_IN", ++ [ MAINTENANCE_OUT ] = "MAINTENANCE_OUT", ++ [ MODE_SELECT ] = "MODE_SELECT", ++ [ RESERVE ] = "RESERVE", ++ [ RELEASE ] = "RELEASE", ++ [ COPY ] = "COPY", ++ [ ERASE ] = "ERASE", ++ [ MODE_SENSE ] = "MODE_SENSE", ++ [ START_STOP ] = "START_STOP/LOAD_UNLOAD", ++ /* LOAD_UNLOAD and START_STOP use the same operation code */ ++ [ RECEIVE_DIAGNOSTIC ] = "RECEIVE_DIAGNOSTIC", ++ [ SEND_DIAGNOSTIC ] = "SEND_DIAGNOSTIC", ++ [ ALLOW_MEDIUM_REMOVAL ] = "ALLOW_MEDIUM_REMOVAL", ++ [ READ_CAPACITY_10 ] = "READ_CAPACITY_10", ++ [ READ_10 ] = "READ_10", ++ [ WRITE_10 ] = "WRITE_10", ++ [ SEEK_10 ] = "SEEK_10/POSITION_TO_ELEMENT", ++ /* SEEK_10 and POSITION_TO_ELEMENT use the same operation code */ ++ [ WRITE_VERIFY_10 ] = "WRITE_VERIFY_10", ++ [ VERIFY_10 ] = "VERIFY_10", ++ [ SEARCH_HIGH ] = "SEARCH_HIGH", ++ [ SEARCH_EQUAL ] = "SEARCH_EQUAL", ++ [ SEARCH_LOW ] = "SEARCH_LOW", ++ [ SET_LIMITS ] = "SET_LIMITS", ++ [ PRE_FETCH ] = "PRE_FETCH/READ_POSITION", ++ /* READ_POSITION and PRE_FETCH use the same operation code */ ++ [ SYNCHRONIZE_CACHE ] = "SYNCHRONIZE_CACHE", ++ [ LOCK_UNLOCK_CACHE ] = "LOCK_UNLOCK_CACHE", ++ [ READ_DEFECT_DATA ] = "READ_DEFECT_DATA/INITIALIZE_ELEMENT_STATUS_WITH_RANGE", ++ /* READ_DEFECT_DATA and INITIALIZE_ELEMENT_STATUS_WITH_RANGE use the same operation code */ ++ [ MEDIUM_SCAN ] = "MEDIUM_SCAN", ++ [ COMPARE ] = "COMPARE", ++ [ COPY_VERIFY ] = "COPY_VERIFY", ++ [ WRITE_BUFFER ] = "WRITE_BUFFER", ++ [ READ_BUFFER ] = "READ_BUFFER", ++ [ UPDATE_BLOCK ] = "UPDATE_BLOCK", ++ [ READ_LONG_10 ] = "READ_LONG_10", ++ [ WRITE_LONG_10 ] = "WRITE_LONG_10", ++ [ CHANGE_DEFINITION ] = "CHANGE_DEFINITION", ++ [ WRITE_SAME_10 ] = "WRITE_SAME_10", ++ [ UNMAP ] = "UNMAP", ++ [ READ_TOC ] = "READ_TOC", ++ [ REPORT_DENSITY_SUPPORT ] = "REPORT_DENSITY_SUPPORT", ++ [ SANITIZE ] = "SANITIZE", ++ [ GET_CONFIGURATION ] = "GET_CONFIGURATION", ++ [ LOG_SELECT ] = "LOG_SELECT", ++ [ LOG_SENSE ] = "LOG_SENSE", ++ [ MODE_SELECT_10 ] = "MODE_SELECT_10", ++ [ RESERVE_10 ] = "RESERVE_10", ++ [ RELEASE_10 ] = "RELEASE_10", ++ [ MODE_SENSE_10 ] = "MODE_SENSE_10", ++ [ PERSISTENT_RESERVE_IN ] = "PERSISTENT_RESERVE_IN", ++ [ PERSISTENT_RESERVE_OUT ] = "PERSISTENT_RESERVE_OUT", ++ [ WRITE_FILEMARKS_16 ] = "WRITE_FILEMARKS_16", ++ [ EXTENDED_COPY ] = "EXTENDED_COPY", ++ [ ATA_PASSTHROUGH_16 ] = "ATA_PASSTHROUGH_16", ++ [ ACCESS_CONTROL_IN ] = "ACCESS_CONTROL_IN", ++ [ ACCESS_CONTROL_OUT ] = "ACCESS_CONTROL_OUT", ++ [ READ_16 ] = "READ_16", ++ [ COMPARE_AND_WRITE ] = "COMPARE_AND_WRITE", ++ [ WRITE_16 ] = "WRITE_16", ++ [ WRITE_VERIFY_16 ] = "WRITE_VERIFY_16", ++ [ VERIFY_16 ] = "VERIFY_16", ++ [ PRE_FETCH_16 ] = "PRE_FETCH_16", ++ [ SYNCHRONIZE_CACHE_16 ] = "SPACE_16/SYNCHRONIZE_CACHE_16", ++ /* SPACE_16 and SYNCHRONIZE_CACHE_16 use the same operation code */ ++ [ LOCATE_16 ] = "LOCATE_16", ++ [ WRITE_SAME_16 ] = "ERASE_16/WRITE_SAME_16", ++ /* ERASE_16 and WRITE_SAME_16 use the same operation code */ ++ [ SERVICE_ACTION_IN_16 ] = "SERVICE_ACTION_IN_16", ++ [ WRITE_LONG_16 ] = "WRITE_LONG_16", ++ [ REPORT_LUNS ] = "REPORT_LUNS", ++ [ ATA_PASSTHROUGH_12 ] = "BLANK/ATA_PASSTHROUGH_12", ++ [ MOVE_MEDIUM ] = "MOVE_MEDIUM", ++ [ EXCHANGE_MEDIUM ] = "EXCHANGE MEDIUM", ++ [ READ_12 ] = "READ_12", ++ [ WRITE_12 ] = "WRITE_12", ++ [ ERASE_12 ] = "ERASE_12/GET_PERFORMANCE", ++ /* ERASE_12 and GET_PERFORMANCE use the same operation code */ ++ [ SERVICE_ACTION_IN_12 ] = "SERVICE_ACTION_IN_12", ++ [ WRITE_VERIFY_12 ] = "WRITE_VERIFY_12", ++ [ VERIFY_12 ] = "VERIFY_12", ++ [ SEARCH_HIGH_12 ] = "SEARCH_HIGH_12", ++ [ SEARCH_EQUAL_12 ] = "SEARCH_EQUAL_12", ++ [ SEARCH_LOW_12 ] = "SEARCH_LOW_12", ++ [ READ_ELEMENT_STATUS ] = "READ_ELEMENT_STATUS", ++ [ SEND_VOLUME_TAG ] = "SEND_VOLUME_TAG/SET_STREAMING", ++ /* SEND_VOLUME_TAG and SET_STREAMING use the same operation code */ ++ [ READ_CD ] = "READ_CD", ++ [ READ_DEFECT_DATA_12 ] = "READ_DEFECT_DATA_12", ++ [ READ_DVD_STRUCTURE ] = "READ_DVD_STRUCTURE", ++ [ RESERVE_TRACK ] = "RESERVE_TRACK", ++ [ SEND_CUE_SHEET ] = "SEND_CUE_SHEET", ++ [ SEND_DVD_STRUCTURE ] = "SEND_DVD_STRUCTURE", ++ [ SET_CD_SPEED ] = "SET_CD_SPEED", ++ [ SET_READ_AHEAD ] = "SET_READ_AHEAD", ++ [ ALLOW_OVERWRITE ] = "ALLOW_OVERWRITE", ++ [ MECHANISM_STATUS ] = "MECHANISM_STATUS", ++ [ GET_EVENT_STATUS_NOTIFICATION ] = "GET_EVENT_STATUS_NOTIFICATION", ++ [ READ_DISC_INFORMATION ] = "READ_DISC_INFORMATION", ++ }; ++ ++ if (cmd >= ARRAY_SIZE(names) || names[cmd] == NULL) { ++ return "*UNKNOWN*"; ++ } ++ return names[cmd]; ++} +diff --git a/util/Makefile.objs b/util/Makefile.objs +index c9e6c493d3..50a55ecc75 100644 +--- a/util/Makefile.objs ++++ b/util/Makefile.objs +@@ -45,4 +45,3 @@ util-obj-y += qht.o + util-obj-y += range.o + util-obj-y += stats64.o + util-obj-y += systemd.o +-util-obj-y += scsi.o +diff --git a/util/scsi.c b/util/scsi.c +deleted file mode 100644 +index 472293d59b..0000000000 +--- a/util/scsi.c ++++ /dev/null +@@ -1,90 +0,0 @@ +-/* +- * SCSI helpers +- * +- * Copyright 2017 Red Hat, Inc. +- * +- * Authors: +- * Fam Zheng +- * +- * This program is free software; you can redistribute it and/or modify it +- * under the terms of the GNU General Public License as published by the Free +- * Software Foundation; either version 2 of the License, or (at your option) +- * any later version. +- */ +- +-#include "qemu/osdep.h" +-#include "scsi/scsi.h" +- +-int scsi_sense_to_errno(int key, int asc, int ascq) +-{ +- switch (key) { +- case 0x00: /* NO SENSE */ +- case 0x01: /* RECOVERED ERROR */ +- case 0x06: /* UNIT ATTENTION */ +- /* These sense keys are not errors */ +- return 0; +- case 0x0b: /* COMMAND ABORTED */ +- return ECANCELED; +- case 0x02: /* NOT READY */ +- case 0x05: /* ILLEGAL REQUEST */ +- case 0x07: /* DATA PROTECTION */ +- /* Parse ASCQ */ +- break; +- default: +- return EIO; +- } +- switch ((asc << 8) | ascq) { +- case 0x1a00: /* PARAMETER LIST LENGTH ERROR */ +- case 0x2000: /* INVALID OPERATION CODE */ +- case 0x2400: /* INVALID FIELD IN CDB */ +- case 0x2600: /* INVALID FIELD IN PARAMETER LIST */ +- return EINVAL; +- case 0x2100: /* LBA OUT OF RANGE */ +- case 0x2707: /* SPACE ALLOC FAILED */ +- return ENOSPC; +- case 0x2500: /* LOGICAL UNIT NOT SUPPORTED */ +- return ENOTSUP; +- case 0x3a00: /* MEDIUM NOT PRESENT */ +- case 0x3a01: /* MEDIUM NOT PRESENT TRAY CLOSED */ +- case 0x3a02: /* MEDIUM NOT PRESENT TRAY OPEN */ +- return ENOMEDIUM; +- case 0x2700: /* WRITE PROTECTED */ +- return EACCES; +- case 0x0401: /* NOT READY, IN PROGRESS OF BECOMING READY */ +- return EAGAIN; +- case 0x0402: /* NOT READY, INITIALIZING COMMAND REQUIRED */ +- return ENOTCONN; +- default: +- return EIO; +- } +-} +- +-int scsi_sense_buf_to_errno(const uint8_t *sense, size_t sense_size) +-{ +- int key, asc, ascq; +- if (sense_size < 1) { +- return EIO; +- } +- switch (sense[0]) { +- case 0x70: /* Fixed format sense data. */ +- if (sense_size < 14) { +- return EIO; +- } +- key = sense[2] & 0xF; +- asc = sense[12]; +- ascq = sense[13]; +- break; +- case 0x72: /* Descriptor format sense data. */ +- if (sense_size < 4) { +- return EIO; +- } +- key = sense[1] & 0xF; +- asc = sense[2]; +- ascq = sense[3]; +- break; +- default: +- return EIO; +- break; +- } +- return scsi_sense_to_errno(key, asc, ascq); +-} +-- +2.13.5 + diff --git a/1009-scsi-introduce-scsi_build_sense.patch b/1009-scsi-introduce-scsi_build_sense.patch new file mode 100644 index 0000000..8b16369 --- /dev/null +++ b/1009-scsi-introduce-scsi_build_sense.patch @@ -0,0 +1,77 @@ +From 82e922c8be1b32eeb0c8c22165cdeff39e12d3ef Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +Date: Tue, 22 Aug 2017 09:42:59 +0200 +Subject: [PATCH 09/15] scsi: introduce scsi_build_sense +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Move more knowledge of sense data format out of hw/scsi/scsi-bus.c +for reusability. + +Reviewed-by: Philippe Mathieu-Daudé +Reviewed-by: Stefan Hajnoczi +Signed-off-by: Paolo Bonzini +--- + hw/scsi/scsi-bus.c | 8 +------- + include/scsi/utils.h | 2 ++ + scsi/utils.c | 11 +++++++++++ + 3 files changed, 14 insertions(+), 7 deletions(-) + +diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c +index 42920d5422..652ab046ab 100644 +--- a/hw/scsi/scsi-bus.c ++++ b/hw/scsi/scsi-bus.c +@@ -818,13 +818,7 @@ void scsi_req_build_sense(SCSIRequest *req, SCSISense sense) + { + trace_scsi_req_build_sense(req->dev->id, req->lun, req->tag, + sense.key, sense.asc, sense.ascq); +- memset(req->sense, 0, 18); +- req->sense[0] = 0x70; +- req->sense[2] = sense.key; +- req->sense[7] = 10; +- req->sense[12] = sense.asc; +- req->sense[13] = sense.ascq; +- req->sense_len = 18; ++ req->sense_len = scsi_build_sense(req->sense, sense); + } + + static void scsi_req_enqueue_internal(SCSIRequest *req) +diff --git a/include/scsi/utils.h b/include/scsi/utils.h +index 90bf4dce6e..b49392d841 100644 +--- a/include/scsi/utils.h ++++ b/include/scsi/utils.h +@@ -30,6 +30,8 @@ typedef struct SCSISense { + uint8_t ascq; + } SCSISense; + ++int scsi_build_sense(uint8_t *buf, SCSISense sense); ++ + /* + * Predefined sense codes + */ +diff --git a/scsi/utils.c b/scsi/utils.c +index 2327e06da0..89d9167d9d 100644 +--- a/scsi/utils.c ++++ b/scsi/utils.c +@@ -96,6 +96,17 @@ int scsi_cdb_length(uint8_t *buf) + return cdb_len; + } + ++int scsi_build_sense(uint8_t *buf, SCSISense sense) ++{ ++ memset(buf, 0, 18); ++ buf[0] = 0x70; ++ buf[2] = sense.key; ++ buf[7] = 10; ++ buf[12] = sense.asc; ++ buf[13] = sense.ascq; ++ return 18; ++} ++ + /* + * Predefined sense codes + */ +-- +2.13.5 + diff --git a/1010-scsi-introduce-sg_io_sense_from_errno.patch b/1010-scsi-introduce-sg_io_sense_from_errno.patch new file mode 100644 index 0000000..7cb02e1 --- /dev/null +++ b/1010-scsi-introduce-sg_io_sense_from_errno.patch @@ -0,0 +1,137 @@ +From 1ebf7935eca7f2a1f5a1376ee3b234f4fce98023 Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +Date: Tue, 22 Aug 2017 09:43:14 +0200 +Subject: [PATCH 10/15] scsi: introduce sg_io_sense_from_errno + +Move more knowledge of SG_IO out of hw/scsi/scsi-generic.c, for +reusability. + +Reviewed-by: Stefan Hajnoczi +Signed-off-by: Paolo Bonzini +--- + hw/scsi/scsi-generic.c | 40 +++++++--------------------------------- + include/scsi/utils.h | 3 +++ + scsi/utils.c | 35 +++++++++++++++++++++++++++++++++++ + 3 files changed, 45 insertions(+), 33 deletions(-) + +diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c +index 7a8f500934..04c687ee76 100644 +--- a/hw/scsi/scsi-generic.c ++++ b/hw/scsi/scsi-generic.c +@@ -81,6 +81,7 @@ static void scsi_free_request(SCSIRequest *req) + static void scsi_command_complete_noio(SCSIGenericReq *r, int ret) + { + int status; ++ SCSISense sense; + + assert(r->req.aiocb == NULL); + +@@ -88,42 +89,15 @@ static void scsi_command_complete_noio(SCSIGenericReq *r, int ret) + scsi_req_cancel_complete(&r->req); + goto done; + } +- if (r->io_header.driver_status & SG_ERR_DRIVER_SENSE) { +- r->req.sense_len = r->io_header.sb_len_wr; +- } +- +- if (ret != 0) { +- switch (ret) { +- case -EDOM: +- status = TASK_SET_FULL; +- break; +- case -ENOMEM: +- status = CHECK_CONDITION; +- scsi_req_build_sense(&r->req, SENSE_CODE(TARGET_FAILURE)); +- break; +- default: +- status = CHECK_CONDITION; +- scsi_req_build_sense(&r->req, SENSE_CODE(IO_ERROR)); +- break; +- } +- } else { +- if (r->io_header.host_status == SG_ERR_DID_NO_CONNECT || +- r->io_header.host_status == SG_ERR_DID_BUS_BUSY || +- r->io_header.host_status == SG_ERR_DID_TIME_OUT || +- (r->io_header.driver_status & SG_ERR_DRIVER_TIMEOUT)) { +- status = BUSY; +- BADF("Driver Timeout\n"); +- } else if (r->io_header.host_status) { +- status = CHECK_CONDITION; +- scsi_req_build_sense(&r->req, SENSE_CODE(I_T_NEXUS_LOSS)); +- } else if (r->io_header.status) { +- status = r->io_header.status; +- } else if (r->io_header.driver_status & SG_ERR_DRIVER_SENSE) { +- status = CHECK_CONDITION; ++ status = sg_io_sense_from_errno(-ret, &r->io_header, &sense); ++ if (status == CHECK_CONDITION) { ++ if (r->io_header.driver_status & SG_ERR_DRIVER_SENSE) { ++ r->req.sense_len = r->io_header.sb_len_wr; + } else { +- status = GOOD; ++ scsi_req_build_sense(&r->req, sense); + } + } ++ + DPRINTF("Command complete 0x%p tag=0x%x status=%d\n", + r, r->req.tag, status); + +diff --git a/include/scsi/utils.h b/include/scsi/utils.h +index b49392d841..d301b31768 100644 +--- a/include/scsi/utils.h ++++ b/include/scsi/utils.h +@@ -116,6 +116,9 @@ int scsi_cdb_length(uint8_t *buf); + #define SG_ERR_DID_TIME_OUT 0x03 + + #define SG_ERR_DRIVER_SENSE 0x08 ++ ++int sg_io_sense_from_errno(int errno_value, struct sg_io_hdr *io_hdr, ++ SCSISense *sense); + #endif + + #endif +diff --git a/scsi/utils.c b/scsi/utils.c +index 89d9167d9d..6ee9f4095b 100644 +--- a/scsi/utils.c ++++ b/scsi/utils.c +@@ -501,3 +501,38 @@ const char *scsi_command_name(uint8_t cmd) + } + return names[cmd]; + } ++ ++#ifdef CONFIG_LINUX ++int sg_io_sense_from_errno(int errno_value, struct sg_io_hdr *io_hdr, ++ SCSISense *sense) ++{ ++ if (errno_value != 0) { ++ switch (errno_value) { ++ case EDOM: ++ return TASK_SET_FULL; ++ case ENOMEM: ++ *sense = SENSE_CODE(TARGET_FAILURE); ++ return CHECK_CONDITION; ++ default: ++ *sense = SENSE_CODE(IO_ERROR); ++ 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; ++ } else if (io_hdr->driver_status & SG_ERR_DRIVER_SENSE) { ++ return CHECK_CONDITION; ++ } else { ++ return GOOD; ++ } ++ } ++} ++#endif +-- +2.13.5 + diff --git a/1011-scsi-move-block-scsi.h-to-include-scsi-constants.h.patch b/1011-scsi-move-block-scsi.h-to-include-scsi-constants.h.patch new file mode 100644 index 0000000..1541323 --- /dev/null +++ b/1011-scsi-move-block-scsi.h-to-include-scsi-constants.h.patch @@ -0,0 +1,250 @@ +From e2b560d9f9966d7256488d0a359200c65c2c07f8 Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +Date: Tue, 22 Aug 2017 09:23:55 +0200 +Subject: [PATCH 11/15] scsi: move block/scsi.h to include/scsi/constants.h +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Complete the transition by renaming this header, which was +shared by block/iscsi.c and the SCSI emulation code. + +Reviewed-by: Philippe Mathieu-Daudé +Reviewed-by: Stefan Hajnoczi +Signed-off-by: Paolo Bonzini +--- + block/iscsi.c | 2 +- + hw/block/virtio-blk.c | 2 +- + hw/scsi/megasas.c | 2 +- + hw/scsi/mptendian.c | 2 +- + hw/scsi/mptsas.c | 2 +- + hw/scsi/scsi-bus.c | 2 +- + hw/scsi/scsi-disk.c | 2 +- + hw/scsi/scsi-generic.c | 2 +- + hw/scsi/spapr_vscsi.c | 2 +- + hw/scsi/virtio-scsi-dataplane.c | 2 +- + hw/scsi/virtio-scsi.c | 2 +- + hw/scsi/vmw_pvscsi.c | 2 +- + hw/usb/dev-uas.c | 2 +- + include/hw/ide/internal.h | 2 +- + include/{block/scsi.h => scsi/constants.h} | 0 + scsi/utils.c | 2 +- + tests/virtio-scsi-test.c | 2 +- + 17 files changed, 16 insertions(+), 16 deletions(-) + rename include/{block/scsi.h => scsi/constants.h} (100%) + +diff --git a/block/iscsi.c b/block/iscsi.c +index 40adc3c493..c4586be720 100644 +--- a/block/iscsi.c ++++ b/block/iscsi.c +@@ -34,7 +34,7 @@ + #include "qemu/bitops.h" + #include "qemu/bitmap.h" + #include "block/block_int.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "qemu/iov.h" + #include "qemu/uuid.h" + #include "qmp-commands.h" +diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c +index a16ac75090..05d1440786 100644 +--- a/hw/block/virtio-blk.c ++++ b/hw/block/virtio-blk.c +@@ -22,7 +22,7 @@ + #include "sysemu/blockdev.h" + #include "hw/virtio/virtio-blk.h" + #include "dataplane/virtio-blk.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #ifdef __linux__ + # include + #endif +diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c +index 734fdaef90..0db68aacee 100644 +--- a/hw/scsi/megasas.c ++++ b/hw/scsi/megasas.c +@@ -27,7 +27,7 @@ + #include "hw/pci/msix.h" + #include "qemu/iov.h" + #include "hw/scsi/scsi.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "trace.h" + #include "qapi/error.h" + #include "mfi.h" +diff --git a/hw/scsi/mptendian.c b/hw/scsi/mptendian.c +index b7fe2a2a36..3415229b5e 100644 +--- a/hw/scsi/mptendian.c ++++ b/hw/scsi/mptendian.c +@@ -28,7 +28,7 @@ + #include "hw/pci/msi.h" + #include "qemu/iov.h" + #include "hw/scsi/scsi.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "trace.h" + + #include "mptsas.h" +diff --git a/hw/scsi/mptsas.c b/hw/scsi/mptsas.c +index 765ab53c34..8bae8f543e 100644 +--- a/hw/scsi/mptsas.c ++++ b/hw/scsi/mptsas.c +@@ -30,7 +30,7 @@ + #include "hw/pci/msi.h" + #include "qemu/iov.h" + #include "hw/scsi/scsi.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "trace.h" + #include "qapi/error.h" + #include "mptsas.h" +diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c +index 652ab046ab..977f7bce1f 100644 +--- a/hw/scsi/scsi-bus.c ++++ b/hw/scsi/scsi-bus.c +@@ -3,7 +3,7 @@ + #include "qapi/error.h" + #include "qemu/error-report.h" + #include "hw/scsi/scsi.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "hw/qdev.h" + #include "sysemu/block-backend.h" + #include "sysemu/blockdev.h" +diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c +index 0a1f4ef0c7..5faf6682c5 100644 +--- a/hw/scsi/scsi-disk.c ++++ b/hw/scsi/scsi-disk.c +@@ -32,7 +32,7 @@ do { printf("scsi-disk: " fmt , ## __VA_ARGS__); } while (0) + #include "qapi/error.h" + #include "qemu/error-report.h" + #include "hw/scsi/scsi.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "sysemu/sysemu.h" + #include "sysemu/block-backend.h" + #include "sysemu/blockdev.h" +diff --git a/hw/scsi/scsi-generic.c b/hw/scsi/scsi-generic.c +index 04c687ee76..bd0d9ff355 100644 +--- a/hw/scsi/scsi-generic.c ++++ b/hw/scsi/scsi-generic.c +@@ -34,7 +34,7 @@ do { printf("scsi-generic: " fmt , ## __VA_ARGS__); } while (0) + do { fprintf(stderr, "scsi-generic: " fmt , ## __VA_ARGS__); } while (0) + + #include +-#include "block/scsi.h" ++#include "scsi/constants.h" + + #ifndef MAX_UINT + #define MAX_UINT ((unsigned int)-1) +diff --git a/hw/scsi/spapr_vscsi.c b/hw/scsi/spapr_vscsi.c +index 55ee48c4da..360db53ac8 100644 +--- a/hw/scsi/spapr_vscsi.c ++++ b/hw/scsi/spapr_vscsi.c +@@ -36,7 +36,7 @@ + #include "cpu.h" + #include "hw/hw.h" + #include "hw/scsi/scsi.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "srp.h" + #include "hw/qdev.h" + #include "hw/ppc/spapr.h" +diff --git a/hw/scsi/virtio-scsi-dataplane.c b/hw/scsi/virtio-scsi-dataplane.c +index 944ea4eb53..add4b3f4a4 100644 +--- a/hw/scsi/virtio-scsi-dataplane.c ++++ b/hw/scsi/virtio-scsi-dataplane.c +@@ -17,7 +17,7 @@ + #include "qemu/error-report.h" + #include "sysemu/block-backend.h" + #include "hw/scsi/scsi.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "hw/virtio/virtio-bus.h" + #include "hw/virtio/virtio-access.h" + +diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c +index eb639442d1..823a1e9a42 100644 +--- a/hw/scsi/virtio-scsi.c ++++ b/hw/scsi/virtio-scsi.c +@@ -21,7 +21,7 @@ + #include "qemu/iov.h" + #include "sysemu/block-backend.h" + #include "hw/scsi/scsi.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "hw/virtio/virtio-bus.h" + #include "hw/virtio/virtio-access.h" + +diff --git a/hw/scsi/vmw_pvscsi.c b/hw/scsi/vmw_pvscsi.c +index 77d8b6f9e2..6d3f0bf11d 100644 +--- a/hw/scsi/vmw_pvscsi.c ++++ b/hw/scsi/vmw_pvscsi.c +@@ -28,7 +28,7 @@ + #include "qemu/osdep.h" + #include "qapi/error.h" + #include "hw/scsi/scsi.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "hw/pci/msi.h" + #include "vmw_pvscsi.h" + #include "trace.h" +diff --git a/hw/usb/dev-uas.c b/hw/usb/dev-uas.c +index fffc424396..c218b53f09 100644 +--- a/hw/usb/dev-uas.c ++++ b/hw/usb/dev-uas.c +@@ -19,7 +19,7 @@ + #include "hw/usb.h" + #include "hw/usb/desc.h" + #include "hw/scsi/scsi.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + + /* --------------------------------------------------------------------- */ + +diff --git a/include/hw/ide/internal.h b/include/hw/ide/internal.h +index 482a9512be..63a99e0366 100644 +--- a/include/hw/ide/internal.h ++++ b/include/hw/ide/internal.h +@@ -11,7 +11,7 @@ + #include "sysemu/dma.h" + #include "sysemu/sysemu.h" + #include "hw/block/block.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + + /* debug IDE devices */ + //#define DEBUG_IDE +diff --git a/include/block/scsi.h b/include/scsi/constants.h +similarity index 100% +rename from include/block/scsi.h +rename to include/scsi/constants.h +diff --git a/scsi/utils.c b/scsi/utils.c +index 6ee9f4095b..fab60bdf20 100644 +--- a/scsi/utils.c ++++ b/scsi/utils.c +@@ -14,7 +14,7 @@ + */ + + #include "qemu/osdep.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "scsi/utils.h" + #include "qemu/bswap.h" + +diff --git a/tests/virtio-scsi-test.c b/tests/virtio-scsi-test.c +index 87a3b6e81a..082d323541 100644 +--- a/tests/virtio-scsi-test.c ++++ b/tests/virtio-scsi-test.c +@@ -10,7 +10,7 @@ + + #include "qemu/osdep.h" + #include "libqtest.h" +-#include "block/scsi.h" ++#include "scsi/constants.h" + #include "libqos/libqos-pc.h" + #include "libqos/libqos-spapr.h" + #include "libqos/virtio.h" +-- +2.13.5 + diff --git a/1012-scsi-file-posix-add-support-for-persistent-reservati.patch b/1012-scsi-file-posix-add-support-for-persistent-reservati.patch new file mode 100644 index 0000000..2780bb3 --- /dev/null +++ b/1012-scsi-file-posix-add-support-for-persistent-reservati.patch @@ -0,0 +1,440 @@ +From 5c4a4b825189c2e9f322c8673104add7f76e38d5 Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +Date: Mon, 21 Aug 2017 18:58:56 +0200 +Subject: [PATCH 12/15] scsi, file-posix: add support for persistent + reservation management + +It is a common requirement for virtual machine to send persistent +reservations, but this currently requires either running QEMU with +CAP_SYS_RAWIO, or using out-of-tree patches that let an unprivileged +QEMU bypass Linux's filter on SG_IO commands. + +As an alternative mechanism, the next patches will introduce a +privileged helper to run persistent reservation commands without +expanding QEMU's attack surface unnecessarily. + +The helper is invoked through a "pr-manager" QOM object, to which +file-posix.c passes SG_IO requests for PERSISTENT RESERVE OUT and +PERSISTENT RESERVE IN commands. For example: + + $ qemu-system-x86_64 + -device virtio-scsi \ + -object pr-manager-helper,id=helper0,path=/var/run/qemu-pr-helper.sock + -drive if=none,id=hd,driver=raw,file.filename=/dev/sdb,file.pr-manager=helper0 + -device scsi-block,drive=hd + +or: + + $ qemu-system-x86_64 + -device virtio-scsi \ + -object pr-manager-helper,id=helper0,path=/var/run/qemu-pr-helper.sock + -blockdev node-name=hd,driver=raw,file.driver=host_device,file.filename=/dev/sdb,file.pr-manager=helper0 + -device scsi-block,drive=hd + +Multiple pr-manager implementations are conceivable and possible, though +only one is implemented right now. For example, a pr-manager could: + +- talk directly to the multipath daemon from a privileged QEMU + (i.e. QEMU links to libmpathpersist); this makes reservation work + properly with multipath, but still requires CAP_SYS_RAWIO + +- use the Linux IOC_PR_* ioctls (they require CAP_SYS_ADMIN though) + +- more interestingly, implement reservations directly in QEMU + through file system locks or a shared database (e.g. sqlite) + +Signed-off-by: Paolo Bonzini +--- + Makefile.objs | 1 + + block/file-posix.c | 30 +++++++++++++ + docs/pr-manager.rst | 51 ++++++++++++++++++++++ + include/scsi/pr-manager.h | 56 ++++++++++++++++++++++++ + qapi/block-core.json | 4 ++ + scsi/Makefile.objs | 2 + + scsi/pr-manager.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++ + scsi/trace-events | 3 ++ + vl.c | 3 +- + 9 files changed, 258 insertions(+), 1 deletion(-) + create mode 100644 docs/pr-manager.rst + create mode 100644 include/scsi/pr-manager.h + create mode 100644 scsi/pr-manager.c + create mode 100644 scsi/trace-events + +diff --git a/Makefile.objs b/Makefile.objs +index f68aa3b60d..64bebd05db 100644 +--- a/Makefile.objs ++++ b/Makefile.objs +@@ -168,6 +168,7 @@ trace-events-subdirs += qapi + trace-events-subdirs += accel/tcg + trace-events-subdirs += accel/kvm + trace-events-subdirs += nbd ++trace-events-subdirs += scsi + + trace-events-files = $(SRC_PATH)/trace-events $(trace-events-subdirs:%=$(SRC_PATH)/%/trace-events) + +diff --git a/block/file-posix.c b/block/file-posix.c +index cb3bfce147..9cacf06685 100644 +--- a/block/file-posix.c ++++ b/block/file-posix.c +@@ -34,6 +34,9 @@ + #include "qapi/util.h" + #include "qapi/qmp/qstring.h" + ++#include "scsi/pr-manager.h" ++#include "scsi/constants.h" ++ + #if defined(__APPLE__) && (__MACH__) + #include + #include +@@ -156,6 +159,8 @@ typedef struct BDRVRawState { + bool page_cache_inconsistent:1; + bool has_fallocate; + bool needs_alignment; ++ ++ PRManager *pr_mgr; + } BDRVRawState; + + typedef struct BDRVRawReopenState { +@@ -403,6 +408,11 @@ static QemuOptsList raw_runtime_opts = { + .type = QEMU_OPT_STRING, + .help = "file locking mode (on/off/auto, default: auto)", + }, ++ { ++ .name = "pr-manager", ++ .type = QEMU_OPT_STRING, ++ .help = "id of persistent reservation manager object (default: none)", ++ }, + { /* end of list */ } + }, + }; +@@ -414,6 +424,7 @@ static int raw_open_common(BlockDriverState *bs, QDict *options, + QemuOpts *opts; + Error *local_err = NULL; + const char *filename = NULL; ++ const char *str; + BlockdevAioOptions aio, aio_default; + int fd, ret; + struct stat st; +@@ -475,6 +486,16 @@ static int raw_open_common(BlockDriverState *bs, QDict *options, + abort(); + } + ++ str = qemu_opt_get(opts, "pr-manager"); ++ if (str) { ++ s->pr_mgr = pr_manager_lookup(str, &local_err); ++ if (local_err) { ++ error_propagate(errp, local_err); ++ ret = -EINVAL; ++ goto fail; ++ } ++ } ++ + s->open_flags = open_flags; + raw_parse_flags(bdrv_flags, &s->open_flags); + +@@ -2597,6 +2618,15 @@ static BlockAIOCB *hdev_aio_ioctl(BlockDriverState *bs, + if (fd_open(bs) < 0) + return NULL; + ++ if (req == SG_IO && s->pr_mgr) { ++ struct sg_io_hdr *io_hdr = buf; ++ if (io_hdr->cmdp[0] == PERSISTENT_RESERVE_OUT || ++ io_hdr->cmdp[0] == PERSISTENT_RESERVE_IN) { ++ return pr_manager_execute(s->pr_mgr, bdrv_get_aio_context(bs), ++ s->fd, io_hdr, cb, opaque); ++ } ++ } ++ + acb = g_new(RawPosixAIOData, 1); + acb->bs = bs; + acb->aio_type = QEMU_AIO_IOCTL; +diff --git a/docs/pr-manager.rst b/docs/pr-manager.rst +new file mode 100644 +index 0000000000..b6089fb57c +--- /dev/null ++++ b/docs/pr-manager.rst +@@ -0,0 +1,51 @@ ++====================================== ++Persistent reservation managers ++====================================== ++ ++SCSI persistent Reservations allow restricting access to block devices ++to specific initiators in a shared storage setup. When implementing ++clustering of virtual machines, it is a common requirement for virtual ++machines to send persistent reservation SCSI commands. However, ++the operating system restricts sending these commands to unprivileged ++programs because incorrect usage can disrupt regular operation of the ++storage fabric. ++ ++For this reason, QEMU's SCSI passthrough devices, ``scsi-block`` ++and ``scsi-generic`` (both are only available on Linux) can delegate ++implementation of persistent reservations to a separate object, ++the "persistent reservation manager". Only PERSISTENT RESERVE OUT and ++PERSISTENT RESERVE IN commands are passed to the persistent reservation ++manager object; other commands are processed by QEMU as usual. ++ ++----------------------------------------- ++Defining a persistent reservation manager ++----------------------------------------- ++ ++A persistent reservation manager is an instance of a subclass of the ++"pr-manager" QOM class. ++ ++Right now only one subclass is defined, ``pr-manager-helper``, which ++forwards the commands to an external privileged helper program ++over Unix sockets. The helper program only allows sending persistent ++reservation commands to devices for which QEMU has a file descriptor, ++so that QEMU will not be able to effect persistent reservations ++unless it has access to both the socket and the device. ++ ++``pr-manager-helper`` has a single string property, ``path``, which ++accepts the path to the helper program's Unix socket. For example, ++the following command line defines a ``pr-manager-helper`` object and ++attaches it to a SCSI passthrough device:: ++ ++ $ qemu-system-x86_64 ++ -device virtio-scsi \ ++ -object pr-manager-helper,id=helper0,path=/var/run/qemu-pr-helper.sock ++ -drive if=none,id=hd,driver=raw,file.filename=/dev/sdb,file.pr-manager=helper0 ++ -device scsi-block,drive=hd ++ ++Alternatively, using ``-blockdev``:: ++ ++ $ qemu-system-x86_64 ++ -device virtio-scsi \ ++ -object pr-manager-helper,id=helper0,path=/var/run/qemu-pr-helper.sock ++ -blockdev node-name=hd,driver=raw,file.driver=host_device,file.filename=/dev/sdb,file.pr-manager=helper0 ++ -device scsi-block,drive=hd +diff --git a/include/scsi/pr-manager.h b/include/scsi/pr-manager.h +new file mode 100644 +index 0000000000..b2b37d63bc +--- /dev/null ++++ b/include/scsi/pr-manager.h +@@ -0,0 +1,56 @@ ++#ifndef PR_MANAGER_H ++#define PR_MANAGER_H ++ ++#include "qom/object.h" ++#include "qapi/qmp/qdict.h" ++#include "qapi/visitor.h" ++#include "qom/object_interfaces.h" ++#include "block/aio.h" ++ ++#define TYPE_PR_MANAGER "pr-manager" ++ ++#define PR_MANAGER_CLASS(klass) \ ++ OBJECT_CLASS_CHECK(PRManagerClass, (klass), TYPE_PR_MANAGER) ++#define PR_MANAGER_GET_CLASS(obj) \ ++ OBJECT_GET_CLASS(PRManagerClass, (obj), TYPE_PR_MANAGER) ++#define PR_MANAGER(obj) \ ++ OBJECT_CHECK(PRManager, (obj), TYPE_PR_MANAGER) ++ ++struct sg_io_hdr; ++ ++typedef struct PRManager { ++ /* */ ++ Object parent; ++} PRManager; ++ ++/** ++ * PRManagerClass: ++ * @parent_class: the base class ++ * @run: callback invoked in thread pool context ++ */ ++typedef struct PRManagerClass { ++ /* */ ++ ObjectClass parent_class; ++ ++ /* */ ++ int (*run)(PRManager *pr_mgr, int fd, struct sg_io_hdr *hdr); ++} PRManagerClass; ++ ++BlockAIOCB *pr_manager_execute(PRManager *pr_mgr, ++ AioContext *ctx, int fd, ++ struct sg_io_hdr *hdr, ++ BlockCompletionFunc *complete, ++ void *opaque); ++ ++#ifdef CONFIG_LINUX ++PRManager *pr_manager_lookup(const char *id, Error **errp); ++#else ++static inline PRManager *pr_manager_lookup(const char *id, Error **errp) ++{ ++ /* The classes do not exist at all! */ ++ error_setg(errp, "No persistent reservation manager with id '%s'", id); ++ return NULL; ++} ++#endif ++ ++#endif +diff --git a/qapi/block-core.json b/qapi/block-core.json +index 833c602150..1cf6ec8be7 100644 +--- a/qapi/block-core.json ++++ b/qapi/block-core.json +@@ -2191,6 +2191,9 @@ + # Driver specific block device options for the file backend. + # + # @filename: path to the image file ++# @pr-manager: the id for the object that will handle persistent reservations ++# for this device (default: none, forward the commands via SG_IO; ++# since 2.11) + # @aio: AIO backend (default: threads) (since: 2.8) + # @locking: whether to enable file locking. If set to 'auto', only enable + # when Open File Descriptor (OFD) locking API is available +@@ -2200,6 +2203,7 @@ + ## + { 'struct': 'BlockdevOptionsFile', + 'data': { 'filename': 'str', ++ '*pr-manager': 'str', + '*locking': 'OnOffAuto', + '*aio': 'BlockdevAioOptions' } } + +diff --git a/scsi/Makefile.objs b/scsi/Makefile.objs +index 31b82a5a36..5496d2ae6a 100644 +--- a/scsi/Makefile.objs ++++ b/scsi/Makefile.objs +@@ -1 +1,3 @@ + block-obj-y += utils.o ++ ++block-obj-$(CONFIG_LINUX) += pr-manager.o +diff --git a/scsi/pr-manager.c b/scsi/pr-manager.c +new file mode 100644 +index 0000000000..87c45db5d4 +--- /dev/null ++++ b/scsi/pr-manager.c +@@ -0,0 +1,109 @@ ++/* ++ * Persistent reservation manager abstract class ++ * ++ * Copyright (c) 2017 Red Hat, Inc. ++ * ++ * Author: Paolo Bonzini ++ * ++ * This code is licensed under the LGPL. ++ * ++ */ ++ ++#include "qemu/osdep.h" ++#include ++ ++#include "qapi/error.h" ++#include "block/aio.h" ++#include "block/thread-pool.h" ++#include "scsi/pr-manager.h" ++#include "trace.h" ++ ++typedef struct PRManagerData { ++ PRManager *pr_mgr; ++ struct sg_io_hdr *hdr; ++ int fd; ++} PRManagerData; ++ ++static int pr_manager_worker(void *opaque) ++{ ++ PRManagerData *data = opaque; ++ PRManager *pr_mgr = data->pr_mgr; ++ PRManagerClass *pr_mgr_class = ++ PR_MANAGER_GET_CLASS(pr_mgr); ++ struct sg_io_hdr *hdr = data->hdr; ++ int fd = data->fd; ++ int r; ++ ++ g_free(data); ++ trace_pr_manager_run(fd, hdr->cmdp[0], hdr->cmdp[1]); ++ ++ /* The reference was taken in pr_manager_execute. */ ++ r = pr_mgr_class->run(pr_mgr, fd, hdr); ++ object_unref(OBJECT(pr_mgr)); ++ return r; ++} ++ ++ ++BlockAIOCB *pr_manager_execute(PRManager *pr_mgr, ++ AioContext *ctx, int fd, ++ struct sg_io_hdr *hdr, ++ BlockCompletionFunc *complete, ++ void *opaque) ++{ ++ PRManagerData *data = g_new(PRManagerData, 1); ++ ThreadPool *pool = aio_get_thread_pool(ctx); ++ ++ trace_pr_manager_execute(fd, hdr->cmdp[0], hdr->cmdp[1], opaque); ++ data->pr_mgr = pr_mgr; ++ data->fd = fd; ++ data->hdr = hdr; ++ ++ /* The matching object_unref is in pr_manager_worker. */ ++ object_ref(OBJECT(pr_mgr)); ++ return thread_pool_submit_aio(pool, pr_manager_worker, ++ data, complete, opaque); ++} ++ ++static const TypeInfo pr_manager_info = { ++ .parent = TYPE_OBJECT, ++ .name = TYPE_PR_MANAGER, ++ .class_size = sizeof(PRManagerClass), ++ .abstract = true, ++ .interfaces = (InterfaceInfo[]) { ++ { TYPE_USER_CREATABLE }, ++ { } ++ } ++}; ++ ++PRManager *pr_manager_lookup(const char *id, Error **errp) ++{ ++ Object *obj; ++ PRManager *pr_mgr; ++ ++ obj = object_resolve_path_component(object_get_objects_root(), id); ++ if (!obj) { ++ error_setg(errp, "No persistent reservation manager with id '%s'", id); ++ return NULL; ++ } ++ ++ pr_mgr = (PRManager *) ++ object_dynamic_cast(obj, ++ TYPE_PR_MANAGER); ++ if (!pr_mgr) { ++ error_setg(errp, ++ "Object with id '%s' is not a persistent reservation manager", ++ id); ++ return NULL; ++ } ++ ++ return pr_mgr; ++} ++ ++static void ++pr_manager_register_types(void) ++{ ++ type_register_static(&pr_manager_info); ++} ++ ++ ++type_init(pr_manager_register_types); +diff --git a/scsi/trace-events b/scsi/trace-events +new file mode 100644 +index 0000000000..45f5b6e49b +--- /dev/null ++++ b/scsi/trace-events +@@ -0,0 +1,3 @@ ++# scsi/pr-manager.c ++pr_manager_execute(int fd, int cmd, int sa, void *opaque) "fd=%d cmd=0x%02x service action=0x%02x opaque=%p" ++pr_manager_run(int fd, int cmd, int sa) "fd=%d cmd=0x%02x service action=0x%02x" +diff --git a/vl.c b/vl.c +index 8e247cc2a2..af0e6576ab 100644 +--- a/vl.c ++++ b/vl.c +@@ -2811,7 +2811,8 @@ static int machine_set_property(void *opaque, + */ + static bool object_create_initial(const char *type) + { +- if (g_str_equal(type, "rng-egd")) { ++ if (g_str_equal(type, "rng-egd") || ++ g_str_has_prefix(type, "pr-manager-")) { + return false; + } + +-- +2.13.5 + diff --git a/1013-scsi-build-qemu-pr-helper.patch b/1013-scsi-build-qemu-pr-helper.patch new file mode 100644 index 0000000..795bb75 --- /dev/null +++ b/1013-scsi-build-qemu-pr-helper.patch @@ -0,0 +1,1013 @@ +From 226e00649123536737dcd20ccb5bd3d54d074338 Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +Date: Tue, 22 Aug 2017 06:50:18 +0200 +Subject: [PATCH 13/15] scsi: build qemu-pr-helper + +Introduce a privileged helper to run persistent reservation commands. +This lets virtual machines send persistent reservations without using +CAP_SYS_RAWIO or out-of-tree patches. The helper uses Unix permissions +and SCM_RIGHTS to restrict access to processes that can access its socket +and prove that they have an open file descriptor for a raw SCSI device. + +The next patch will also correct the usage of persistent reservations +with multipath devices. + +It would also be possible to support for Linux's IOC_PR_* ioctls in +the future, to support NVMe devices. For now, however, only SCSI is +supported. + +Signed-off-by: Paolo Bonzini +--- + Makefile | 4 +- + configure | 14 +- + docs/interop/pr-helper.rst | 83 +++++ + docs/pr-manager.rst | 33 ++ + scsi/pr-helper.h | 41 +++ + scsi/qemu-pr-helper.c | 735 +++++++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 905 insertions(+), 5 deletions(-) + create mode 100644 docs/interop/pr-helper.rst + create mode 100644 scsi/pr-helper.h + create mode 100644 scsi/qemu-pr-helper.c + +diff --git a/Makefile b/Makefile +index eb831b98d1..8406aeb8cb 100644 +--- a/Makefile ++++ b/Makefile +@@ -372,6 +372,8 @@ qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o $(COMMON_LDADDS) + fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal.o fsdev/9p-iov-marshal.o $(COMMON_LDADDS) + fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap + ++scsi/qemu-pr-helper$(EXESUF): scsi/qemu-pr-helper.o scsi/utils.o $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS) ++ + qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool + $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$@") + +@@ -488,7 +490,7 @@ clean: + rm -f *.msi + find . \( -name '*.so' -o -name '*.dll' -o -name '*.mo' -o -name '*.[oda]' \) -type f -exec rm {} + + rm -f $(filter-out %.tlb,$(TOOLS)) $(HELPERS-y) qemu-ga TAGS cscope.* *.pod *~ */*~ +- rm -f fsdev/*.pod ++ rm -f fsdev/*.pod scsi/*.pod + rm -f qemu-img-cmds.h + rm -f ui/shader/*-vert.h ui/shader/*-frag.h + @# May not be present in GENERATED_FILES +diff --git a/configure b/configure +index cb0f7ed0ab..becc21a0fe 100755 +--- a/configure ++++ b/configure +@@ -5034,16 +5034,22 @@ if test "$want_tools" = "yes" ; then + fi + fi + if test "$softmmu" = yes ; then +- if test "$virtfs" != no ; then +- if test "$cap" = yes && test "$linux" = yes && test "$attr" = yes ; then ++ if test "$linux" = yes; then ++ if test "$virtfs" != no && test "$cap" = yes && test "$attr" = yes ; then + virtfs=yes + tools="$tools fsdev/virtfs-proxy-helper\$(EXESUF)" + else + if test "$virtfs" = yes; then +- error_exit "VirtFS is supported only on Linux and requires libcap devel and libattr devel" ++ error_exit "VirtFS requires libcap devel and libattr devel" + fi + virtfs=no + fi ++ tools="$tools scsi/qemu-pr-helper\$(EXESUF)" ++ else ++ if test "$virtfs" = yes; then ++ error_exit "VirtFS is supported only on Linux" ++ fi ++ virtfs=no + fi + fi + +@@ -6506,7 +6512,7 @@ fi + + # build tree in object directory in case the source is not in the current directory + DIRS="tests tests/tcg tests/tcg/cris tests/tcg/lm32 tests/libqos tests/qapi-schema tests/tcg/xtensa tests/qemu-iotests" +-DIRS="$DIRS docs docs/interop fsdev" ++DIRS="$DIRS docs docs/interop fsdev scsi" + DIRS="$DIRS pc-bios/optionrom pc-bios/spapr-rtas pc-bios/s390-ccw" + DIRS="$DIRS roms/seabios roms/vgabios" + DIRS="$DIRS qapi-generated" +diff --git a/docs/interop/pr-helper.rst b/docs/interop/pr-helper.rst +new file mode 100644 +index 0000000000..9f76d5bcf9 +--- /dev/null ++++ b/docs/interop/pr-helper.rst +@@ -0,0 +1,83 @@ ++.. ++ ++====================================== ++Persistent reservation helper protocol ++====================================== ++ ++QEMU's SCSI passthrough devices, ``scsi-block`` and ``scsi-generic``, ++can delegate implementation of persistent reservations to an external ++(and typically privileged) program. Persistent Reservations allow ++restricting access to block devices to specific initiators in a shared ++storage setup. ++ ++For a more detailed reference please refer the the SCSI Primary ++Commands standard, specifically the section on Reservations and the ++"PERSISTENT RESERVE IN" and "PERSISTENT RESERVE OUT" commands. ++ ++This document describes the socket protocol used between QEMU's ++``pr-manager-helper`` object and the external program. ++ ++.. contents:: ++ ++Connection and initialization ++----------------------------- ++ ++All data transmitted on the socket is big-endian. ++ ++After connecting to the helper program's socket, the helper starts a simple ++feature negotiation process by writing four bytes corresponding to ++the features it exposes (``supported_features``). QEMU reads it, ++then writes four bytes corresponding to the desired features of the ++helper program (``requested_features``). ++ ++If a bit is 1 in ``requested_features`` and 0 in ``supported_features``, ++the corresponding feature is not supported by the helper and the connection ++is closed. On the other hand, it is acceptable for a bit to be 0 in ++``requested_features`` and 1 in ``supported_features``; in this case, ++the helper will not enable the feature. ++ ++Right now no feature is defined, so the two parties always write four ++zero bytes. ++ ++Command format ++-------------- ++ ++It is invalid to send multiple commands concurrently on the same ++socket. It is however possible to connect multiple sockets to the ++helper and send multiple commands to the helper for one or more ++file descriptors. ++ ++A command consists of a request and a response. A request consists ++of a 16-byte SCSI CDB. A file descriptor must be passed to the helper ++together with the SCSI CDB using ancillary data. ++ ++The CDB has the following limitations: ++ ++- the command (stored in the first byte) must be one of 0x5E ++ (PERSISTENT RESERVE IN) or 0x5F (PERSISTENT RESERVE OUT). ++ ++- the allocation length (stored in bytes 7-8 of the CDB for PERSISTENT ++ RESERVE IN) or parameter list length (stored in bytes 5-8 of the CDB ++ for PERSISTENT RESERVE OUT) is limited to 8 KiB. ++ ++For PERSISTENT RESERVE OUT, the parameter list is sent right after the ++CDB. The length of the parameter list is taken from the CDB itself. ++ ++The helper's reply has the following structure: ++ ++- 4 bytes for the SCSI status ++ ++- 4 bytes for the payload size (nonzero only for PERSISTENT RESERVE IN ++ and only if the SCSI status is 0x00, i.e. GOOD) ++ ++- 96 bytes for the SCSI sense data ++ ++- if the size is nonzero, the payload follows ++ ++The sense data is always sent to keep the protocol simple, even though ++it is only valid if the SCSI status is CHECK CONDITION (0x02). ++ ++The payload size is always less than or equal to the allocation length ++specified in the CDB for the PERSISTENT RESERVE IN command. ++ ++If the protocol is violated, the helper closes the socket. +diff --git a/docs/pr-manager.rst b/docs/pr-manager.rst +index b6089fb57c..7107e59fb8 100644 +--- a/docs/pr-manager.rst ++++ b/docs/pr-manager.rst +@@ -49,3 +49,36 @@ Alternatively, using ``-blockdev``:: + -object pr-manager-helper,id=helper0,path=/var/run/qemu-pr-helper.sock + -blockdev node-name=hd,driver=raw,file.driver=host_device,file.filename=/dev/sdb,file.pr-manager=helper0 + -device scsi-block,drive=hd ++ ++---------------------------------- ++Invoking :program:`qemu-pr-helper` ++---------------------------------- ++ ++QEMU provides an implementation of the persistent reservation helper, ++called :program:`qemu-pr-helper`. The helper should be started as a ++system service and supports the following option: ++ ++-d, --daemon run in the background ++-q, --quiet decrease verbosity ++-f, --pidfile=path PID file when running as a daemon ++-k, --socket=path path to the socket ++-T, --trace=trace-opts tracing options ++ ++By default, the socket and PID file are placed in the runtime state ++directory, for example :file:`/var/run/qemu-pr-helper.sock` and ++:file:`/var/run/qemu-pr-helper.pid`. The PID file is not created ++unless :option:`-d` is passed too. ++ ++:program:`qemu-pr-helper` can also use the systemd socket activation ++protocol. In this case, the systemd socket unit should specify a ++Unix stream socket, like this:: ++ ++ [Socket] ++ ListenStream=/var/run/qemu-pr-helper.sock ++ ++After connecting to the socket, :program:`qemu-pr-helper`` can optionally drop ++root privileges, except for those capabilities that are needed for ++its operation. To do this, add the following options: ++ ++-u, --user=user user to drop privileges to ++-g, --group=group group to drop privileges to +diff --git a/scsi/pr-helper.h b/scsi/pr-helper.h +new file mode 100644 +index 0000000000..96c50a9e5f +--- /dev/null ++++ b/scsi/pr-helper.h +@@ -0,0 +1,41 @@ ++/* Definitions for QEMU's persistent reservation helper daemon ++ * ++ * Copyright (C) 2017 Red Hat, Inc. ++ * ++ * Author: ++ * Paolo Bonzini ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the "Software"), to ++ * deal in the Software without restriction, including without limitation the ++ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ++ * sell copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS ++ * IN THE SOFTWARE. ++ */ ++#ifndef QEMU_PR_HELPER_H ++#define QEMU_PR_HELPER_H 1 ++ ++#include ++ ++#define PR_HELPER_CDB_SIZE 16 ++#define PR_HELPER_SENSE_SIZE 96 ++#define PR_HELPER_DATA_SIZE 8192 ++ ++typedef struct PRHelperResponse { ++ int32_t result; ++ int32_t sz; ++ uint8_t sense[PR_HELPER_SENSE_SIZE]; ++} PRHelperResponse; ++ ++#endif +diff --git a/scsi/qemu-pr-helper.c b/scsi/qemu-pr-helper.c +new file mode 100644 +index 0000000000..e39efbd529 +--- /dev/null ++++ b/scsi/qemu-pr-helper.c +@@ -0,0 +1,735 @@ ++/* ++ * Privileged helper to handle persistent reservation commands for QEMU ++ * ++ * Copyright (C) 2017 Red Hat, Inc. ++ * ++ * Author: Paolo Bonzini ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; under version 2 of the License. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++#include "qemu/osdep.h" ++#include ++#include ++#include ++#include ++ ++#ifdef CONFIG_LIBCAP ++#include ++#endif ++#include ++#include ++ ++#include "qapi/error.h" ++#include "qemu-common.h" ++#include "qemu/cutils.h" ++#include "qemu/main-loop.h" ++#include "qemu/error-report.h" ++#include "qemu/config-file.h" ++#include "qemu/bswap.h" ++#include "qemu/log.h" ++#include "qemu/systemd.h" ++#include "qapi/util.h" ++#include "qapi/qmp/qstring.h" ++#include "io/channel-socket.h" ++#include "trace/control.h" ++#include "qemu-version.h" ++ ++#include "block/aio.h" ++#include "block/thread-pool.h" ++ ++#include "scsi/constants.h" ++#include "scsi/utils.h" ++#include "pr-helper.h" ++ ++#define PR_OUT_FIXED_PARAM_SIZE 24 ++ ++static char *socket_path; ++static char *pidfile; ++static enum { RUNNING, TERMINATE, TERMINATING } state; ++static QIOChannelSocket *server_ioc; ++static int server_watch; ++static int num_active_sockets = 1; ++static int verbose; ++ ++#ifdef CONFIG_LIBCAP ++static int uid = -1; ++static int gid = -1; ++#endif ++ ++static void usage(const char *name) ++{ ++ (printf) ( ++"Usage: %s [OPTIONS] FILE\n" ++"Persistent Reservation helper program for QEMU\n" ++"\n" ++" -h, --help display this help and exit\n" ++" -V, --version output version information and exit\n" ++"\n" ++" -d, --daemon run in the background\n" ++" -f, --pidfile=PATH PID file when running as a daemon\n" ++" (default '%s')\n" ++" -k, --socket=PATH path to the unix socket\n" ++" (default '%s')\n" ++" -T, --trace [[enable=]][,events=][,file=]\n" ++" specify tracing options\n" ++#ifdef CONFIG_LIBCAP ++" -u, --user=USER user to drop privileges to\n" ++" -g, --group=GROUP group to drop privileges to\n" ++#endif ++"\n" ++QEMU_HELP_BOTTOM "\n" ++ , name, pidfile, socket_path); ++} ++ ++static void version(const char *name) ++{ ++ printf( ++"%s " QEMU_VERSION QEMU_PKGVERSION "\n" ++"Written by Paolo Bonzini.\n" ++"\n" ++QEMU_COPYRIGHT "\n" ++"This is free software; see the source for copying conditions. There is NO\n" ++"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ++ , name); ++} ++ ++static void write_pidfile(void) ++{ ++ int pidfd; ++ char pidstr[32]; ++ ++ pidfd = qemu_open(pidfile, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR); ++ if (pidfd == -1) { ++ error_report("Cannot open pid file, %s", strerror(errno)); ++ exit(EXIT_FAILURE); ++ } ++ ++ if (lockf(pidfd, F_TLOCK, 0)) { ++ error_report("Cannot lock pid file, %s", strerror(errno)); ++ goto fail; ++ } ++ if (ftruncate(pidfd, 0)) { ++ error_report("Failed to truncate pid file"); ++ goto fail; ++ } ++ ++ snprintf(pidstr, sizeof(pidstr), "%d\n", getpid()); ++ if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) { ++ error_report("Failed to write pid file"); ++ goto fail; ++ } ++ return; ++ ++fail: ++ unlink(pidfile); ++ close(pidfd); ++ exit(EXIT_FAILURE); ++} ++ ++/* SG_IO support */ ++ ++typedef struct PRHelperSGIOData { ++ int fd; ++ const uint8_t *cdb; ++ uint8_t *sense; ++ uint8_t *buf; ++ int sz; /* input/output */ ++ int dir; ++} PRHelperSGIOData; ++ ++static int do_sgio_worker(void *opaque) ++{ ++ PRHelperSGIOData *data = opaque; ++ struct sg_io_hdr io_hdr; ++ int ret; ++ int status; ++ SCSISense sense_code; ++ ++ memset(data->sense, 0, PR_HELPER_SENSE_SIZE); ++ memset(&io_hdr, 0, sizeof(io_hdr)); ++ io_hdr.interface_id = 'S'; ++ io_hdr.cmd_len = PR_HELPER_CDB_SIZE; ++ io_hdr.cmdp = (uint8_t *)data->cdb; ++ io_hdr.sbp = data->sense; ++ io_hdr.mx_sb_len = PR_HELPER_SENSE_SIZE; ++ io_hdr.timeout = 1; ++ io_hdr.dxfer_direction = data->dir; ++ io_hdr.dxferp = (char *)data->buf; ++ io_hdr.dxfer_len = data->sz; ++ 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) { ++ data->sz -= io_hdr.resid; ++ } else { ++ data->sz = 0; ++ } ++ ++ if (status == CHECK_CONDITION && ++ !(io_hdr.driver_status & SG_ERR_DRIVER_SENSE)) { ++ scsi_build_sense(data->sense, sense_code); ++ } ++ ++ return status; ++} ++ ++static int do_sgio(int fd, const uint8_t *cdb, uint8_t *sense, ++ uint8_t *buf, int *sz, int dir) ++{ ++ ThreadPool *pool = aio_get_thread_pool(qemu_get_aio_context()); ++ int r; ++ ++ PRHelperSGIOData data = { ++ .fd = fd, ++ .cdb = cdb, ++ .sense = sense, ++ .buf = buf, ++ .sz = *sz, ++ .dir = dir, ++ }; ++ ++ r = thread_pool_submit_co(pool, do_sgio_worker, &data); ++ *sz = data.sz; ++ return r; ++} ++ ++static int do_pr_in(int fd, const uint8_t *cdb, uint8_t *sense, ++ uint8_t *data, int *resp_sz) ++{ ++ return do_sgio(fd, cdb, sense, data, resp_sz, ++ SG_DXFER_FROM_DEV); ++} ++ ++static int do_pr_out(int fd, const uint8_t *cdb, uint8_t *sense, ++ const uint8_t *param, int sz) ++{ ++ int resp_sz = sz; ++ return do_sgio(fd, cdb, sense, (uint8_t *)param, &resp_sz, ++ SG_DXFER_TO_DEV); ++} ++ ++/* Client */ ++ ++typedef struct PRHelperClient { ++ QIOChannelSocket *ioc; ++ Coroutine *co; ++ int fd; ++ uint8_t data[PR_HELPER_DATA_SIZE]; ++} PRHelperClient; ++ ++typedef struct PRHelperRequest { ++ int fd; ++ size_t sz; ++ uint8_t cdb[PR_HELPER_CDB_SIZE]; ++} PRHelperRequest; ++ ++static int coroutine_fn prh_read(PRHelperClient *client, void *buf, int sz, ++ Error **errp) ++{ ++ int ret = 0; ++ ++ while (sz > 0) { ++ int *fds = NULL; ++ size_t nfds = 0; ++ int i; ++ struct iovec iov; ++ ssize_t n_read; ++ ++ iov.iov_base = buf; ++ iov.iov_len = sz; ++ n_read = qio_channel_readv_full(QIO_CHANNEL(client->ioc), &iov, 1, ++ &fds, &nfds, errp); ++ ++ if (n_read == QIO_CHANNEL_ERR_BLOCK) { ++ qio_channel_yield(QIO_CHANNEL(client->ioc), G_IO_IN); ++ continue; ++ } ++ if (n_read <= 0) { ++ ret = n_read ? n_read : -1; ++ goto err; ++ } ++ ++ /* Stash one file descriptor per request. */ ++ if (nfds) { ++ bool too_many = false; ++ for (i = 0; i < nfds; i++) { ++ if (client->fd == -1) { ++ client->fd = fds[i]; ++ } else { ++ close(fds[i]); ++ too_many = true; ++ } ++ } ++ g_free(fds); ++ if (too_many) { ++ ret = -1; ++ goto err; ++ } ++ } ++ ++ buf += n_read; ++ sz -= n_read; ++ } ++ ++ return 0; ++ ++err: ++ if (client->fd != -1) { ++ close(client->fd); ++ client->fd = -1; ++ } ++ return ret; ++} ++ ++static int coroutine_fn prh_read_request(PRHelperClient *client, ++ PRHelperRequest *req, ++ PRHelperResponse *resp, Error **errp) ++{ ++ uint32_t sz; ++ ++ if (prh_read(client, req->cdb, sizeof(req->cdb), NULL) < 0) { ++ return -1; ++ } ++ ++ if (client->fd == -1) { ++ error_setg(errp, "No file descriptor in request."); ++ return -1; ++ } ++ ++ if (req->cdb[0] != PERSISTENT_RESERVE_OUT && ++ req->cdb[0] != PERSISTENT_RESERVE_IN) { ++ error_setg(errp, "Invalid CDB, closing socket."); ++ goto out_close; ++ } ++ ++ sz = scsi_cdb_xfer(req->cdb); ++ if (sz > sizeof(client->data)) { ++ goto out_close; ++ } ++ ++ if (req->cdb[0] == PERSISTENT_RESERVE_OUT) { ++ if (qio_channel_read_all(QIO_CHANNEL(client->ioc), ++ (char *)client->data, sz, ++ errp) < 0) { ++ goto out_close; ++ } ++ if ((fcntl(client->fd, F_GETFL) & O_ACCMODE) == O_RDONLY) { ++ scsi_build_sense(resp->sense, SENSE_CODE(INVALID_OPCODE)); ++ sz = 0; ++ } else if (sz < PR_OUT_FIXED_PARAM_SIZE) { ++ /* Illegal request, Parameter list length error. This isn't fatal; ++ * we have read the data, send an error without closing the socket. ++ */ ++ scsi_build_sense(resp->sense, SENSE_CODE(INVALID_PARAM_LEN)); ++ sz = 0; ++ } ++ if (sz == 0) { ++ resp->result = CHECK_CONDITION; ++ close(client->fd); ++ client->fd = -1; ++ } ++ } ++ ++ req->fd = client->fd; ++ req->sz = sz; ++ client->fd = -1; ++ return sz; ++ ++out_close: ++ close(client->fd); ++ client->fd = -1; ++ return -1; ++} ++ ++static int coroutine_fn prh_write_response(PRHelperClient *client, ++ PRHelperRequest *req, ++ PRHelperResponse *resp, Error **errp) ++{ ++ ssize_t r; ++ size_t sz; ++ ++ if (req->cdb[0] == PERSISTENT_RESERVE_IN && resp->result == GOOD) { ++ assert(resp->sz <= req->sz && resp->sz <= sizeof(client->data)); ++ } else { ++ assert(resp->sz == 0); ++ } ++ ++ sz = resp->sz; ++ ++ resp->result = cpu_to_be32(resp->result); ++ resp->sz = cpu_to_be32(resp->sz); ++ r = qio_channel_write_all(QIO_CHANNEL(client->ioc), ++ (char *) resp, sizeof(*resp), errp); ++ if (r < 0) { ++ return r; ++ } ++ ++ r = qio_channel_write_all(QIO_CHANNEL(client->ioc), ++ (char *) client->data, ++ sz, errp); ++ return r < 0 ? r : 0; ++} ++ ++static void coroutine_fn prh_co_entry(void *opaque) ++{ ++ PRHelperClient *client = opaque; ++ Error *local_err = NULL; ++ uint32_t flags; ++ int r; ++ ++ qio_channel_set_blocking(QIO_CHANNEL(client->ioc), ++ false, NULL); ++ qio_channel_attach_aio_context(QIO_CHANNEL(client->ioc), ++ qemu_get_aio_context()); ++ ++ /* A very simple negotiation for future extensibility. No features ++ * are defined so write 0. ++ */ ++ flags = cpu_to_be32(0); ++ r = qio_channel_write_all(QIO_CHANNEL(client->ioc), ++ (char *) &flags, sizeof(flags), NULL); ++ if (r < 0) { ++ goto out; ++ } ++ ++ r = qio_channel_read_all(QIO_CHANNEL(client->ioc), ++ (char *) &flags, sizeof(flags), NULL); ++ if (be32_to_cpu(flags) != 0 || r < 0) { ++ goto out; ++ } ++ ++ while (atomic_read(&state) == RUNNING) { ++ PRHelperRequest req; ++ PRHelperResponse resp; ++ int sz; ++ ++ sz = prh_read_request(client, &req, &resp, &local_err); ++ if (sz < 0) { ++ break; ++ } ++ ++ if (sz > 0) { ++ num_active_sockets++; ++ if (req.cdb[0] == PERSISTENT_RESERVE_OUT) { ++ r = do_pr_out(req.fd, req.cdb, resp.sense, ++ client->data, sz); ++ resp.sz = 0; ++ } else { ++ resp.sz = sizeof(client->data); ++ r = do_pr_in(req.fd, req.cdb, resp.sense, ++ client->data, &resp.sz); ++ resp.sz = MIN(resp.sz, sz); ++ } ++ num_active_sockets--; ++ close(req.fd); ++ if (r == -1) { ++ break; ++ } ++ resp.result = r; ++ } ++ ++ if (prh_write_response(client, &req, &resp, &local_err) < 0) { ++ break; ++ } ++ } ++ ++ if (local_err) { ++ if (verbose == 0) { ++ error_free(local_err); ++ } else { ++ error_report_err(local_err); ++ } ++ } ++ ++out: ++ qio_channel_detach_aio_context(QIO_CHANNEL(client->ioc)); ++ object_unref(OBJECT(client->ioc)); ++ g_free(client); ++} ++ ++static gboolean accept_client(QIOChannel *ioc, GIOCondition cond, gpointer opaque) ++{ ++ QIOChannelSocket *cioc; ++ PRHelperClient *prh; ++ ++ cioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc), ++ NULL); ++ if (!cioc) { ++ return TRUE; ++ } ++ ++ prh = g_new(PRHelperClient, 1); ++ prh->ioc = cioc; ++ prh->fd = -1; ++ prh->co = qemu_coroutine_create(prh_co_entry, prh); ++ qemu_coroutine_enter(prh->co); ++ ++ return TRUE; ++} ++ ++ ++/* ++ * Check socket parameters compatibility when socket activation is used. ++ */ ++static const char *socket_activation_validate_opts(void) ++{ ++ if (socket_path != NULL) { ++ return "Unix socket can't be set when using socket activation"; ++ } ++ ++ return NULL; ++} ++ ++static void compute_default_paths(void) ++{ ++ if (!socket_path) { ++ socket_path = qemu_get_local_state_pathname("run/qemu-pr-helper.sock"); ++ } ++} ++ ++static void termsig_handler(int signum) ++{ ++ atomic_cmpxchg(&state, RUNNING, TERMINATE); ++ qemu_notify_event(); ++} ++ ++static void close_server_socket(void) ++{ ++ assert(server_ioc); ++ ++ g_source_remove(server_watch); ++ server_watch = -1; ++ object_unref(OBJECT(server_ioc)); ++ num_active_sockets--; ++} ++ ++#ifdef CONFIG_LIBCAP ++static int drop_privileges(void) ++{ ++ /* clear all capabilities */ ++ capng_clear(CAPNG_SELECT_BOTH); ++ ++ if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, ++ CAP_SYS_RAWIO) < 0) { ++ return -1; ++ } ++ ++ /* Change user/group id, retaining the capabilities. Because file descriptors ++ * are passed via SCM_RIGHTS, we don't need supplementary groups (and in ++ * fact the helper can run as "nobody"). ++ */ ++ if (capng_change_id(uid != -1 ? uid : getuid(), ++ gid != -1 ? gid : getgid(), ++ CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)) { ++ return -1; ++ } ++ ++ return 0; ++} ++#endif ++ ++int main(int argc, char **argv) ++{ ++ const char *sopt = "hVk:fdT:u:g:q"; ++ struct option lopt[] = { ++ { "help", no_argument, NULL, 'h' }, ++ { "version", no_argument, NULL, 'V' }, ++ { "socket", required_argument, NULL, 'k' }, ++ { "pidfile", no_argument, NULL, 'f' }, ++ { "daemon", no_argument, NULL, 'd' }, ++ { "trace", required_argument, NULL, 'T' }, ++ { "user", required_argument, NULL, 'u' }, ++ { "group", required_argument, NULL, 'g' }, ++ { "quiet", no_argument, NULL, 'q' }, ++ { NULL, 0, NULL, 0 } ++ }; ++ int opt_ind = 0; ++ int quiet = 0; ++ char ch; ++ Error *local_err = NULL; ++ char *trace_file = NULL; ++ bool daemonize = false; ++ unsigned socket_activation; ++ ++ struct sigaction sa_sigterm; ++ memset(&sa_sigterm, 0, sizeof(sa_sigterm)); ++ sa_sigterm.sa_handler = termsig_handler; ++ sigaction(SIGTERM, &sa_sigterm, NULL); ++ sigaction(SIGINT, &sa_sigterm, NULL); ++ sigaction(SIGHUP, &sa_sigterm, NULL); ++ ++ signal(SIGPIPE, SIG_IGN); ++ ++ module_call_init(MODULE_INIT_TRACE); ++ module_call_init(MODULE_INIT_QOM); ++ qemu_add_opts(&qemu_trace_opts); ++ qemu_init_exec_dir(argv[0]); ++ ++ pidfile = qemu_get_local_state_pathname("run/qemu-pr-helper.pid"); ++ ++ while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) { ++ switch (ch) { ++ case 'k': ++ socket_path = optarg; ++ if (socket_path[0] != '/') { ++ error_report("socket path must be absolute"); ++ exit(EXIT_FAILURE); ++ } ++ break; ++ case 'f': ++ pidfile = optarg; ++ break; ++#ifdef CONFIG_LIBCAP ++ case 'u': { ++ unsigned long res; ++ struct passwd *userinfo = getpwnam(optarg); ++ if (userinfo) { ++ uid = userinfo->pw_uid; ++ } else if (qemu_strtoul(optarg, NULL, 10, &res) == 0 && ++ (uid_t)res == res) { ++ uid = res; ++ } else { ++ error_report("invalid user '%s'", optarg); ++ exit(EXIT_FAILURE); ++ } ++ break; ++ } ++ case 'g': { ++ unsigned long res; ++ struct group *groupinfo = getgrnam(optarg); ++ if (groupinfo) { ++ gid = groupinfo->gr_gid; ++ } else if (qemu_strtoul(optarg, NULL, 10, &res) == 0 && ++ (gid_t)res == res) { ++ gid = res; ++ } else { ++ error_report("invalid group '%s'", optarg); ++ exit(EXIT_FAILURE); ++ } ++ break; ++ } ++#else ++ case 'u': ++ case 'g': ++ error_report("-%c not supported by this %s", ch, argv[0]); ++ exit(1); ++#endif ++ case 'd': ++ daemonize = true; ++ break; ++ case 'q': ++ quiet = 1; ++ break; ++ case 'T': ++ g_free(trace_file); ++ trace_file = trace_opt_parse(optarg); ++ break; ++ case 'V': ++ version(argv[0]); ++ exit(EXIT_SUCCESS); ++ break; ++ case 'h': ++ usage(argv[0]); ++ exit(EXIT_SUCCESS); ++ break; ++ case '?': ++ error_report("Try `%s --help' for more information.", argv[0]); ++ exit(EXIT_FAILURE); ++ } ++ } ++ ++ /* set verbosity */ ++ verbose = !quiet; ++ ++ if (!trace_init_backends()) { ++ exit(EXIT_FAILURE); ++ } ++ trace_init_file(trace_file); ++ qemu_set_log(LOG_TRACE); ++ ++ socket_activation = check_socket_activation(); ++ if (socket_activation == 0) { ++ SocketAddress saddr; ++ compute_default_paths(); ++ saddr = (SocketAddress){ ++ .type = SOCKET_ADDRESS_TYPE_UNIX, ++ .u.q_unix.path = g_strdup(socket_path) ++ }; ++ server_ioc = qio_channel_socket_new(); ++ if (qio_channel_socket_listen_sync(server_ioc, &saddr, &local_err) < 0) { ++ object_unref(OBJECT(server_ioc)); ++ error_report_err(local_err); ++ return 1; ++ } ++ g_free(saddr.u.q_unix.path); ++ } else { ++ /* Using socket activation - check user didn't use -p etc. */ ++ const char *err_msg = socket_activation_validate_opts(); ++ if (err_msg != NULL) { ++ error_report("%s", err_msg); ++ exit(EXIT_FAILURE); ++ } ++ ++ /* Can only listen on a single socket. */ ++ if (socket_activation > 1) { ++ error_report("%s does not support socket activation with LISTEN_FDS > 1", ++ argv[0]); ++ exit(EXIT_FAILURE); ++ } ++ server_ioc = qio_channel_socket_new_fd(FIRST_SOCKET_ACTIVATION_FD, ++ &local_err); ++ if (server_ioc == NULL) { ++ error_report("Failed to use socket activation: %s", ++ error_get_pretty(local_err)); ++ exit(EXIT_FAILURE); ++ } ++ socket_path = NULL; ++ } ++ ++ if (qemu_init_main_loop(&local_err)) { ++ error_report_err(local_err); ++ exit(EXIT_FAILURE); ++ } ++ ++ server_watch = qio_channel_add_watch(QIO_CHANNEL(server_ioc), ++ G_IO_IN, ++ accept_client, ++ NULL, NULL); ++ ++#ifdef CONFIG_LIBCAP ++ if (drop_privileges() < 0) { ++ error_report("Failed to drop privileges: %s", strerror(errno)); ++ exit(EXIT_FAILURE); ++ } ++#endif ++ ++ if (daemonize) { ++ if (daemon(0, 0) < 0) { ++ error_report("Failed to daemonize: %s", strerror(errno)); ++ exit(EXIT_FAILURE); ++ } ++ write_pidfile(); ++ } ++ ++ state = RUNNING; ++ do { ++ main_loop_wait(false); ++ if (state == TERMINATE) { ++ state = TERMINATING; ++ close_server_socket(); ++ } ++ } while (num_active_sockets > 0); ++ ++ exit(EXIT_SUCCESS); ++} +-- +2.13.5 + diff --git a/1014-scsi-add-multipath-support-to-qemu-pr-helper.patch b/1014-scsi-add-multipath-support-to-qemu-pr-helper.patch new file mode 100644 index 0000000..252ef14 --- /dev/null +++ b/1014-scsi-add-multipath-support-to-qemu-pr-helper.patch @@ -0,0 +1,660 @@ +From 43fedb8ae2c2b3bbb43023c118be708226e38179 Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +Date: Tue, 22 Aug 2017 06:50:55 +0200 +Subject: [PATCH 14/15] scsi: add multipath support to qemu-pr-helper + +Proper support of persistent reservation for multipath devices requires +communication with the multipath daemon, so that the reservation is +registered and applied when a path comes up. The device mapper +utilities provide a library to do so; this patch makes qemu-pr-helper.c +detect multipath devices and, when one is found, delegate the operation +to libmpathpersist. + +Signed-off-by: Paolo Bonzini +--- + Makefile | 3 + + configure | 46 +++++++ + docs/pr-manager.rst | 27 ++++ + include/scsi/utils.h | 4 + + scsi/qemu-pr-helper.c | 346 +++++++++++++++++++++++++++++++++++++++++++++++++- + scsi/utils.c | 10 ++ + 6 files changed, 433 insertions(+), 3 deletions(-) + +diff --git a/Makefile b/Makefile +index 8406aeb8cb..4eb40376d2 100644 +--- a/Makefile ++++ b/Makefile +@@ -373,6 +373,9 @@ fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal + fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap + + scsi/qemu-pr-helper$(EXESUF): scsi/qemu-pr-helper.o scsi/utils.o $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS) ++ifdef CONFIG_MPATH ++scsi/qemu-pr-helper$(EXESUF): LIBS += -ludev -lmultipath -lmpathpersist ++endif + + qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool + $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"GEN","$@") +diff --git a/configure b/configure +index becc21a0fe..f6edc2a33f 100755 +--- a/configure ++++ b/configure +@@ -290,6 +290,7 @@ netmap="no" + sdl="" + sdlabi="" + virtfs="" ++mpath="" + vnc="yes" + sparse="no" + vde="" +@@ -936,6 +937,10 @@ for opt do + ;; + --enable-virtfs) virtfs="yes" + ;; ++ --disable-mpath) mpath="no" ++ ;; ++ --enable-mpath) mpath="yes" ++ ;; + --disable-vnc) vnc="no" + ;; + --enable-vnc) vnc="yes" +@@ -1479,6 +1484,7 @@ disabled with --disable-FEATURE, default is enabled if available: + vnc-png PNG compression for VNC server + cocoa Cocoa UI (Mac OS X only) + virtfs VirtFS ++ mpath Multipath persistent reservation passthrough + xen xen backend driver support + xen-pci-passthrough + brlapi BrlAPI (Braile) +@@ -3300,6 +3306,30 @@ else + fi + + ########################################## ++# libmpathpersist probe ++ ++if test "$mpath" != "no" ; then ++ cat > $TMPC < ++#include ++unsigned mpath_mx_alloc_len = 1024; ++int logsink; ++int main(void) { ++ struct udev *udev = udev_new(); ++ mpath_lib_init(udev); ++ return 0; ++} ++EOF ++ if compile_prog "" "-ludev -lmultipath -lmpathpersist" ; then ++ mpathpersist=yes ++ else ++ mpathpersist=no ++ fi ++else ++ mpathpersist=no ++fi ++ ++########################################## + # libcap probe + + if test "$cap" != "no" ; then +@@ -5044,12 +5074,24 @@ if test "$softmmu" = yes ; then + fi + virtfs=no + fi ++ if test "$mpath" != no && test "$mpathpersist" = yes ; then ++ mpath=yes ++ else ++ if test "$mpath" = yes; then ++ error_exit "Multipath requires libmpathpersist devel" ++ fi ++ mpath=no ++ fi + tools="$tools scsi/qemu-pr-helper\$(EXESUF)" + else + if test "$virtfs" = yes; then + error_exit "VirtFS is supported only on Linux" + fi + virtfs=no ++ if test "$mpath" = yes; then ++ error_exit "Multipath is supported only on Linux" ++ fi ++ mpath=no + fi + fi + +@@ -5295,6 +5337,7 @@ echo "Audio drivers $audio_drv_list" + echo "Block whitelist (rw) $block_drv_rw_whitelist" + echo "Block whitelist (ro) $block_drv_ro_whitelist" + echo "VirtFS support $virtfs" ++echo "Multipath support $mpath" + echo "VNC support $vnc" + if test "$vnc" = "yes" ; then + echo "VNC SASL support $vnc_sasl" +@@ -5738,6 +5781,9 @@ fi + if test "$virtfs" = "yes" ; then + echo "CONFIG_VIRTFS=y" >> $config_host_mak + fi ++if test "$mpath" = "yes" ; then ++ echo "CONFIG_MPATH=y" >> $config_host_mak ++fi + if test "$vhost_scsi" = "yes" ; then + echo "CONFIG_VHOST_SCSI=y" >> $config_host_mak + fi +diff --git a/docs/pr-manager.rst b/docs/pr-manager.rst +index 7107e59fb8..9b1de198b1 100644 +--- a/docs/pr-manager.rst ++++ b/docs/pr-manager.rst +@@ -60,6 +60,7 @@ system service and supports the following option: + + -d, --daemon run in the background + -q, --quiet decrease verbosity ++-v, --verbose increase verbosity + -f, --pidfile=path PID file when running as a daemon + -k, --socket=path path to the socket + -T, --trace=trace-opts tracing options +@@ -82,3 +83,29 @@ its operation. To do this, add the following options: + + -u, --user=user user to drop privileges to + -g, --group=group group to drop privileges to ++ ++--------------------------------------------- ++Multipath devices and persistent reservations ++--------------------------------------------- ++ ++Proper support of persistent reservation for multipath devices requires ++communication with the multipath daemon, so that the reservation is ++registered and applied when a path is newly discovered or becomes online ++again. :command:`qemu-pr-helper` can do this if the ``libmpathpersist`` ++library was available on the system at build time. ++ ++As of August 2017, a reservation key must be specified in ``multipath.conf`` ++for ``multipathd`` to check for persistent reservation for newly ++discovered paths or reinstated paths. The attribute can be added ++to the ``defaults`` section or the ``multipaths`` section; for example:: ++ ++ multipaths { ++ multipath { ++ wwid XXXXXXXXXXXXXXXX ++ alias yellow ++ reservation_key 0x123abc ++ } ++ } ++ ++Linking :program:`qemu-pr-helper` to ``libmpathpersist`` does not impede ++its usage on regular SCSI devices. +diff --git a/include/scsi/utils.h b/include/scsi/utils.h +index d301b31768..00a4bdb080 100644 +--- a/include/scsi/utils.h ++++ b/include/scsi/utils.h +@@ -72,10 +72,14 @@ extern const struct SCSISense sense_code_IO_ERROR; + extern const struct SCSISense sense_code_I_T_NEXUS_LOSS; + /* Command aborted, Logical Unit failure */ + extern const struct SCSISense sense_code_LUN_FAILURE; ++/* Command aborted, LUN Communication failure */ ++extern const struct SCSISense sense_code_LUN_COMM_FAILURE; + /* Command aborted, Overlapped Commands Attempted */ + extern const struct SCSISense sense_code_OVERLAPPED_COMMANDS; + /* LUN not ready, Capacity data has changed */ + extern const struct SCSISense sense_code_CAPACITY_CHANGED; ++/* Unit attention, SCSI bus reset */ ++extern const struct SCSISense sense_code_SCSI_BUS_RESET; + /* LUN not ready, Medium not present */ + extern const struct SCSISense sense_code_UNIT_ATTENTION_NO_MEDIUM; + /* Unit attention, Power on, reset or bus device reset occurred */ +diff --git a/scsi/qemu-pr-helper.c b/scsi/qemu-pr-helper.c +index e39efbd529..5f77c873e1 100644 +--- a/scsi/qemu-pr-helper.c ++++ b/scsi/qemu-pr-helper.c +@@ -30,6 +30,12 @@ + #include + #include + ++#ifdef CONFIG_MPATH ++#include ++#include ++#include ++#endif ++ + #include "qapi/error.h" + #include "qemu-common.h" + #include "qemu/cutils.h" +@@ -60,6 +66,7 @@ static enum { RUNNING, TERMINATE, TERMINATING } state; + static QIOChannelSocket *server_ioc; + static int server_watch; + static int num_active_sockets = 1; ++static int noisy; + static int verbose; + + #ifdef CONFIG_LIBCAP +@@ -204,9 +211,316 @@ static int do_sgio(int fd, const uint8_t *cdb, uint8_t *sense, + return r; + } + ++/* Device mapper interface */ ++ ++#ifdef CONFIG_MPATH ++#define CONTROL_PATH "/dev/mapper/control" ++ ++typedef struct DMData { ++ struct dm_ioctl dm; ++ uint8_t data[1024]; ++} DMData; ++ ++static int control_fd; ++ ++static void *dm_ioctl(int ioc, struct dm_ioctl *dm) ++{ ++ static DMData d; ++ memcpy(&d.dm, dm, sizeof(d.dm)); ++ QEMU_BUILD_BUG_ON(sizeof(d.data) < sizeof(struct dm_target_spec)); ++ ++ d.dm.version[0] = DM_VERSION_MAJOR; ++ d.dm.version[1] = 0; ++ d.dm.version[2] = 0; ++ d.dm.data_size = 1024; ++ d.dm.data_start = offsetof(DMData, data); ++ if (ioctl(control_fd, ioc, &d) < 0) { ++ return NULL; ++ } ++ memcpy(dm, &d.dm, sizeof(d.dm)); ++ return &d.data; ++} ++ ++static void *dm_dev_ioctl(int fd, int ioc, struct dm_ioctl *dm) ++{ ++ struct stat st; ++ int r; ++ ++ r = fstat(fd, &st); ++ if (r < 0) { ++ perror("fstat"); ++ exit(1); ++ } ++ ++ dm->dev = st.st_rdev; ++ return dm_ioctl(ioc, dm); ++} ++ ++static void dm_init(void) ++{ ++ control_fd = open(CONTROL_PATH, O_RDWR); ++ if (control_fd < 0) { ++ perror("Cannot open " CONTROL_PATH); ++ exit(1); ++ } ++ struct dm_ioctl dm = { 0 }; ++ if (!dm_ioctl(DM_VERSION, &dm)) { ++ perror("ioctl"); ++ exit(1); ++ } ++ if (dm.version[0] != DM_VERSION_MAJOR) { ++ fprintf(stderr, "Unsupported device mapper interface"); ++ exit(1); ++ } ++} ++ ++/* Variables required by libmultipath and libmpathpersist. */ ++QEMU_BUILD_BUG_ON(PR_HELPER_DATA_SIZE > MPATH_MAX_PARAM_LEN); ++unsigned mpath_mx_alloc_len = PR_HELPER_DATA_SIZE; ++int logsink; ++ ++static void multipath_pr_init(void) ++{ ++ static struct udev *udev; ++ ++ udev = udev_new(); ++ mpath_lib_init(udev); ++} ++ ++static int is_mpath(int fd) ++{ ++ struct dm_ioctl dm = { .flags = DM_NOFLUSH_FLAG }; ++ struct dm_target_spec *tgt; ++ ++ tgt = dm_dev_ioctl(fd, DM_TABLE_STATUS, &dm); ++ if (!tgt) { ++ if (errno == ENXIO) { ++ return 0; ++ } ++ perror("ioctl"); ++ exit(EXIT_FAILURE); ++ } ++ return !strncmp(tgt->target_type, "multipath", DM_MAX_TYPE_NAME); ++} ++ ++static int mpath_reconstruct_sense(int fd, int r, uint8_t *sense) ++{ ++ switch (r) { ++ case MPATH_PR_SUCCESS: ++ return GOOD; ++ case MPATH_PR_SENSE_NOT_READY: ++ case MPATH_PR_SENSE_MEDIUM_ERROR: ++ case MPATH_PR_SENSE_HARDWARE_ERROR: ++ case MPATH_PR_SENSE_ABORTED_COMMAND: ++ { ++ /* libmpathpersist ate the exact sense. Try to find it by ++ * issuing TEST UNIT READY. ++ */ ++ uint8_t cdb[6] = { TEST_UNIT_READY }; ++ int sz = 0; ++ return do_sgio(fd, cdb, sense, NULL, &sz, SG_DXFER_NONE); ++ } ++ ++ case MPATH_PR_SENSE_UNIT_ATTENTION: ++ /* Congratulations libmpathpersist, you ruined the Unit Attention... ++ * Return a heavyweight one. ++ */ ++ scsi_build_sense(sense, SENSE_CODE(SCSI_BUS_RESET)); ++ return CHECK_CONDITION; ++ case MPATH_PR_SENSE_INVALID_OP: ++ /* Only one valid sense. */ ++ scsi_build_sense(sense, SENSE_CODE(INVALID_OPCODE)); ++ return CHECK_CONDITION; ++ case MPATH_PR_ILLEGAL_REQ: ++ /* Guess. */ ++ scsi_build_sense(sense, SENSE_CODE(INVALID_PARAM)); ++ return CHECK_CONDITION; ++ case MPATH_PR_NO_SENSE: ++ scsi_build_sense(sense, SENSE_CODE(NO_SENSE)); ++ return CHECK_CONDITION; ++ ++ case MPATH_PR_RESERV_CONFLICT: ++ return RESERVATION_CONFLICT; ++ ++ case MPATH_PR_OTHER: ++ default: ++ scsi_build_sense(sense, SENSE_CODE(LUN_COMM_FAILURE)); ++ return CHECK_CONDITION; ++ } ++} ++ ++static int multipath_pr_in(int fd, const uint8_t *cdb, uint8_t *sense, ++ uint8_t *data, int sz) ++{ ++ int rq_servact = cdb[1]; ++ struct prin_resp resp; ++ size_t written; ++ int r; ++ ++ switch (rq_servact) { ++ case MPATH_PRIN_RKEY_SA: ++ case MPATH_PRIN_RRES_SA: ++ case MPATH_PRIN_RCAP_SA: ++ break; ++ case MPATH_PRIN_RFSTAT_SA: ++ /* Nobody implements it anyway, so bail out. */ ++ default: ++ /* Cannot parse any other output. */ ++ scsi_build_sense(sense, SENSE_CODE(INVALID_FIELD)); ++ return CHECK_CONDITION; ++ } ++ ++ r = mpath_persistent_reserve_in(fd, rq_servact, &resp, noisy, verbose); ++ if (r == MPATH_PR_SUCCESS) { ++ switch (rq_servact) { ++ case MPATH_PRIN_RKEY_SA: ++ case MPATH_PRIN_RRES_SA: { ++ struct prin_readdescr *out = &resp.prin_descriptor.prin_readkeys; ++ assert(sz >= 8); ++ written = MIN(out->additional_length + 8, sz); ++ stl_be_p(&data[0], out->prgeneration); ++ stl_be_p(&data[4], out->additional_length); ++ memcpy(&data[8], out->key_list, written - 8); ++ break; ++ } ++ case MPATH_PRIN_RCAP_SA: { ++ struct prin_capdescr *out = &resp.prin_descriptor.prin_readcap; ++ assert(sz >= 6); ++ written = 6; ++ stw_be_p(&data[0], out->length); ++ data[2] = out->flags[0]; ++ data[3] = out->flags[1]; ++ stw_be_p(&data[4], out->pr_type_mask); ++ break; ++ } ++ default: ++ scsi_build_sense(sense, SENSE_CODE(INVALID_OPCODE)); ++ return CHECK_CONDITION; ++ } ++ assert(written <= sz); ++ memset(data + written, 0, sz - written); ++ } ++ ++ return mpath_reconstruct_sense(fd, r, sense); ++} ++ ++static int multipath_pr_out(int fd, const uint8_t *cdb, uint8_t *sense, ++ const uint8_t *param, int sz) ++{ ++ int rq_servact = cdb[1]; ++ int rq_scope = cdb[2] >> 4; ++ int rq_type = cdb[2] & 0xf; ++ struct prout_param_descriptor paramp; ++ char transportids[PR_HELPER_DATA_SIZE]; ++ int r; ++ ++ switch (rq_servact) { ++ case MPATH_PROUT_REG_SA: ++ case MPATH_PROUT_RES_SA: ++ case MPATH_PROUT_REL_SA: ++ case MPATH_PROUT_CLEAR_SA: ++ case MPATH_PROUT_PREE_SA: ++ case MPATH_PROUT_PREE_AB_SA: ++ case MPATH_PROUT_REG_IGN_SA: ++ break; ++ case MPATH_PROUT_REG_MOV_SA: ++ /* Not supported by struct prout_param_descriptor. */ ++ default: ++ /* Cannot parse any other input. */ ++ scsi_build_sense(sense, SENSE_CODE(INVALID_FIELD)); ++ return CHECK_CONDITION; ++ } ++ ++ /* Convert input data, especially transport IDs, to the structs ++ * used by libmpathpersist (which, of course, will immediately ++ * do the opposite). ++ */ ++ memset(¶mp, 0, sizeof(paramp)); ++ memcpy(¶mp.key, ¶m[0], 8); ++ memcpy(¶mp.sa_key, ¶m[8], 8); ++ paramp.sa_flags = param[10]; ++ if (sz > PR_OUT_FIXED_PARAM_SIZE) { ++ size_t transportid_len; ++ int i, j; ++ if (sz < PR_OUT_FIXED_PARAM_SIZE + 4) { ++ scsi_build_sense(sense, SENSE_CODE(INVALID_PARAM_LEN)); ++ return CHECK_CONDITION; ++ } ++ transportid_len = ldl_be_p(¶m[24]) + PR_OUT_FIXED_PARAM_SIZE + 4; ++ if (transportid_len > sz) { ++ scsi_build_sense(sense, SENSE_CODE(INVALID_PARAM)); ++ return CHECK_CONDITION; ++ } ++ for (i = PR_OUT_FIXED_PARAM_SIZE + 4, j = 0; i < transportid_len; ) { ++ struct transportid *id = (struct transportid *) &transportids[j]; ++ int len; ++ ++ id->format_code = param[i] & 0xc0; ++ id->protocol_id = param[i] & 0x0f; ++ switch (param[i] & 0xcf) { ++ case 0: ++ /* FC transport. */ ++ if (i + 24 > transportid_len) { ++ goto illegal_req; ++ } ++ memcpy(id->n_port_name, ¶m[i + 8], 8); ++ j += offsetof(struct transportid, n_port_name[8]); ++ i += 24; ++ break; ++ case 3: ++ case 0x43: ++ /* iSCSI transport. */ ++ len = lduw_be_p(¶m[i + 2]); ++ if (len > 252 || (len & 3) || i + len + 4 > transportid_len) { ++ /* For format code 00, the standard says the maximum is 223 ++ * plus the NUL terminator. For format code 01 there is no ++ * maximum length, but libmpathpersist ignores the first ++ * byte of id->iscsi_name so our maximum is 252. ++ */ ++ goto illegal_req; ++ } ++ if (memchr(¶m[i + 4], 0, len) == NULL) { ++ goto illegal_req; ++ } ++ memcpy(id->iscsi_name, ¶m[i + 2], len + 2); ++ j += offsetof(struct transportid, iscsi_name[len + 2]); ++ i += len + 4; ++ break; ++ case 6: ++ /* SAS transport. */ ++ if (i + 24 > transportid_len) { ++ goto illegal_req; ++ } ++ memcpy(id->sas_address, ¶m[i + 4], 8); ++ j += offsetof(struct transportid, sas_address[8]); ++ i += 24; ++ break; ++ default: ++ illegal_req: ++ scsi_build_sense(sense, SENSE_CODE(INVALID_PARAM)); ++ return CHECK_CONDITION; ++ } ++ ++ paramp.trnptid_list[paramp.num_transportid++] = id; ++ } ++ } ++ ++ r = mpath_persistent_reserve_out(fd, rq_servact, rq_scope, rq_type, ++ ¶mp, noisy, verbose); ++ return mpath_reconstruct_sense(fd, r, sense); ++} ++#endif ++ + static int do_pr_in(int fd, const uint8_t *cdb, uint8_t *sense, + uint8_t *data, int *resp_sz) + { ++#ifdef CONFIG_MPATH ++ if (is_mpath(fd)) { ++ /* multipath_pr_in fills the whole input buffer. */ ++ return multipath_pr_in(fd, cdb, sense, data, *resp_sz); ++ } ++#endif ++ + return do_sgio(fd, cdb, sense, data, resp_sz, + SG_DXFER_FROM_DEV); + } +@@ -214,7 +528,14 @@ static int do_pr_in(int fd, const uint8_t *cdb, uint8_t *sense, + static int do_pr_out(int fd, const uint8_t *cdb, uint8_t *sense, + const uint8_t *param, int sz) + { +- int resp_sz = sz; ++ int resp_sz; ++#ifdef CONFIG_MPATH ++ if (is_mpath(fd)) { ++ return multipath_pr_out(fd, cdb, sense, param, sz); ++ } ++#endif ++ ++ resp_sz = sz; + return do_sgio(fd, cdb, sense, (uint8_t *)param, &resp_sz, + SG_DXFER_TO_DEV); + } +@@ -525,6 +846,14 @@ static int drop_privileges(void) + return -1; + } + ++#ifdef CONFIG_MPATH ++ /* For /dev/mapper/control ioctls */ ++ if (capng_update(CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED, ++ CAP_SYS_ADMIN) < 0) { ++ return -1; ++ } ++#endif ++ + /* Change user/group id, retaining the capabilities. Because file descriptors + * are passed via SCM_RIGHTS, we don't need supplementary groups (and in + * fact the helper can run as "nobody"). +@@ -541,7 +870,7 @@ static int drop_privileges(void) + + int main(int argc, char **argv) + { +- const char *sopt = "hVk:fdT:u:g:q"; ++ const char *sopt = "hVk:fdT:u:g:vq"; + struct option lopt[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, +@@ -551,10 +880,12 @@ int main(int argc, char **argv) + { "trace", required_argument, NULL, 'T' }, + { "user", required_argument, NULL, 'u' }, + { "group", required_argument, NULL, 'g' }, ++ { "verbose", no_argument, NULL, 'v' }, + { "quiet", no_argument, NULL, 'q' }, + { NULL, 0, NULL, 0 } + }; + int opt_ind = 0; ++ int loglevel = 1; + int quiet = 0; + char ch; + Error *local_err = NULL; +@@ -631,6 +962,9 @@ int main(int argc, char **argv) + case 'q': + quiet = 1; + break; ++ case 'v': ++ ++loglevel; ++ break; + case 'T': + g_free(trace_file); + trace_file = trace_opt_parse(optarg); +@@ -650,7 +984,8 @@ int main(int argc, char **argv) + } + + /* set verbosity */ +- verbose = !quiet; ++ noisy = !quiet && (loglevel >= 3); ++ verbose = quiet ? 0 : MIN(loglevel, 3); + + if (!trace_init_backends()) { + exit(EXIT_FAILURE); +@@ -658,6 +993,11 @@ int main(int argc, char **argv) + trace_init_file(trace_file); + qemu_set_log(LOG_TRACE); + ++#ifdef CONFIG_MPATH ++ dm_init(); ++ multipath_pr_init(); ++#endif ++ + socket_activation = check_socket_activation(); + if (socket_activation == 0) { + SocketAddress saddr; +diff --git a/scsi/utils.c b/scsi/utils.c +index fab60bdf20..5684951b12 100644 +--- a/scsi/utils.c ++++ b/scsi/utils.c +@@ -206,6 +206,11 @@ const struct SCSISense sense_code_OVERLAPPED_COMMANDS = { + .key = ABORTED_COMMAND, .asc = 0x4e, .ascq = 0x00 + }; + ++/* Command aborted, LUN Communication Failure */ ++const struct SCSISense sense_code_LUN_COMM_FAILURE = { ++ .key = ABORTED_COMMAND, .asc = 0x08, .ascq = 0x00 ++}; ++ + /* Unit attention, Capacity data has changed */ + const struct SCSISense sense_code_CAPACITY_CHANGED = { + .key = UNIT_ATTENTION, .asc = 0x2a, .ascq = 0x09 +@@ -216,6 +221,11 @@ const struct SCSISense sense_code_RESET = { + .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x00 + }; + ++/* Unit attention, SCSI bus reset */ ++const struct SCSISense sense_code_SCSI_BUS_RESET = { ++ .key = UNIT_ATTENTION, .asc = 0x29, .ascq = 0x02 ++}; ++ + /* Unit attention, No medium */ + const struct SCSISense sense_code_UNIT_ATTENTION_NO_MEDIUM = { + .key = UNIT_ATTENTION, .asc = 0x3a, .ascq = 0x00 +-- +2.13.5 + diff --git a/1015-scsi-add-persistent-reservation-manager-using-qemu-p.patch b/1015-scsi-add-persistent-reservation-manager-using-qemu-p.patch new file mode 100644 index 0000000..acc9e7f --- /dev/null +++ b/1015-scsi-add-persistent-reservation-manager-using-qemu-p.patch @@ -0,0 +1,335 @@ +From 3caf122d29ecc3317671a9f651a236e8d02e2e90 Mon Sep 17 00:00:00 2001 +From: Paolo Bonzini +Date: Mon, 21 Aug 2017 18:58:56 +0200 +Subject: [PATCH 15/15] scsi: add persistent reservation manager using + qemu-pr-helper + +This adds a concrete subclass of pr-manager that talks to qemu-pr-helper. + +Signed-off-by: Paolo Bonzini +--- + scsi/Makefile.objs | 2 +- + scsi/pr-manager-helper.c | 302 +++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 303 insertions(+), 1 deletion(-) + create mode 100644 scsi/pr-manager-helper.c + +diff --git a/scsi/Makefile.objs b/scsi/Makefile.objs +index 5496d2ae6a..4d25e476cf 100644 +--- a/scsi/Makefile.objs ++++ b/scsi/Makefile.objs +@@ -1,3 +1,3 @@ + block-obj-y += utils.o + +-block-obj-$(CONFIG_LINUX) += pr-manager.o ++block-obj-$(CONFIG_LINUX) += pr-manager.o pr-manager-helper.o +diff --git a/scsi/pr-manager-helper.c b/scsi/pr-manager-helper.c +new file mode 100644 +index 0000000000..82ff6b6123 +--- /dev/null ++++ b/scsi/pr-manager-helper.c +@@ -0,0 +1,302 @@ ++/* ++ * Persistent reservation manager that talks to qemu-pr-helper ++ * ++ * Copyright (c) 2017 Red Hat, Inc. ++ * ++ * Author: Paolo Bonzini ++ * ++ * This code is licensed under the LGPL v2.1 or later. ++ * ++ */ ++ ++#include "qemu/osdep.h" ++#include "qapi/error.h" ++#include "scsi/constants.h" ++#include "scsi/pr-manager.h" ++#include "scsi/utils.h" ++#include "io/channel.h" ++#include "io/channel-socket.h" ++#include "pr-helper.h" ++ ++#include ++ ++#define PR_MAX_RECONNECT_ATTEMPTS 5 ++ ++#define TYPE_PR_MANAGER_HELPER "pr-manager-helper" ++ ++#define PR_MANAGER_HELPER(obj) \ ++ OBJECT_CHECK(PRManagerHelper, (obj), \ ++ TYPE_PR_MANAGER_HELPER) ++ ++typedef struct PRManagerHelper { ++ /* */ ++ PRManager parent; ++ ++ char *path; ++ ++ QemuMutex lock; ++ QIOChannel *ioc; ++} PRManagerHelper; ++ ++/* Called with lock held. */ ++static int pr_manager_helper_read(PRManagerHelper *pr_mgr, ++ void *buf, int sz, Error **errp) ++{ ++ ssize_t r = qio_channel_read_all(pr_mgr->ioc, buf, sz, errp); ++ ++ if (r < 0) { ++ object_unref(OBJECT(pr_mgr->ioc)); ++ pr_mgr->ioc = NULL; ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++/* Called with lock held. */ ++static int pr_manager_helper_write(PRManagerHelper *pr_mgr, ++ int fd, ++ const void *buf, int sz, Error **errp) ++{ ++ size_t nfds = (fd != -1); ++ while (sz > 0) { ++ struct iovec iov; ++ ssize_t n_written; ++ ++ iov.iov_base = (void *)buf; ++ iov.iov_len = sz; ++ n_written = qio_channel_writev_full(QIO_CHANNEL(pr_mgr->ioc), &iov, 1, ++ nfds ? &fd : NULL, nfds, errp); ++ ++ if (n_written <= 0) { ++ assert(n_written != QIO_CHANNEL_ERR_BLOCK); ++ object_unref(OBJECT(pr_mgr->ioc)); ++ return n_written < 0 ? -EINVAL : 0; ++ } ++ ++ nfds = 0; ++ buf += n_written; ++ sz -= n_written; ++ } ++ ++ return 0; ++} ++ ++/* Called with lock held. */ ++static int pr_manager_helper_initialize(PRManagerHelper *pr_mgr, ++ Error **errp) ++{ ++ char *path = g_strdup(pr_mgr->path); ++ SocketAddress saddr = { ++ .type = SOCKET_ADDRESS_TYPE_UNIX, ++ .u.q_unix.path = path ++ }; ++ QIOChannelSocket *sioc = qio_channel_socket_new(); ++ Error *local_err = NULL; ++ ++ uint32_t flags; ++ int r; ++ ++ assert(!pr_mgr->ioc); ++ qio_channel_set_name(QIO_CHANNEL(sioc), "pr-manager-helper"); ++ qio_channel_socket_connect_sync(sioc, ++ &saddr, ++ &local_err); ++ g_free(path); ++ if (local_err) { ++ object_unref(OBJECT(sioc)); ++ error_propagate(errp, local_err); ++ return -ENOTCONN; ++ } ++ ++ qio_channel_set_delay(QIO_CHANNEL(sioc), false); ++ pr_mgr->ioc = QIO_CHANNEL(sioc); ++ ++ /* A simple feature negotation protocol, even though there is ++ * no optional feature right now. ++ */ ++ r = pr_manager_helper_read(pr_mgr, &flags, sizeof(flags), errp); ++ if (r < 0) { ++ goto out_close; ++ } ++ ++ flags = 0; ++ r = pr_manager_helper_write(pr_mgr, -1, &flags, sizeof(flags), errp); ++ if (r < 0) { ++ goto out_close; ++ } ++ ++ return 0; ++ ++out_close: ++ object_unref(OBJECT(pr_mgr->ioc)); ++ pr_mgr->ioc = NULL; ++ return r; ++} ++ ++static int pr_manager_helper_run(PRManager *p, ++ int fd, struct sg_io_hdr *io_hdr) ++{ ++ PRManagerHelper *pr_mgr = PR_MANAGER_HELPER(p); ++ ++ uint32_t len; ++ PRHelperResponse resp; ++ int ret; ++ int expected_dir; ++ int attempts; ++ uint8_t cdb[PR_HELPER_CDB_SIZE] = { 0 }; ++ ++ if (!io_hdr->cmd_len || io_hdr->cmd_len > PR_HELPER_CDB_SIZE) { ++ return -EINVAL; ++ } ++ ++ memcpy(cdb, io_hdr->cmdp, io_hdr->cmd_len); ++ assert(cdb[0] == PERSISTENT_RESERVE_OUT || cdb[0] == PERSISTENT_RESERVE_IN); ++ expected_dir = ++ (cdb[0] == PERSISTENT_RESERVE_OUT ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV); ++ if (io_hdr->dxfer_direction != expected_dir) { ++ return -EINVAL; ++ } ++ ++ len = scsi_cdb_xfer(cdb); ++ if (io_hdr->dxfer_len < len || len > PR_HELPER_DATA_SIZE) { ++ return -EINVAL; ++ } ++ ++ qemu_mutex_lock(&pr_mgr->lock); ++ ++ /* Try to reconnect while sending the CDB. */ ++ for (attempts = 0; attempts < PR_MAX_RECONNECT_ATTEMPTS; attempts++) { ++ if (!pr_mgr->ioc) { ++ ret = pr_manager_helper_initialize(pr_mgr, NULL); ++ if (ret < 0) { ++ qemu_mutex_unlock(&pr_mgr->lock); ++ g_usleep(G_USEC_PER_SEC); ++ qemu_mutex_lock(&pr_mgr->lock); ++ continue; ++ } ++ } ++ ++ ret = pr_manager_helper_write(pr_mgr, fd, cdb, ARRAY_SIZE(cdb), NULL); ++ if (ret >= 0) { ++ break; ++ } ++ } ++ if (ret < 0) { ++ goto out; ++ } ++ ++ /* After sending the CDB, any communications failure causes the ++ * command to fail. The failure is transient, retrying the command ++ * will invoke pr_manager_helper_initialize again. ++ */ ++ if (expected_dir == SG_DXFER_TO_DEV) { ++ io_hdr->resid = io_hdr->dxfer_len - len; ++ ret = pr_manager_helper_write(pr_mgr, -1, io_hdr->dxferp, len, NULL); ++ if (ret < 0) { ++ goto out; ++ } ++ } ++ ret = pr_manager_helper_read(pr_mgr, &resp, sizeof(resp), NULL); ++ if (ret < 0) { ++ goto out; ++ } ++ ++ resp.result = be32_to_cpu(resp.result); ++ resp.sz = be32_to_cpu(resp.sz); ++ if (io_hdr->dxfer_direction == SG_DXFER_FROM_DEV) { ++ assert(resp.sz <= io_hdr->dxfer_len); ++ ret = pr_manager_helper_read(pr_mgr, io_hdr->dxferp, resp.sz, NULL); ++ if (ret < 0) { ++ goto out; ++ } ++ io_hdr->resid = io_hdr->dxfer_len - resp.sz; ++ } else { ++ assert(resp.sz == 0); ++ } ++ ++ io_hdr->status = resp.result; ++ if (resp.result == CHECK_CONDITION) { ++ io_hdr->driver_status = SG_ERR_DRIVER_SENSE; ++ io_hdr->sb_len_wr = MIN(io_hdr->mx_sb_len, PR_HELPER_SENSE_SIZE); ++ memcpy(io_hdr->sbp, resp.sense, io_hdr->sb_len_wr); ++ } ++ ++out: ++ if (ret < 0) { ++ int sense_len = scsi_build_sense(io_hdr->sbp, ++ SENSE_CODE(LUN_COMM_FAILURE)); ++ io_hdr->driver_status = SG_ERR_DRIVER_SENSE; ++ io_hdr->sb_len_wr = MIN(io_hdr->mx_sb_len, sense_len); ++ io_hdr->status = CHECK_CONDITION; ++ } ++ qemu_mutex_unlock(&pr_mgr->lock); ++ return ret; ++} ++ ++static void pr_manager_helper_complete(UserCreatable *uc, Error **errp) ++{ ++ PRManagerHelper *pr_mgr = PR_MANAGER_HELPER(uc); ++ ++ qemu_mutex_lock(&pr_mgr->lock); ++ pr_manager_helper_initialize(pr_mgr, errp); ++ qemu_mutex_unlock(&pr_mgr->lock); ++} ++ ++static char *get_path(Object *obj, Error **errp) ++{ ++ PRManagerHelper *pr_mgr = PR_MANAGER_HELPER(obj); ++ ++ return g_strdup(pr_mgr->path); ++} ++ ++static void set_path(Object *obj, const char *str, Error **errp) ++{ ++ PRManagerHelper *pr_mgr = PR_MANAGER_HELPER(obj); ++ ++ g_free(pr_mgr->path); ++ pr_mgr->path = g_strdup(str); ++} ++ ++static void pr_manager_helper_instance_finalize(Object *obj) ++{ ++ PRManagerHelper *pr_mgr = PR_MANAGER_HELPER(obj); ++ ++ object_unref(OBJECT(pr_mgr->ioc)); ++ qemu_mutex_destroy(&pr_mgr->lock); ++} ++ ++static void pr_manager_helper_instance_init(Object *obj) ++{ ++ PRManagerHelper *pr_mgr = PR_MANAGER_HELPER(obj); ++ ++ qemu_mutex_init(&pr_mgr->lock); ++} ++ ++static void pr_manager_helper_class_init(ObjectClass *klass, ++ void *class_data G_GNUC_UNUSED) ++{ ++ PRManagerClass *prmgr_klass = PR_MANAGER_CLASS(klass); ++ UserCreatableClass *uc_klass = USER_CREATABLE_CLASS(klass); ++ ++ object_class_property_add_str(klass, "path", get_path, set_path, ++ &error_abort); ++ uc_klass->complete = pr_manager_helper_complete; ++ prmgr_klass->run = pr_manager_helper_run; ++} ++ ++static const TypeInfo pr_manager_helper_info = { ++ .parent = TYPE_PR_MANAGER, ++ .name = TYPE_PR_MANAGER_HELPER, ++ .instance_size = sizeof(PRManagerHelper), ++ .instance_init = pr_manager_helper_instance_init, ++ .instance_finalize = pr_manager_helper_instance_finalize, ++ .class_init = pr_manager_helper_class_init, ++}; ++ ++static void pr_manager_helper_register_types(void) ++{ ++ type_register_static(&pr_manager_helper_info); ++} ++ ++type_init(pr_manager_helper_register_types); +-- +2.13.5 + diff --git a/qemu-pr-helper.service b/qemu-pr-helper.service new file mode 100644 index 0000000..a1d27b0 --- /dev/null +++ b/qemu-pr-helper.service @@ -0,0 +1,15 @@ +[Unit] +Description=Persistent Reservation Daemon for QEMU + +[Service] +WorkingDirectory=/tmp +Type=simple +ExecStart=/usr/bin/qemu-pr-helper +PrivateTmp=yes +ProtectSystem=strict +ReadWritePaths=/var/run +RestrictAddressFamilies=AF_UNIX +Restart=always +RestartSec=0 + +[Install] diff --git a/qemu-pr-helper.socket b/qemu-pr-helper.socket new file mode 100644 index 0000000..6391c3d --- /dev/null +++ b/qemu-pr-helper.socket @@ -0,0 +1,8 @@ +[Unit] +Description=Persistent Reservation Daemon for QEMU + +[Socket] +ListenStream=/run/qemu-pr-helper.sock + +[Install] +WantedBy=multi-user.target diff --git a/qemu.spec b/qemu.spec index c25e9e2..de4b3ad 100644 --- a/qemu.spec +++ b/qemu.spec @@ -107,7 +107,7 @@ Requires: %{name}-block-ssh = %{epoch}:%{version}-%{release} Summary: QEMU is a FAST! processor emulator Name: qemu Version: 2.10.0 -Release: 1%{?rcrel}%{?dist} +Release: 3%{?rcrel}%{?dist} Epoch: 2 License: GPLv2+ and LGPLv2+ and BSD Group: Development/Tools @@ -132,6 +132,9 @@ Source11: 99-qemu-guest-agent.rules Source12: bridge.conf # qemu-kvm back compat wrapper installed as /usr/bin/qemu-kvm Source13: qemu-kvm.sh +# PR manager service +Source14: qemu-pr-helper.service +Source15: qemu-pr-helper.socket # /etc/modprobe.d/kvm.conf Source20: kvm.conf # /etc/sysctl.d/50-kvm-s390x.conf @@ -139,6 +142,22 @@ Source21: 50-kvm-s390x.conf # /etc/security/limits.d/95-kvm-ppc64-memlock.conf Source22: 95-kvm-ppc64-memlock.conf +Patch1001: 1001-io-add-new-qio_channel_-readv-writev-read-write-_all.patch +Patch1002: 1002-io-Yield-rather-than-wait-when-already-in-coroutine.patch +Patch1003: 1003-scsi-bus-correct-responses-for-INQUIRY-and-REQUEST-S.patch +Patch1004: 1004-scsi-Refactor-scsi-sense-interpreting-code.patch +Patch1005: 1005-scsi-Improve-scsi_sense_to_errno.patch +Patch1006: 1006-scsi-Introduce-scsi_sense_buf_to_errno.patch +Patch1007: 1007-scsi-rename-scsi_build_sense-to-scsi_convert_sense.patch +Patch1008: 1008-scsi-move-non-emulation-specific-code-to-scsi.patch +Patch1009: 1009-scsi-introduce-scsi_build_sense.patch +Patch1010: 1010-scsi-introduce-sg_io_sense_from_errno.patch +Patch1011: 1011-scsi-move-block-scsi.h-to-include-scsi-constants.h.patch +Patch1012: 1012-scsi-file-posix-add-support-for-persistent-reservati.patch +Patch1013: 1013-scsi-build-qemu-pr-helper.patch +Patch1014: 1014-scsi-add-multipath-support-to-qemu-pr-helper.patch +Patch1015: 1015-scsi-add-persistent-reservation-manager-using-qemu-p.patch + # documentation deps BuildRequires: texinfo # For /usr/bin/pod2man @@ -165,6 +184,9 @@ BuildRequires: libaio-devel BuildRequires: pulseaudio-libs-devel # alsa audio output BuildRequires: alsa-lib-devel +# qemu-pr-helper multipath support (requires libudev too) +BuildRequires: device-mapper-multipath-devel +BuildRequires: systemd-devel # iscsi drive support BuildRequires: libiscsi-devel # NFS drive support @@ -178,7 +200,7 @@ BuildRequires: ncurses-devel # used by 9pfs BuildRequires: libattr-devel BuildRequires: libcap-devel -# used by qemu-bridge-helper +# used by qemu-bridge-helper and qemu-pr-helper BuildRequires: libcap-ng-devel # spice usb redirection support BuildRequires: usbredir-devel >= 0.5.2 @@ -1197,6 +1219,10 @@ install -D -p -m 0644 %{_sourcedir}/kvm.conf %{buildroot}%{_sysconfdir}/modprobe install -m 0644 %{_sourcedir}/qemu-guest-agent.service %{buildroot}%{_unitdir} install -m 0644 %{_sourcedir}/99-qemu-guest-agent.rules %{buildroot}%{_udevdir} +# Install qemu-pr-helper service +install -m 0644 %{_sourcedir}/qemu-pr-helper.service %{buildroot}%{_unitdir} +install -m 0644 %{_sourcedir}/qemu-pr-helper.socket %{buildroot}%{_unitdir} + %ifarch s390x install -d %{buildroot}%{_sysconfdir}/sysctl.d install -m 0644 %{_sourcedir}/50-kvm-s390x.conf %{buildroot}%{_sysconfdir}/sysctl.d @@ -1513,6 +1539,9 @@ getent passwd qemu >/dev/null || \ %{_mandir}/man7/qemu-ga-ref.7* %{_mandir}/man7/qemu-qmp-ref.7* %{_bindir}/virtfs-proxy-helper +%{_bindir}/qemu-pr-helper +%{_unitdir}/qemu-pr-helper.service +%{_unitdir}/qemu-pr-helper.socket %attr(4755, root, root) %{_libexecdir}/qemu-bridge-helper %config(noreplace) %{_sysconfdir}/sasl2/qemu.conf %config(noreplace) %{_sysconfdir}/modprobe.d/kvm.conf @@ -2000,6 +2029,10 @@ getent passwd qemu >/dev/null || \ %changelog +* Fri Sep 22 2017 Paolo Bonzini - 2:2.10.0-3 +- Backport persistent reservation manager in preparation for SELinux work +- Fix endianness of e_type in the ppc64le binfmt (Nathaniel McCallum) + * Thu Sep 07 2017 Cole Robinson - 2:2.10.0-1 - Rebase to 2.10.0 GA