From patchwork Sat Dec 25 23:44:45 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: ronnie sahlberg X-Patchwork-Id: 76710 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) by ozlabs.org (Postfix) with ESMTP id A5921B70DF for ; Sun, 26 Dec 2010 10:52:07 +1100 (EST) Received: from localhost ([127.0.0.1]:49566 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1PWdp5-0001qo-UZ for incoming@patchwork.ozlabs.org; Sat, 25 Dec 2010 18:46:52 -0500 Received: from [140.186.70.92] (port=33504 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1PWdnS-00016Y-ND for qemu-devel@nongnu.org; Sat, 25 Dec 2010 18:45:13 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1PWdnQ-0000bh-O8 for qemu-devel@nongnu.org; Sat, 25 Dec 2010 18:45:10 -0500 Received: from mail-iw0-f173.google.com ([209.85.214.173]:47902) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1PWdnQ-0000bT-BE for qemu-devel@nongnu.org; Sat, 25 Dec 2010 18:45:08 -0500 Received: by iwn40 with SMTP id 40so8918255iwn.4 for ; Sat, 25 Dec 2010 15:45:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:received:from:to:cc:subject :date:message-id:x-mailer:in-reply-to:references; bh=MPvPIZbvBeKM6p+KUbc0Vky0bqJLcM/JpHLZyoO6qzM=; b=kbiTvftG2BrP1o2WhlBimBH8+9n5KeqHrWeALMNA3v5gr4EuZUHg1ZTEF/ediuIGzN rgC6iSAa2VGwgbPh2gsmNuIWUYUTxAzIW4cHwy3TGkYRY6eAd10UkPiOoCLKXpdXezOw ITxKAgqvnagk9BSL+7n9YLNz+EEBcQVQ8pec4= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; b=ONwZF7/dutHkdyoTNpC8AF9se29Yx1miigXrkzRhMrKNnuQOr/cxZq7EvDYy+pByCu ba+KIco6ygu4S09Mh26mDHnTih5LnrxdeE3AzIH5otoRW69GfoGck7M5KGkNelFDA3DU wRZRJHbVa8UYbkTCQxTcyuBippLrbHR4jGMJI= Received: by 10.42.241.9 with SMTP id lc9mr10807248icb.527.1293320706612; Sat, 25 Dec 2010 15:45:06 -0800 (PST) Received: from ronniesahlberg@gmail.com (CPE-124-179-18-181.lns5.ken.bigpond.net.au [124.179.18.181]) by mx.google.com with ESMTPS id gy41sm9362930ibb.23.2010.12.25.15.45.03 (version=TLSv1/SSLv3 cipher=RC4-MD5); Sat, 25 Dec 2010 15:45:06 -0800 (PST) Received: by ronniesahlberg@gmail.com (sSMTP sendmail emulation); Sun, 26 Dec 2010 10:44:59 +1100 From: Ronnie Sahlberg To: qemu-devel@nongnu.org Date: Sun, 26 Dec 2010 10:44:45 +1100 Message-Id: <1293320685-1616-2-git-send-email-ronniesahlberg@gmail.com> X-Mailer: git-send-email 1.7.3.1 In-Reply-To: <1293320685-1616-1-git-send-email-ronniesahlberg@gmail.com> References: <1293320685-1616-1-git-send-email-ronniesahlberg@gmail.com> X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 2) Cc: Ronnie Sahlberg Subject: [Qemu-devel] [PATCH] iSCSI support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This patch adds a new block driver : block.iscsi.c This driver interfaces with the multiplatform posix library for iscsi initiator/client access to iscsi devices hosted at git://github.com/sahlberg/libiscsi.git The patch adds the driver to interface with the iscsi library. It also updated the configure script to * by default, probe is libiscsi is available and if so, build qemu against libiscsi. * --enable-libiscsi Force a build against libiscsi. If libiscsi is not available the build will fail. * --disable-libiscsi Do not link against libiscsi, even if it is available. When linked with libiscsi, qemu gains support to access iscsi resources such as disks and cdrom directly, without having to make the devices visible to the host. You can specify devices using a iscsi url of the form : iscsi://[%@][:/ Example: -drive file=iscsi://10.1.1.1:3260/iqn.ronnie.test/1 -cdrom iscsi://10.1.1.1:3260/iqn.ronnie.test/2 Version 2: Changes from Blue Swirl's suggestions - Change naming of structures and typedefs to match coding style - Use CONFIG_ISCSI in the makefile to conditionally compile iscsi.c - Missing spaces added around operators - Use uint8_t instead of unsigned char for buffer pointer - Use a temporary variable for the result of long functions, to move them out from the if(...) expressions. Making the code easier to read. - Create a function sector_qemu2lun() and use it instead of performing the same conversion explicitely at several palces in the code. - Use the name opaque instead of private_data - Use memset() instead of bzero() Other changes - Use the common library function to parse the iSCSI URL - Add support for CHAP authentication Signed-off-by: Ronnie Sahlberg --- Makefile.objs | 1 + block/iscsi.c | 518 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ configure | 31 ++++ 3 files changed, 550 insertions(+), 0 deletions(-) create mode 100644 block/iscsi.c diff --git a/Makefile.objs b/Makefile.objs index d6b3d60..ee47887 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -25,6 +25,7 @@ block-nested-y += qed-check.o block-nested-y += parallels.o nbd.o blkdebug.o sheepdog.o blkverify.o block-nested-$(CONFIG_WIN32) += raw-win32.o block-nested-$(CONFIG_POSIX) += raw-posix.o +block-nested-$(CONFIG_LIBISCSI) += iscsi.o block-nested-$(CONFIG_CURL) += curl.o block-nested-$(CONFIG_RBD) += rbd.o diff --git a/block/iscsi.c b/block/iscsi.c new file mode 100644 index 0000000..16ab7f8 --- /dev/null +++ b/block/iscsi.c @@ -0,0 +1,518 @@ +/* + * QEMU Block driver for iSCSI images + * + * Copyright (c) 2010 Ronnie Sahlberg + * + * 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. + */ + +#include "config-host.h" + +#include +#include "sysemu.h" +#include "qemu-common.h" +#include "qemu-error.h" +#include "block_int.h" + +#include +#include + + +typedef struct IscsiLun { + struct iscsi_context *iscsi; + int lun; + int block_size; + unsigned long num_blocks; +} IscsiLun; + +typedef struct IscsiAIOCB { + BlockDriverAIOCB common; + QEMUIOVector *qiov; + QEMUBH *bh; + IscsiLun *iscsilun; + int canceled; + int status; + size_t read_size; +} IscsiAIOCB; + +struct IscsiTask { + IscsiLun *iscsilun; + int status; + int complete; +}; + +static int +iscsi_is_inserted(BlockDriverState *bs) +{ + IscsiLun *iscsilun = bs->opaque; + struct iscsi_context *iscsi = iscsilun->iscsi; + + return iscsi_is_logged_in(iscsi); +} + + +static void +iscsi_aio_cancel(BlockDriverAIOCB *blockacb) +{ + IscsiAIOCB *acb = (IscsiAIOCB *)blockacb; + + acb->status = -EIO; + acb->common.cb(acb->common.opaque, acb->status); + acb->canceled = 1; +} + +static AIOPool iscsi_aio_pool = { + .aiocb_size = sizeof(IscsiAIOCB), + .cancel = iscsi_aio_cancel, +}; + + +static void iscsi_process_read(void *arg); +static void iscsi_process_write(void *arg); + +static void +iscsi_set_events(IscsiLun *iscsilun) +{ + struct iscsi_context *iscsi = iscsilun->iscsi; + + qemu_aio_set_fd_handler(iscsi_get_fd(iscsi), iscsi_process_read, + (iscsi_which_events(iscsi) & POLLOUT) + ? iscsi_process_write : NULL, + NULL, NULL, iscsilun); +} + +static void +iscsi_process_read(void *arg) +{ + IscsiLun *iscsilun = arg; + struct iscsi_context *iscsi = iscsilun->iscsi; + + iscsi_service(iscsi, POLLIN); + iscsi_set_events(iscsilun); +} + +static void +iscsi_process_write(void *arg) +{ + IscsiLun *iscsilun = arg; + struct iscsi_context *iscsi = iscsilun->iscsi; + + iscsi_service(iscsi, POLLOUT); + iscsi_set_events(iscsilun); +} + + +static int +iscsi_schedule_bh(QEMUBHFunc *cb, IscsiAIOCB *acb) +{ + acb->bh = qemu_bh_new(cb, acb); + if (!acb->bh) { + error_report("oom: could not create iscsi bh"); + return -EIO; + } + + qemu_bh_schedule(acb->bh); + return 0; +} + +static void +iscsi_readv_writev_bh_cb(void *p) +{ + IscsiAIOCB *acb = p; + + acb->common.cb(acb->common.opaque, acb->status); + qemu_aio_release(acb); +} + +static void +iscsi_aio_write10_cb(struct iscsi_context *iscsi, int status, + void *command_data, void *opaque) +{ + IscsiAIOCB *acb = opaque; + + if (acb->canceled != 0) { + qemu_aio_release(acb); + return; + } + + acb->status = 0; + if (status < 0) { + error_report("Failed to write10 data to iSCSI lun. %s", + iscsi_get_error(iscsi)); + acb->status = -EIO; + } + + iscsi_schedule_bh(iscsi_readv_writev_bh_cb, acb); +} + +static int64_t sector_qemu2lun(int64_t sector, IscsiLun *iscsilun) +{ + return sector * BDRV_SECTOR_SIZE / iscsilun->block_size; +} + +static BlockDriverAIOCB * +iscsi_aio_writev(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + IscsiLun *iscsilun = bs->opaque; + struct iscsi_context *iscsi = iscsilun->iscsi; + IscsiAIOCB *acb; + size_t size; + uint8_t *buf; + int ret; + + acb = qemu_aio_get(&iscsi_aio_pool, bs, cb, opaque); + if (!acb) { + return NULL; + } + + acb->iscsilun = iscsilun; + acb->qiov = qiov; + + acb->canceled = 0; + + /* XXX we should pass the iovec to write10 to avoid the extra copy */ + /* this will allow us to get rid of 'buf' completely */ + size = nb_sectors * BDRV_SECTOR_SIZE; + buf = qemu_malloc(size); + qemu_iovec_to_buffer(acb->qiov, buf); + ret = iscsi_write10_async(iscsi, iscsilun->lun, buf, size, + sector_qemu2lun(sector_num, iscsilun), + 0, 0, iscsilun->block_size, + iscsi_aio_write10_cb, acb); + if (ret != 0) { + error_report("iSCSI: Failed to send write10 command. %s", + iscsi_get_error(iscsi)); + qemu_free(buf); + qemu_aio_release(acb); + return NULL; + } + qemu_free(buf); + iscsi_set_events(iscsilun); + + return &acb->common; +} + +static void +iscsi_aio_read10_cb(struct iscsi_context *iscsi, int status, + void *command_data, void *opaque) +{ + IscsiAIOCB *acb = opaque; + struct scsi_task *scsi = command_data; + + if (acb->canceled != 0) { + qemu_aio_release(acb); + return; + } + + acb->status = 0; + if (status < 0) { + error_report("Failed to read10 data from iSCSI lun. %s", + iscsi_get_error(iscsi)); + acb->status = -EIO; + } else { + qemu_iovec_from_buffer(acb->qiov, scsi->datain.data, acb->read_size); + } + + iscsi_schedule_bh(iscsi_readv_writev_bh_cb, acb); +} + +static BlockDriverAIOCB * +iscsi_aio_readv(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + IscsiLun *iscsilun = bs->opaque; + struct iscsi_context *iscsi = iscsilun->iscsi; + IscsiAIOCB *acb; + size_t qemu_read_size, lun_read_size; + int ret; + + qemu_read_size = BDRV_SECTOR_SIZE * (size_t)nb_sectors; + lun_read_size = (qemu_read_size + iscsilun->block_size - 1) + / iscsilun->block_size * iscsilun->block_size; + + acb = qemu_aio_get(&iscsi_aio_pool, bs, cb, opaque); + if (!acb) { + return NULL; + } + + acb->iscsilun = iscsilun; + acb->qiov = qiov; + + acb->canceled = 0; + acb->read_size = qemu_read_size; + + ret = iscsi_read10_async(iscsi, iscsilun->lun, + sector_qemu2lun(sector_num, iscsilun), + lun_read_size, iscsilun->block_size, + iscsi_aio_read10_cb, acb); + if (ret != 0) { + error_report("iSCSI: Failed to send read10 command. %s", + iscsi_get_error(iscsi)); + qemu_aio_release(acb); + return NULL; + } + iscsi_set_events(iscsilun); + + return &acb->common; +} + + +static void synccache10_cb(struct iscsi_context *iscsi, int status, + void *command_data, void *opaque) +{ + struct IscsiTask *task = opaque; + + task->status = status; + task->complete = 1; +} + +static int +iscsi_flush(BlockDriverState *bs) +{ + IscsiLun *iscsilun = bs->opaque; + struct iscsi_context *iscsi = iscsilun->iscsi; + struct IscsiTask task; + int ret; + + task.iscsilun = iscsilun; + task.status = 0; + task.complete = 0; + + ret = iscsi_synchronizecache10_async(iscsi, iscsilun->lun, + 0, 0, 0, 0, + synccache10_cb, + &task); + if (ret != 0) { + error_report("iSCSI: Failed to send SYNCHRONIZECACHE10. %s", + iscsi_get_error(iscsi)); + return -1; + } + + async_context_push(); + while (!task.complete) { + iscsi_set_events(iscsilun); + qemu_aio_wait(); + } + async_context_pop(); + if (task.status != 0) { + error_report("iSCSI: Failed to send SYNCHRONIZECACHE10: %s", + iscsi_get_error(iscsi)); + return -EIO; + } + + return 0; +} + +static int64_t +iscsi_getlength(BlockDriverState *bs) +{ + IscsiLun *iscsilun = bs->opaque; + int64_t len; + + len = iscsilun->num_blocks; + len *= iscsilun->block_size; + + return len; +} + +static void +iscsi_readcapacity10_cb(struct iscsi_context *iscsi, int status, + void *command_data, void *opaque) +{ + struct IscsiTask *task = opaque; + struct scsi_readcapacity10 *rc10; + struct scsi_task *scsi = command_data; + + if (status != 0) { + error_report("iSCSI: Failed to read capacity of iSCSI lun. %s", + iscsi_get_error(iscsi)); + task->status = 1; + task->complete = 1; + return; + } + + rc10 = scsi_datain_unmarshall(scsi); + if (rc10 == NULL) { + error_report("iSCSI: Failed to unmarshall readcapacity10 data."); + task->status = 1; + task->complete = 1; + return; + } + + task->iscsilun->block_size = rc10->block_size; + task->iscsilun->num_blocks = rc10->lba; + + task->status = 0; + task->complete = 1; +} + + +static void +iscsi_connect_cb(struct iscsi_context *iscsi, int status, void *command_data, + void *opaque) +{ + struct IscsiTask *task = opaque; + + if (status != 0) { + task->status = 1; + task->complete = 1; + return; + } + + if (iscsi_readcapacity10_async(iscsi, task->iscsilun->lun, 0, 0, + iscsi_readcapacity10_cb, opaque) + != 0) { + error_report("iSCSI: failed to send readcapacity command."); + task->status = 1; + task->complete = 1; + } +} + +/* + * We support iscsi url's on the form + * iscsi://[%@][:]// + */ +static int iscsi_open(BlockDriverState *bs, const char *filename, int flags) +{ + IscsiLun *iscsilun = bs->opaque; + struct iscsi_context *iscsi = NULL; + struct iscsi_url *iscsi_url = NULL; + struct IscsiTask task; + int ret; + + memset(iscsilun, 0, sizeof(IscsiLun)); + + /* Should really append the KVM name after the ':' here */ + iscsi = iscsi_create_context("iqn.2008-11.org.linux-kvm:"); + if (iscsi == NULL) { + error_report("iSCSI: Failed to create iSCSI context."); + ret = -ENOMEM; + goto failed; + } + + iscsi_url = iscsi_parse_full_url(iscsi, filename); + if (iscsi_url == NULL) { + error_report("Failed to parse URL : %s %s", filename, + iscsi_get_error(iscsi)); + ret = -ENOMEM; + goto failed; + } + + if (iscsi_set_targetname(iscsi, iscsi_url->target)) { + error_report("iSCSI: Failed to set target name."); + ret = -ENOMEM; + goto failed; + } + + if (iscsi_url->user != NULL) { + ret = iscsi_set_initiator_username_pwd(iscsi, iscsi_url->user, + iscsi_url->passwd); + if (ret != 0) { + error_report("Failed to set initiator username and password"); + ret = -ENOMEM; + goto failed; + } + } + if (iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL) != 0) { + error_report("iSCSI: Failed to set session type to normal."); + ret = -ENOMEM; + goto failed; + } + + iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C); + + task.iscsilun = iscsilun; + task.status = 0; + task.complete = 0; + + iscsilun->iscsi = iscsi; + iscsilun->lun = iscsi_url->lun; + + if (iscsi_full_connect_async(iscsi, iscsi_url->portal, iscsi_url->lun, + iscsi_connect_cb, &task) + != 0) { + error_report("iSCSI: Failed to start async connect."); + ret = -ENOMEM; + goto failed; + } + async_context_push(); + while (!task.complete) { + iscsi_set_events(iscsilun); + qemu_aio_wait(); + } + async_context_pop(); + if (task.status != 0) { + error_report("iSCSI: Failed to connect to LUN : %s", + iscsi_get_error(iscsi)); + ret = -EINVAL; + goto failed; + } + + return 0; + +failed: + if (iscsi_url != NULL) { + iscsi_destroy_url(iscsi_url); + } + if (iscsi != NULL) { + iscsi_destroy_context(iscsi); + } + memset(iscsilun, 0, sizeof(IscsiLun)); + return ret; +} + +static void iscsi_close(BlockDriverState *bs) +{ + IscsiLun *iscsilun = bs->opaque; + struct iscsi_context *iscsi = iscsilun->iscsi; + + qemu_aio_set_fd_handler(iscsi_get_fd(iscsi), NULL, NULL, NULL, NULL, NULL); + iscsi_destroy_context(iscsi); + memset(iscsilun, 0, sizeof(IscsiLun)); +} + +static BlockDriver bdrv_iscsi = { + .format_name = "iscsi", + .protocol_name = "iscsi", + + .instance_size = sizeof(IscsiLun), + .bdrv_file_open = iscsi_open, + .bdrv_close = iscsi_close, + .bdrv_flush = iscsi_flush, + + .bdrv_getlength = iscsi_getlength, + + .bdrv_aio_readv = iscsi_aio_readv, + .bdrv_aio_writev = iscsi_aio_writev, + + .bdrv_is_inserted = iscsi_is_inserted, +}; + +static void iscsi_block_init(void) +{ + bdrv_register(&bdrv_iscsi); +} + +block_init(iscsi_block_init); + diff --git a/configure b/configure index 47e4cf0..e7170e8 100755 --- a/configure +++ b/configure @@ -334,6 +334,7 @@ trace_backend="nop" trace_file="trace" spice="" rbd="" +libiscsi="" # OS specific if check_define __linux__ ; then @@ -650,6 +651,10 @@ for opt do ;; --enable-spice) spice="yes" ;; + --disable-libiscsi) libiscsi="no" + ;; + --enable-libiscsi) libiscsi="yes" + ;; --enable-profiler) profiler="yes" ;; --enable-cocoa) @@ -941,6 +946,8 @@ echo " Default:trace-" echo " --disable-spice disable spice" echo " --enable-spice enable spice" echo " --enable-rbd enable building the rados block device (rbd)" +echo " --disable-libiscsi disable iscsi support" +echo " --enable-libiscsi enable iscsi support" echo "" echo "NOTE: The object files are built at the place where configure is launched" exit 1 @@ -2125,6 +2132,25 @@ if compile_prog "" "" ; then fi ########################################## +# Do we have libiscsi +if test "$libiscsi" != "no" ; then + cat > $TMPC << EOF +#include +int main(void) { iscsi_create_context(""); return 0; } +EOF + if compile_prog "-Werror" "-liscsi" ; then + libiscsi="yes" + LIBS="$LIBS -liscsi" + else + if test "$libiscsi" = "yes" ; then + feature_not_found "libiscsi" + fi + libiscsi="no" + fi +fi + + +########################################## # Do we need librt cat > $TMPC < @@ -2426,6 +2452,7 @@ echo "Trace output file $trace_file-" echo "spice support $spice" echo "rbd support $rbd" echo "xfsctl support $xfs" +echo "libiscsi support $libiscsi" if test $sdl_too_old = "yes"; then echo "-> Your SDL version is too old - please upgrade to have SDL support" @@ -2692,6 +2719,10 @@ if test "$spice" = "yes" ; then echo "CONFIG_SPICE=y" >> $config_host_mak fi +if test "$libiscsi" = "yes" ; then + echo "CONFIG_LIBISCSI=y" >> $config_host_mak +fi + # XXX: suppress that if [ "$bsd" = "yes" ] ; then echo "CONFIG_BSD=y" >> $config_host_mak