/*
   iscsi-test tool support

   Copyright (C) 2012 by Lee Duncan <leeman.duncan@gmail.com>
   Copyright (C) 2014 Ronnie Sahlberg <ronniesahlberg@gmail.com>

   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.

   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 <http://www.gnu.org/licenses/>.
*/

#include "config.h"

#define _GNU_SOURCE
#include <assert.h>
#include <sys/syscall.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <inttypes.h>
#include <string.h>
#include <poll.h>
#include <fnmatch.h>
#include <errno.h>
#include <time.h>

#ifdef HAVE_SG_IO
#include <fcntl.h>
#include <sys/ioctl.h>
#include <scsi/sg.h>
#endif

#include "slist.h"
#include "iscsi.h"
#include "scsi-lowlevel.h"
#include "iscsi-private.h"
#include "iscsi-support.h"
#include "iscsi-multipath.h"


/*****************************************************************
 * globals
 *****************************************************************/
const char *initiatorname1 =
        "iqn.2007-10.com.github:sahlberg:libiscsi:iscsi-test";
const char *initiatorname2 =
        "iqn.2007-10.com.github:sahlberg:libiscsi:iscsi-test-2";

int no_medium_ascqs[3] = {
        SCSI_SENSE_ASCQ_MEDIUM_NOT_PRESENT,
        SCSI_SENSE_ASCQ_MEDIUM_NOT_PRESENT_TRAY_OPEN,
        SCSI_SENSE_ASCQ_MEDIUM_NOT_PRESENT_TRAY_CLOSED
};
int lba_oob_ascqs[1] = {
        SCSI_SENSE_ASCQ_LBA_OUT_OF_RANGE
};
int invalid_cdb_ascqs[2] = {
        SCSI_SENSE_ASCQ_INVALID_FIELD_IN_CDB,
        SCSI_SENSE_ASCQ_INVALID_FIELD_IN_PARAMETER_LIST
};
int param_list_len_err_ascqs[1] = {
        SCSI_SENSE_ASCQ_PARAMETER_LIST_LENGTH_ERROR
};
int write_protect_ascqs[3] = {
        SCSI_SENSE_ASCQ_WRITE_PROTECTED,
        SCSI_SENSE_ASCQ_HARDWARE_WRITE_PROTECTED,
        SCSI_SENSE_ASCQ_SOFTWARE_WRITE_PROTECTED
};
int sanitize_ascqs[1] = {
        SCSI_SENSE_ASCQ_SANITIZE_IN_PROGRESS
};
int removal_ascqs[1] = {
        SCSI_SENSE_ASCQ_MEDIUM_REMOVAL_PREVENTED
};
int miscompare_ascqs[1] = {
        SCSI_SENSE_ASCQ_MISCOMPARE_DURING_VERIFY
};
int too_many_desc_ascqs[2] = {
        SCSI_SENSE_ASCQ_TOO_MANY_TARGET_DESCRIPTORS,
        SCSI_SENSE_ASCQ_TOO_MANY_SEGMENT_DESCRIPTORS,
};
int unsupp_desc_code_ascqs[2] = {
        SCSI_SENSE_ASCQ_UNSUPPORTED_TARGET_DESCRIPTOR_TYPE_CODE,
        SCSI_SENSE_ASCQ_UNSUPPORTED_SEGMENT_DESCRIPTOR_TYPE_CODE
};
int copy_aborted_ascqs[3] = {
        SCSI_SENSE_ASCQ_NO_ADDL_SENSE,
        SCSI_SENSE_ASCQ_UNREACHABLE_COPY_TARGET,
        SCSI_SENSE_ASCQ_COPY_TARGET_DEVICE_NOT_REACHABLE
};

struct scsi_inquiry_standard *inq;
struct scsi_inquiry_logical_block_provisioning *inq_lbp;
struct scsi_inquiry_block_device_characteristics *inq_bdc;
struct scsi_inquiry_block_limits *inq_bl;
struct scsi_readcapacity16 *rc16;
struct scsi_report_supported_op_codes *rsop;

unsigned char *scratch;
size_t block_size;
uint64_t num_blocks;
int lbppb;
enum scsi_inquiry_peripheral_device_type device_type;
int data_loss;
int allow_sanitize;
int readonly;
int sbc3_support;
int maximum_transfer_length;

static const unsigned char zeroBlock[4096];

/**
 * Returns 1 if and only if buf[0..size-1] is zero.
 */
int all_zero(const unsigned char *buf, unsigned size)
{
        unsigned j, e;

        for (j = 0; j < size; j += e) {
                e = size - j;
                if (e > sizeof(zeroBlock))
                        e = sizeof(zeroBlock);
                if (memcmp(buf + j, zeroBlock, e) != 0)
                        return 0;
        }

        return 1;
}

static const char *scsi_status_str(int status)
{
        switch(status) {
        case SCSI_STATUS_GOOD:                 return "SUCCESS";
        case SCSI_STATUS_TIMEOUT:              return "TIMEOUT";
        case SCSI_STATUS_CHECK_CONDITION:      return "CHECK_CONDITION";
        case SCSI_STATUS_CONDITION_MET:        return "CONDITIONS_MET";
        case SCSI_STATUS_BUSY:                 return "BUSY";
        case SCSI_STATUS_RESERVATION_CONFLICT: return "RESERVATION_CONFLICT";
        case SCSI_STATUS_TASK_SET_FULL:        return "TASK_SET_FULL";
        case SCSI_STATUS_ACA_ACTIVE:           return "ACA_ACTIVE";
        case SCSI_STATUS_TASK_ABORTED:         return "TASK_ABORTED";
        }
        return "UNKNOWN";
}

/*
 * There is no agreement among the T10 committee whether a SCSI target should
 * report "invalid opcode", "invalid field in CDB" or "invalid field in
 * parameter list" if the opcode consists of two bytes. Hence accept all three
 * sense codes for two-byte opcodes. For more information see also Frederick
 * Knight, RE: INVALID COMMAND OPERATION CODE, T10 Reflector, 16 May 2008
 * (http://t10.org/ftp/t10/t10r/2008/r0805167.htm).
 */
static int status_is_invalid_opcode(struct scsi_task *task)
{
        if (task->status == SCSI_STATUS_CHECK_CONDITION
            && task->sense.key == SCSI_SENSE_ILLEGAL_REQUEST) {
                if (task->sense.ascq == SCSI_SENSE_ASCQ_INVALID_OPERATION_CODE)
                        return 1;
                switch (task->cdb[0]) {
                case SCSI_OPCODE_MAINTENANCE_IN:
                case SCSI_OPCODE_SERVICE_ACTION_IN:
                        switch (task->sense.ascq) {
                        case SCSI_SENSE_ASCQ_INVALID_FIELD_IN_CDB:
                        case SCSI_SENSE_ASCQ_INVALID_FIELD_IN_PARAMETER_LIST:
                                return !task->sense.sense_specific ||
                                        task->sense.field_pointer == 1;
                        }
                }
        }
        return 0;
}

static int check_result(const char *opcode, struct scsi_device *sdev,
                        struct scsi_task *task,
                        int status, enum scsi_sense_key key,
                        int *ascq, int num_ascq)
{
        int ascq_ok = 0;

        if (task == NULL) {
                logging(LOG_NORMAL, "[FAILED] Failed to send %s command: "
                        "%s", opcode, sdev->error_str);
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                logging(LOG_NORMAL, "[SKIPPED] %s is not implemented.",
                        opcode);
                return -2;
        }
        if (status == SCSI_STATUS_GOOD && task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                        "[FAILED] %s command failed with status 0x%x / sense key %s(0x%02x) / ASCQ %s(0x%04x)",
                        opcode, task->status,
                        scsi_sense_key_str(task->sense.key), task->sense.key,
                        scsi_sense_ascq_str(task->sense.ascq), task->sense.ascq);
                return -1;
        }
        if (status != SCSI_STATUS_GOOD && task->status == SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL, "[FAILED] %s successful but should "
                        "have failed with %s(0x%02x)/%s(0x%04x)",
                        opcode,
                        scsi_sense_key_str(key), key,
                        num_ascq ? scsi_sense_ascq_str(ascq[0]) : "NO ASCQ",
                        num_ascq ? ascq[0] : 0);
                return -1;
        }
        if (status == SCSI_STATUS_RESERVATION_CONFLICT
            && task->status != SCSI_STATUS_RESERVATION_CONFLICT) {
                logging(LOG_NORMAL, "[FAILED] %s command should have failed "
                        "with RESERVATION_CONFLICT.", opcode);
                return -1;
        }
        /* did we get any of the expected ASCQs ?*/
        if (status == SCSI_STATUS_CHECK_CONDITION) {
                int i;
                for (i = 0; i < num_ascq; i++) {
                        if (ascq[i] == task->sense.ascq) {
                                ascq_ok = 1;
                        }
                }
                if (num_ascq == 0) {
                        ascq_ok = 1;
                }
        }
        if (status == SCSI_STATUS_CHECK_CONDITION &&
            (task->status != status
             || task->sense.key  != key
             || !ascq_ok)) {
                logging(LOG_NORMAL, "[FAILED] %s failed with wrong sense. "
                        "Should have failed with %s(0x%02x)/%s(0x%04x) "
                        "but failed with Sense: %s(0x%02x)/(0x%04x)\n",
                        opcode,
                        scsi_sense_key_str(key), key,
                        num_ascq ? scsi_sense_ascq_str(ascq[0]) : "NO ASCQ",
                        num_ascq ? ascq[0] : 0,
                        sdev->error_str,
                        task->sense.key, task->sense.ascq);
                return -1;
        }
        logging(LOG_VERBOSE, "[OK] %s returned %s %s(0x%02x) %s(0x%04x)",
                opcode, scsi_status_str(status),
                scsi_sense_key_str(task->sense.key), task->sense.key,
                scsi_sense_ascq_str(task->sense.ascq), task->sense.ascq);
        return 0;
}

#ifdef HAVE_SG_IO
static size_t iov_tot_len(struct scsi_iovec *iov, int niov)
{
        size_t len = 0;
        int i;

        for (i = 0; i < niov; i++)
                len += iov[i].iov_len;
        return len;
}

static struct scsi_task *
sg_send_scsi_cmd(struct scsi_device *sdev, struct scsi_task *task)
{
        sg_io_hdr_t io_hdr;
        unsigned char sense[32];
        const unsigned int sense_len = sizeof(sense);
        char *buf;

        memset(sense, 0, sizeof(sense));
        memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
        io_hdr.interface_id = 'S';

        /* CDB */
        io_hdr.cmdp = task->cdb;
        io_hdr.cmd_len = task->cdb_size;

        /* Where to store the sense_data, if there was an error */
        io_hdr.sbp = sense;
        io_hdr.mx_sb_len = sense_len;

        /*
         * Transfer direction, either in or out. Support for bidirectional SCSI
         * transfers has been removed from the Linux kernel.
         */
        switch (task->xfer_dir) {
        case SCSI_XFER_WRITE:
                io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
                io_hdr.iovec_count = task->iovector_out.niov;
                io_hdr.dxferp = task->iovector_out.iov;
                io_hdr.dxfer_len = iov_tot_len(task->iovector_out.iov,
                                               task->iovector_out.niov);
                break;
        case SCSI_XFER_READ:
                io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
                task->datain.size = task->expxferlen;
                task->datain.data = malloc(task->datain.size);
                memset(task->datain.data, 0, task->datain.size);
                io_hdr.dxferp = task->datain.data;
                io_hdr.dxfer_len = task->datain.size;
                break;
        }

        /* SCSI timeout in ms */
        io_hdr.timeout = 5000;

        if (ioctl(sdev->sgio_fd, SG_IO, &io_hdr) < 0) {
                int err = errno;

                free(sdev->error_str);
                if (asprintf(&sdev->error_str, "SG_IO ioctl failed: %s",
                             strerror(err)) < 0)
                        sdev->error_str = NULL;
                return NULL;
        }

        task->residual_status = SCSI_RESIDUAL_NO_RESIDUAL;
        task->residual = 0;

        if (io_hdr.resid) {
                task->residual_status = SCSI_RESIDUAL_UNDERFLOW;
                task->residual = io_hdr.resid;
        }

        if (task->xfer_dir == SCSI_XFER_READ)
                task->datain.size -= task->residual;

        /* now for the error processing */
        if (io_hdr.sb_len_wr > 0) {
                task->status = SCSI_STATUS_CHECK_CONDITION;
                scsi_parse_sense_data(&task->sense, sense);
                if (asprintf(&buf, "SENSE KEY:%s(%d) ASCQ:%s(0x%04x)",
                             scsi_sense_key_str(task->sense.key),
                             task->sense.key,
                             scsi_sense_ascq_str(task->sense.ascq),
                             task->sense.ascq) < 0)
                        buf = NULL;
                free(sdev->error_str);
                sdev->error_str = buf;
                return task;
        }
        if (io_hdr.status == SCSI_STATUS_RESERVATION_CONFLICT) {
                task->status = SCSI_STATUS_RESERVATION_CONFLICT;
                free(sdev->error_str);
                sdev->error_str = strdup("Reservation Conflict");
                return task;
        }

        if (io_hdr.masked_status) {
                task->status = SCSI_STATUS_ERROR;
                task->sense.key = 0x0f;
                task->sense.ascq  = 0xffff;

                free(sdev->error_str);
                sdev->error_str = strdup("SCSI masked error");
                return NULL;
        }
        if (io_hdr.host_status) {
                task->status = SCSI_STATUS_ERROR;
                task->sense.key = 0x0f;
                task->sense.ascq  = 0xffff;

                if (asprintf(&buf, "SCSI host error. Status=0x%x",
                             io_hdr.host_status) < 0)
                        buf = NULL;
                free(sdev->error_str);
                sdev->error_str = buf;
                return task;
        }
        if (io_hdr.driver_status) {
                task->status = SCSI_STATUS_ERROR;
                task->sense.key = 0x0f;
                task->sense.ascq  = 0xffff;

                free(sdev->error_str);
                sdev->error_str = strdup("SCSI driver error");
                return NULL;
        }
        return task;
}
#endif

static struct scsi_task *send_scsi_command(struct scsi_device *sdev, struct scsi_task *task, struct iscsi_data *d)
{
        static time_t last_time = 0;

        /* We got an actual buffer from the application. Convert it to
         * a data-out iovector.
         * We need to attach the iovector to the task before calling
         * iscsi_scsi_command_sync() as iSER needs all iovectors to be setup
         * before we queue the task.
         */
        if (d != NULL && d->data != NULL) {
                struct scsi_iovec *iov;

                iov = scsi_malloc(task, sizeof(struct scsi_iovec));
                iov->iov_base = d->data;
                iov->iov_len  = d->size;
                switch (task->xfer_dir) {
                case SCSI_XFER_WRITE:
                        scsi_task_set_iov_out(task, iov, 1);
                        break;
                case SCSI_XFER_READ:
                        scsi_task_set_iov_in(task, iov, 1);
                        break;
                }
        }

        if (sdev->iscsi_url) {
                time_t current_time = time(NULL);

                free(sdev->error_str);
                sdev->error_str = NULL;
                task = iscsi_scsi_command_sync(sdev->iscsi_ctx, sdev->iscsi_lun, task, NULL);
                if (task == NULL) {
                        sdev->error_str = strdup(iscsi_get_error(sdev->iscsi_ctx));
                }

                if (current_time > last_time + 1) {
                        int i;

                        /* Device [0] is where we are doing all the I/O
                         * so it will always work the socket and respond
                         * to NOPs from the target.
                         * But we need to trigger a service event every
                         * now and then on all the other devices to ensure that
                         * we detect and respond to any NOPs.
                         */
                        for (i = 1; i < mp_num_sds; i++) {
                                iscsi_service(mp_sds[i]->iscsi_ctx, POLLIN|POLLOUT);
                        }
                        last_time = current_time;
                }

                return task;
        }

#ifdef HAVE_SG_IO
        if (sdev->sgio_dev)
                return sg_send_scsi_cmd(sdev, task);
#endif

        return NULL;
}

void logging(int level, const char *format, ...)
{
        va_list ap;
        static char message[1024];
        int ret;

        if (loglevel < level) {
                return;
        }

        if (strncmp(LOG_BLANK_LINE, format, LOG_BLANK_LINE_CMP_LEN)==0) {
                printf("\n");
                return;
        }

        va_start(ap, format);
        ret = vsnprintf(message, 1024, format, ap);
        va_end(ap);

        if (ret < 0) {
                return;
        }

        printf("    %s\n", message);
}

struct iscsi_context *
iscsi_context_login(const char *initiatorname, const char *url, int *lun)
{
        struct iscsi_context *iscsi;
        struct iscsi_url *iscsi_url;

        iscsi = iscsi_create_context(initiatorname);
        if (iscsi == NULL) {
                fprintf(stderr, "Failed to create context\n");
                return NULL;
        }

        iscsi_url = iscsi_parse_full_url(iscsi, url);
        if (iscsi_url == NULL) {
                fprintf(stderr, "Failed to parse URL: %s\n",
                        iscsi_get_error(iscsi));
                iscsi_destroy_context(iscsi);
                return NULL;
        }

        iscsi_set_targetname(iscsi, iscsi_url->target);
        iscsi_set_session_type(iscsi, ISCSI_SESSION_NORMAL);
        iscsi_set_header_digest(iscsi, ISCSI_HEADER_DIGEST_NONE_CRC32C);

        if (iscsi_url->user[0] != '\0') {
                if (iscsi_set_initiator_username_pwd(iscsi, iscsi_url->user, iscsi_url->passwd) != 0) {
                        fprintf(stderr, "Failed to set initiator username and password\n");
                        iscsi_destroy_url(iscsi_url);
                        iscsi_destroy_context(iscsi);
                        return NULL;
                }
        }

        if (iscsi_full_connect_sync(iscsi, iscsi_url->portal, iscsi_url->lun) != 0) {
                fprintf(stderr, "Login Failed. %s\n", iscsi_get_error(iscsi));
                iscsi_destroy_url(iscsi_url);
                iscsi_destroy_context(iscsi);
                return NULL;
        }
        if (lun != NULL) {
                *lun = iscsi_url->lun;
        }

        iscsi_destroy_url(iscsi_url);
        return iscsi;
}

void
wait_until_test_finished(struct iscsi_context *iscsi, struct iscsi_async_state *state)
{
        struct pollfd pfd;
        int count = 0;
        int ret;

        while (state->finished == 0) {
                pfd.fd = iscsi_get_fd(iscsi);
                pfd.events = iscsi_which_events(iscsi);

                ret = poll(&pfd, 1, 1000);
                if (ret < 0) {
                        printf("Poll failed");
                        exit(10);
                }
                if (ret == 0) {
                        if (count++ > 5) {
                                struct iscsi_pdu *pdu;

                                state->finished     = 1;
                                state->status       = SCSI_STATUS_CANCELLED;
                                state->task->status = SCSI_STATUS_CANCELLED;
                                /* this may leak memory since we don't free the pdu */
                                while ((pdu = iscsi->outqueue)) {
                                        ISCSI_LIST_REMOVE(&iscsi->outqueue, pdu);
                                }
                                while ((pdu = iscsi->waitpdu)) {
                                        ISCSI_LIST_REMOVE(&iscsi->waitpdu, pdu);
                                }
                                return;
                        }
                        continue;
                }
                if (iscsi_service(iscsi, pfd.revents) < 0) {
                        printf("iscsi_service failed with : %s\n", iscsi_get_error(iscsi));
                        break;
                }
        }
}

int
orwrite(struct scsi_device *sdev, uint64_t lba,
        uint32_t datalen, int blocksize, int wrprotect,
        int dpo, int fua, int fua_nv, int group,
        unsigned char *data,
        int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send ORWRITE (Expecting %s) LBA:%" PRIu64
                " blocks:%d wrprotect:%d dpo:%d fua:%d fua_nv:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, wrprotect,
                dpo, fua, fua_nv, group);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_orwrite(lba, datalen, blocksize, wrprotect,
                                dpo, fua, fua_nv, group);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("ORWRITE", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
prin_task(struct scsi_device *sdev, int service_action,
    int success_expected)
{
        const int buf_sz = 16384;
        struct scsi_task *task;
        int ret = 0;


        logging(LOG_VERBOSE, "Send PRIN/SA=0x%02x, expect %s", service_action,
            success_expected ? "success" : "failure");

        task = scsi_cdb_persistent_reserve_in(service_action, buf_sz);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PRIN command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                scsi_free_scsi_task(task);
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE IN is not implemented.");
                return -2;
        }

        if (success_expected) {
                if (task->status != SCSI_STATUS_GOOD) {
                        logging(LOG_NORMAL,
                            "[FAILED] PRIN/SA=0x%x failed: %s",
                            service_action, iscsi_get_error(sdev->iscsi_ctx));
                        ret = -1;
                }
        } else {
                if (task->status == SCSI_STATUS_GOOD) {
                        logging(LOG_NORMAL,
                            "[FAILED] PRIN/SA=0x%x succeeded with invalid serviceaction",
                                service_action);
                        ret = -1;
                }
        }

        scsi_free_scsi_task(task);
        task = NULL;

        return ret;
}

int
prin_read_keys(struct scsi_device *sdev,
               struct scsi_task **tp,
               struct scsi_persistent_reserve_in_read_keys **rkp,
               uint16_t allocation_len)
{
        struct scsi_persistent_reserve_in_read_keys *rk = NULL;


        logging(LOG_VERBOSE, "Send PRIN/READ_KEYS");

        *tp = scsi_cdb_persistent_reserve_in(SCSI_PERSISTENT_RESERVE_READ_KEYS,
                                             allocation_len);
        assert(*tp != NULL);

        *tp = send_scsi_command(sdev, *tp, NULL);
        if (*tp == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PRIN command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(*tp)) {
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE IN is not implemented.");
                return -2;
        }
        if ((*tp)->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PRIN command: failed with sense. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }

        rk = scsi_datain_unmarshall(*tp);
        if (rk == NULL) {
                logging(LOG_NORMAL,
                    "[FAIL] failed to unmarshall PRIN/READ_KEYS data. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (rkp != NULL)
                *rkp = rk;

        return 0;
}

int
prout_register_and_ignore(struct scsi_device *sdev,
    unsigned long long sark)
{
        struct scsi_persistent_reserve_out_basic poc;
        struct scsi_task *task;
        int ret = 0;


        /* register our reservation key with the target */
        logging(LOG_VERBOSE,
                "Send PROUT/REGISTER_AND_IGNORE to register init=%s",
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping PROUT\n");
                return -1;
        }

        memset(&poc, 0, sizeof (poc));
        poc.service_action_reservation_key = sark;
        task = scsi_cdb_persistent_reserve_out(
            SCSI_PERSISTENT_RESERVE_REGISTER_AND_IGNORE_EXISTING_KEY,
            SCSI_PERSISTENT_RESERVE_SCOPE_LU, 0, &poc);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PROUT command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                logging(LOG_NORMAL, "[SKIPPED] PROUT Not Supported");
                ret = -2;
                goto dun;
        }
        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PROUT command: failed with sense. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
        }

  dun:
        scsi_free_scsi_task(task);
        return ret;
}

int
prout_register_key(struct scsi_device *sdev,
    unsigned long long sark, unsigned long long rk)
{
        struct scsi_persistent_reserve_out_basic poc;
        struct scsi_task *task;
        int ret = 0;


        /* register/unregister our reservation key with the target */

        logging(LOG_VERBOSE, "Send PROUT/REGISTER to %s init=%s",
                sark != 0 ? "register" : "unregister",
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping PROUT\n");
                return -1;
        }

        memset(&poc, 0, sizeof (poc));
        poc.service_action_reservation_key = sark;
        poc.reservation_key = rk;
        task = scsi_cdb_persistent_reserve_out(
            SCSI_PERSISTENT_RESERVE_REGISTER,
            SCSI_PERSISTENT_RESERVE_SCOPE_LU, 0, &poc);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PROUT command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                scsi_free_scsi_task(task);
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE OUT is not implemented.");
                return -2;
        }
        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PROUT command: failed with sense: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
        }

        scsi_free_scsi_task(task);

        return ret;
}

int
prin_verify_key_presence(struct scsi_device *sdev,
    unsigned long long key, int present)
{
        struct scsi_task *task;
        const int buf_sz = 16384;
        int i;
        int key_found;
        struct scsi_persistent_reserve_in_read_keys *rk = NULL;
        int ret = 0;


        logging(LOG_VERBOSE,
                "Send PRIN/READ_KEYS to verify key %s init=%s... ",
                present ? "present" : "absent",
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        task = scsi_cdb_persistent_reserve_in(SCSI_PERSISTENT_RESERVE_READ_KEYS,
            buf_sz);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PRIN command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                scsi_free_scsi_task(task);
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE IN is not implemented.");
                return -2;
        }

        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PRIN command: failed with sense. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
                goto dun;
        }

        rk = scsi_datain_unmarshall(task);
        if (rk == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] failed to unmarshall PRIN/READ_KEYS data. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
                goto dun;
        }

        key_found = 0;
        for (i = 0; i < rk->num_keys; i++) {
                if (rk->keys[i] == key)
                        key_found = 1;
        }

        if ((present && !key_found) || (!present && key_found)) {
                if (present)
                        logging(LOG_NORMAL,
                            "[FAILED] Key found when none expected");
                else
                        logging(LOG_NORMAL,
                            "[FAILED] Key not found when expected");
                ret = -1;
        }

  dun:
        scsi_free_scsi_task(task);
        return ret;
}

int
prout_reregister_key_fails(struct scsi_device *sdev,
    unsigned long long sark)
{
        struct scsi_persistent_reserve_out_basic poc;
        struct scsi_task *task;
        int ret = 0;


        logging(LOG_VERBOSE,
                "Send PROUT/REGISTER to ensure reregister fails init=%s",
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping PROUT\n");
                return -1;
        }

        memset(&poc, 0, sizeof (poc));
        poc.service_action_reservation_key = sark;
        task = scsi_cdb_persistent_reserve_out(
            SCSI_PERSISTENT_RESERVE_REGISTER,
            SCSI_PERSISTENT_RESERVE_SCOPE_LU, 0, &poc);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PROUT command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                scsi_free_scsi_task(task);
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE OUT is not implemented.");
                return -2;
        }

        if (task->status != SCSI_STATUS_RESERVATION_CONFLICT) {
                logging(LOG_NORMAL,
                    "[FAILED] Expected RESERVATION CONFLICT");
                ret = -1;
        }

        scsi_free_scsi_task(task);
        return ret;
}

int
prout_reserve(struct scsi_device *sdev,
    unsigned long long key, enum scsi_persistent_out_type pr_type)
{
        struct scsi_persistent_reserve_out_basic poc;
        struct scsi_task *task;
        int ret = 0;


        /* reserve the target using specified reservation type */
        logging(LOG_VERBOSE,
                "Send PROUT/RESERVE to reserve, type=%d (%s) init=%s",
                pr_type, scsi_pr_type_str(pr_type),
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping PROUT\n");
                return -1;
        }

        memset(&poc, 0, sizeof (poc));
        poc.reservation_key = key;
        task = scsi_cdb_persistent_reserve_out(
            SCSI_PERSISTENT_RESERVE_RESERVE,
            SCSI_PERSISTENT_RESERVE_SCOPE_LU,
            pr_type, &poc);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PROUT command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                scsi_free_scsi_task(task);
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE OUT is not implemented.");
                return -2;
        }

        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PROUT command: failed with sense. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
        }

        scsi_free_scsi_task(task);
        return ret;
}

int
prout_release(struct scsi_device *sdev,
    unsigned long long key, enum scsi_persistent_out_type pr_type)
{
        struct scsi_persistent_reserve_out_basic poc;
        struct scsi_task *task;
        int ret = 0;


        logging(LOG_VERBOSE,
                "Send PROUT/RELEASE to release reservation, type=%d init=%s",
                pr_type, sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping PROUT\n");
                return -1;
        }

        memset(&poc, 0, sizeof (poc));
        poc.reservation_key = key;
        task = scsi_cdb_persistent_reserve_out(
            SCSI_PERSISTENT_RESERVE_RELEASE,
            SCSI_PERSISTENT_RESERVE_SCOPE_LU,
            pr_type, &poc);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PROUT command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                scsi_free_scsi_task(task);
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE OUT is not implemented.");
                return -2;
        }

        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PROUT command: failed with sense. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
        }

        scsi_free_scsi_task(task);
        return ret;
}

int
prout_clear(struct scsi_device *sdev, unsigned long long key)
{
        struct scsi_persistent_reserve_out_basic poc;
        struct scsi_task *task;
        int ret = 0;

        /* reserve the target using specified reservation type */
        logging(LOG_VERBOSE,
                "Send PROUT/CLEAR to clear all registrations and any PR "
                "reservation");

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping PROUT\n");
                return -1;
        }

        memset(&poc, 0, sizeof (poc));
        poc.reservation_key = key;
        task = scsi_cdb_persistent_reserve_out(
            SCSI_PERSISTENT_RESERVE_CLEAR,
            SCSI_PERSISTENT_RESERVE_SCOPE_LU,
            0, &poc);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PROUT command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                scsi_free_scsi_task(task);
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE OUT is not implemented.");
                return -2;
        }

        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PROUT command: failed with sense. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
        }

        scsi_free_scsi_task(task);
        return ret;
}

int
prout_preempt(struct scsi_device *sdev,
              unsigned long long sark, unsigned long long rk,
              enum scsi_persistent_out_type pr_type)
{
        struct scsi_persistent_reserve_out_basic poc;
        struct scsi_task *task;
        int ret = 0;

        /* reserve the target using specified reservation type */
        logging(LOG_VERBOSE,
                "Send PROUT/PREEMPT to preempt reservation and/or "
                "registration");

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping PROUT\n");
                return -1;
        }

        memset(&poc, 0, sizeof (poc));
        poc.reservation_key = rk;
        poc.service_action_reservation_key = sark;
        task = scsi_cdb_persistent_reserve_out(
            SCSI_PERSISTENT_RESERVE_PREEMPT,
            SCSI_PERSISTENT_RESERVE_SCOPE_LU,
            pr_type, &poc);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PROUT command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                scsi_free_scsi_task(task);
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE OUT is not implemented.");
                return -2;
        }

        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PROUT command: failed with sense. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
        }

        scsi_free_scsi_task(task);
        return ret;
}

int
prin_verify_reserved_as(struct scsi_device *sdev,
    unsigned long long key, enum scsi_persistent_out_type pr_type)
{
        struct scsi_task *task;
        const int buf_sz = 16384;
        struct scsi_persistent_reserve_in_read_reservation *rr = NULL;
        int ret = 0;


        logging(LOG_VERBOSE,
                "Send PRIN/READ_RESERVATION to verify type=%d init=%s... ",
                pr_type, sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        task = scsi_cdb_persistent_reserve_in(
            SCSI_PERSISTENT_RESERVE_READ_RESERVATION, buf_sz);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PRIN command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                scsi_free_scsi_task(task);
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE IN is not implemented.");
                return -2;
        }

        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PRIN command: failed with sense: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
                goto dun;
        }
        rr = scsi_datain_unmarshall(task);
        if (rr == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to unmarshall PRIN/READ_RESERVATION data. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
                goto dun;
        }
        if (!rr->reserved) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to find Target reserved as expected.");
                ret = -1;
                goto dun;
        }
        if (rr->reservation_key != key) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to find reservation key 0x%llx: found 0x%"
                    PRIu64 ".",
                    key, rr->reservation_key);
                ret = -1;
                goto dun;
        }
        if (rr->pr_type != pr_type) {
                logging(LOG_NORMAL,
                    "Failed to find reservation type %d: found %d.",
                    pr_type, rr->pr_type);
                return -1;
                ret = -1;
                goto dun;
        }

  dun:
        /* ??? free rr? */
        scsi_free_scsi_task(task);
        return ret;
}

int
prin_verify_not_reserved(struct scsi_device *sdev)
{
        struct scsi_task *task;
        const int buf_sz = 16384;
        struct scsi_persistent_reserve_in_read_reservation *rr = NULL;
        int ret = 0;


        logging(LOG_VERBOSE,
                "Send PRIN/READ_RESERVATION to verify not reserved init=%s",
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        task = scsi_cdb_persistent_reserve_in(
            SCSI_PERSISTENT_RESERVE_READ_RESERVATION, buf_sz);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PRIN command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                scsi_free_scsi_task(task);
                logging(LOG_NORMAL, "[SKIPPED] PERSISTENT RESERVE IN is not implemented.");
                return -2;
        }

        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PRIN command: failed with sense: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
                goto dun;
        }
        rr = scsi_datain_unmarshall(task);
        if (rr == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to unmarshall PRIN/READ_RESERVATION data: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
                goto dun;
        }
        if (rr->reserved) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to find Target not reserved as expected.");
                ret = -1;
                goto dun;
        }

  dun:
        /* ??? free rr? */
        scsi_free_scsi_task(task);
        return ret;
}

int
prin_report_caps(struct scsi_device *sdev, struct scsi_task **tp,
        struct scsi_persistent_reserve_in_report_capabilities **_rcaps)
{
        const int buf_sz = 16384;
        struct scsi_persistent_reserve_in_report_capabilities *rcaps = NULL;

        logging(LOG_VERBOSE, "Send PRIN/REPORT_CAPABILITIES");

        *tp = scsi_cdb_persistent_reserve_in(
                                SCSI_PERSISTENT_RESERVE_REPORT_CAPABILITIES,
                                buf_sz);
        assert(*tp != NULL);

        *tp = send_scsi_command(sdev, *tp, NULL);
        if (*tp == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send PRIN command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(*tp)) {
                logging(LOG_NORMAL,
                        "[SKIPPED] PERSISTENT RESERVE IN is not implemented.");
                return -2;
        }
        if ((*tp)->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] PRIN command: failed with sense. %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }

        rcaps = scsi_datain_unmarshall(*tp);
        if (rcaps == NULL) {
                logging(LOG_NORMAL,
                        "[FAIL] failed to unmarshall PRIN/REPORT_CAPABILITIES "
                        "data. %s", iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (_rcaps != NULL)
                *_rcaps = rcaps;

        return 0;
}

int
verify_read_works(struct scsi_device *sdev, unsigned char *buf)
{
        struct scsi_task *task;
        const uint32_t lba = 1;
        const uint32_t datalen = 1 * block_size;
        int ret = 0;


        /*
         * try to read the second block
         */

        logging(LOG_VERBOSE, "Send READ10 to verify READ works init=%s",
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        task = scsi_cdb_read10(lba, datalen, block_size, 0, 0, 0, 0, 0);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send READ10 command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }

        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] READ10 command: failed with sense: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
                goto dun;
        }
        memcpy(buf, task->datain.data, task->datain.size);

  dun:
        scsi_free_scsi_task(task);
        return ret;
}

int
verify_write_works(struct scsi_device *sdev, unsigned char *buf)
{
        struct scsi_task *task;
        struct iscsi_data d;
        const uint32_t lba = 1;
        const uint32_t datalen = 1 * block_size;
        int ret = 0;


        /*
         * try to write the second block
         */

        logging(LOG_VERBOSE, "Send WRITE10 to verify WRITE works init=%s",
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        task = scsi_cdb_write10(lba, datalen, block_size, 0, 0, 0, 0, 0);
        assert(task != NULL);

        d.data = buf;
        d.size = datalen;

        task = send_scsi_command(sdev, task, &d);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send WRITE10 command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] WRITE10 command: failed with sense: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                ret = -1;
        }
        scsi_free_scsi_task(task);
        return ret;
}

int
verify_read_fails(struct scsi_device *sdev, unsigned char *buf)
{
        struct scsi_task *task;
        const uint32_t lba = 1;
        const uint32_t datalen = 1 * block_size;
        int ret = 0;


        /*
         * try to read the second block -- should fail
         */

        logging(LOG_VERBOSE,
                "Send READ10 to verify READ does not work init=%s",
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        task = scsi_cdb_read10(lba, datalen, block_size, 0, 0, 0, 0, 0);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send READ10 command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }

        if (task->status == SCSI_STATUS_GOOD) {
                memcpy(buf, task->datain.data, task->datain.size);
                logging(LOG_NORMAL,
                    "[FAILED] READ10 command succeeded when expected to fail");
                ret = -1;
                goto dun;
        }

        /*
         * XXX should we verify sense data?
         */

  dun:
        scsi_free_scsi_task(task);
        return ret;
}

int
verify_write_fails(struct scsi_device *sdev, unsigned char *buf)
{
        struct scsi_task *task;
        struct iscsi_data d;
        const uint32_t lba = 1;
        const uint32_t datalen = 1 * block_size;
        int ret = 0;


        /*
         * try to write the second block
         */

        logging(LOG_VERBOSE,
                "Send WRITE10 to verify WRITE does not work init=%s",
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        task = scsi_cdb_write10(lba, datalen, block_size, 0, 0, 0, 0, 0);
        assert(task != NULL);

        d.data = buf;
        d.size = datalen;

        task = send_scsi_command(sdev, task, &d);
        if (task == NULL) {
                logging(LOG_NORMAL,
                    "[FAILED] Failed to send WRITE10 command: %s",
                    iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }

        if (task->status == SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                    "[FAILED] WRITE10 command: succeeded when exptec to fail");
                ret = -1;
                goto dun;
        }

        /*
         * XXX should we verify sense data?
         */

  dun:
        scsi_free_scsi_task(task);
        return ret;
}

int
synchronizecache10(struct scsi_device *sdev, uint32_t lba, int num, int sync_nv, int immed, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send SYNCHRONIZECACHE10 (Expecting %s) LBA:%d"
                " blocks:%d sync_nv:%d immed:%d",
                scsi_status_str(status),
                lba, num, sync_nv, immed);

        task = scsi_cdb_synchronizecache10(lba, num_blocks, sync_nv, immed);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("SYNCHRONIZECACHE10", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
synchronizecache16(struct scsi_device *sdev, uint64_t lba, int num, int sync_nv, int immed, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send SYNCHRONIZECACHE16 (Expecting %s) LBA:%"
                PRIu64 " blocks:%d sync_nv:%d immed:%d",
                scsi_status_str(status),
                lba, num, sync_nv, immed);

        task = scsi_cdb_synchronizecache16(lba, num_blocks, sync_nv, immed);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("SYNCHRONIZECACHE16", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int sanitize(struct scsi_device *sdev, int immed, int ause, int sa, int param_len, struct iscsi_data *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send SANITIZE (Expecting %s) IMMED:%d AUSE:%d "
                "SA:%d PARAM_LEN:%d",
                scsi_status_str(status),
                immed, ause, sa, param_len);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping sanitize\n");
                return -1;
        }

        task = scsi_cdb_sanitize(immed, ause, sa, param_len);

        assert(task != NULL);

        task = send_scsi_command(sdev, task, data);

        ret = check_result("SANITIZE", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int startstopunit(struct scsi_device *sdev, int immed, int pcm, int pc, int no_flush, int loej, int start, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send STARTSTOPUNIT (Expecting %s) IMMED:%d "
                "PCM:%d PC:%d NO_FLUSH:%d LOEJ:%d START:%d",
                scsi_status_str(status),
                immed, pcm, pc, no_flush, loej, start);

        task = scsi_cdb_startstopunit(immed, pcm, pc, no_flush, loej, start);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("STARTSTOPUNIT", sdev, task, status, key, ascq,
                           num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
testunitready(struct scsi_device *sdev, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send TESTUNITREADY (Expecting %s)",
                scsi_status_str(status));

        task = scsi_cdb_testunitready();
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("TESTUNITREADY", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
testunitready_clear_ua(struct scsi_device *sdev)
{
        struct scsi_task *task;
        int ret = -1;

        logging(LOG_VERBOSE,
                "Send TESTUNITREADY (To Clear Possible UA) init=%s",
                sdev->iscsi_ctx ? sdev->iscsi_ctx->initiator_name : sdev->sgio_dev);

        task = scsi_cdb_testunitready();
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL,
                        "[FAILED] Failed to send TESTUNITREADY command: %s",
                        iscsi_get_error(sdev->iscsi_ctx));
                goto out;
        }
        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL,
                        "[INFO] TESTUNITREADY command: failed with sense. %s",
                        iscsi_get_error(sdev->iscsi_ctx));
                goto out;
        }
        logging(LOG_VERBOSE, "[OK] TESTUNITREADY does not return unit "
                "attention.");
        ret = 0;

out:
        scsi_free_scsi_task(task);
        return ret;
}

/*
 * Returns -1 if allocating a SCSI task failed or if a communication error
 * occurred and a SCSI status if a SCSI response has been received.
 */
int modesense6(struct scsi_device *sdev, struct scsi_task **out_task, int dbd, enum scsi_modesense_page_control pc, enum scsi_modesense_page_code page_code, int sub_page_code, unsigned char alloc_len, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send MODESENSE6 (Expecting %s) ",
                scsi_status_str(status));

        task = scsi_cdb_modesense6(dbd, pc, page_code, sub_page_code, alloc_len);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("MODESENSE6", sdev, task, status, key, ascq, num_ascq);
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int modeselect6(struct scsi_device *sdev, int pf, int sp, struct scsi_mode_page *mp, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;
        struct scsi_data *data;
        struct iscsi_data d;

        logging(LOG_VERBOSE, "Send MODESELECT6 (Expecting %s) ",
                scsi_status_str(status));

        task = scsi_cdb_modeselect6(pf, sp, 255);
        assert(task != NULL);

        data = scsi_modesense_dataout_marshall(task, mp, 1);
        if (data == NULL) {
                logging(LOG_VERBOSE, "Failed to marshall MODESELECT6 data");
                scsi_free_scsi_task(task);
                return -1;
        }

        d.data = data->data;
        d.size = data->size;
        task->cdb[4] = data->size;
        task->expxferlen = data->size;

        task = send_scsi_command(sdev, task, &d);

        ret = check_result("MODESELECT6", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int compareandwrite(struct scsi_device *sdev, uint64_t lba,
    unsigned char *data, uint32_t datalen, int blocksize,
    int wrprotect, int dpo,
    int fua, int group_number,
    int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send COMPAREANDWRITE (Expecting %s) LBA:%"
                PRIu64 " LEN:%d WRPROTECT:%d",
                scsi_status_str(status),
                lba, datalen, wrprotect);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_compareandwrite(lba, datalen, blocksize, wrprotect,
                                        dpo, fua, 0, group_number);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("COMPAREANDWRITE", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int get_lba_status(struct scsi_device *sdev, struct scsi_task **out_task, uint64_t lba, uint32_t len, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send GET_LBA_STATUS (Expecting %s) LBA:%" PRIu64
                " alloc_len:%d",
                scsi_status_str(status),
                lba, len);

        task = scsi_cdb_get_lba_status(lba, len);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("GET_LBA_STATUS", sdev, task, status, key, ascq, num_ascq);
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
prefetch10(struct scsi_device *sdev, uint32_t lba, int num, int immed, int group, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send PREFETCH10 (Expecting %s) LBA:%d blocks:%d"
                " immed:%d group:%d",
                scsi_status_str(status),
                lba, num, immed, group);

        task = scsi_cdb_prefetch10(lba, num, immed, group);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("PREFETCH10", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
prefetch16(struct scsi_device *sdev, uint64_t lba, int num, int immed, int group, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send PREFETCH16 (Expecting %s) LBA:%" PRIu64
                " blocks:%d immed:%d group:%d",
                scsi_status_str(status),
                lba, num, immed, group);

        task = scsi_cdb_prefetch16(lba, num, immed, group);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("PREFETCH16", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
preventallow(struct scsi_device *sdev, int prevent)
{
        struct scsi_task *task;

        logging(LOG_VERBOSE, "Send PREVENTALLOW prevent:%d", prevent);
        task = scsi_cdb_preventallow(prevent);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);
        if (task == NULL) {
                logging(LOG_NORMAL, "[FAILED] Failed to send PREVENTALLOW "
                        "command: %s", iscsi_get_error(sdev->iscsi_ctx));
                return -1;
        }
        if (status_is_invalid_opcode(task)) {
                logging(LOG_NORMAL, "[SKIPPED] PREVENTALLOW is not implemented on target");
                scsi_free_scsi_task(task);
                return -2;
        }
        if (task->status != SCSI_STATUS_GOOD) {
                logging(LOG_NORMAL, "[FAILED] PREVENTALLOW command: "
                        "failed with sense. %s", iscsi_get_error(sdev->iscsi_ctx));
                scsi_free_scsi_task(task);
                return -1;
        }

        scsi_free_scsi_task(task);
        logging(LOG_VERBOSE, "[OK] PREVENTALLOW returned SUCCESS.");
        return 0;
}

int
read6(struct scsi_device *sdev, struct scsi_task **out_task, uint32_t lba,
      uint32_t datalen, int blocksize,
      unsigned char *data,
      int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send READ6 (Expecting %s) LBA:%d blocks:%d",
                scsi_status_str(status),
                lba, datalen / blocksize);

        task = scsi_cdb_read6(lba, datalen, blocksize);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("READ6", sdev, task, status, key, ascq, num_ascq);
        if (data && task) {
                memcpy(data, task->datain.data, task->datain.size);
        }
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
read10(struct scsi_device *sdev, struct scsi_task **out_task,
       uint32_t lba,
       uint32_t datalen, int blocksize, int rdprotect,
       int dpo, int fua, int fua_nv, int group,
       unsigned char *data,
       int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send READ10 (Expecting %s) LBA:%d"
                " blocks:%d rdprotect:%d dpo:%d fua:%d fua_nv:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, rdprotect,
                dpo, fua, fua_nv, group);

        task = scsi_cdb_read10(lba, datalen, blocksize, rdprotect,
                                dpo, fua, fua_nv, group);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("READ10", sdev, task, status, key, ascq, num_ascq);
        if (data && task) {
                memcpy(data, task->datain.data, task->datain.size);
        }
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
read12(struct scsi_device *sdev, struct scsi_task **out_task,
       uint32_t lba,
       uint32_t datalen, int blocksize, int rdprotect,
       int dpo, int fua, int fua_nv, int group,
       unsigned char *data,
       int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send READ12 (Expecting %s) LBA:%d"
                " blocks:%d rdprotect:%d dpo:%d fua:%d fua_nv:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, rdprotect,
                dpo, fua, fua_nv, group);

        task = scsi_cdb_read12(lba, datalen, blocksize, rdprotect,
                                dpo, fua, fua_nv, group);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("READ12", sdev, task, status, key, ascq, num_ascq);
        if (data && task) {
                memcpy(data, task->datain.data, task->datain.size);
        }
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
read16(struct scsi_device *sdev, struct scsi_task **out_task,
       uint64_t lba,
       uint32_t datalen, int blocksize, int rdprotect,
       int dpo, int fua, int fua_nv, int group,
       unsigned char *data,
       int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send READ16 (Expecting %s) LBA:%" PRIu64
                " blocks:%d rdprotect:%d dpo:%d fua:%d fua_nv:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, rdprotect,
                dpo, fua, fua_nv, group);

        task = scsi_cdb_read16(lba, datalen, blocksize, rdprotect,
                                dpo, fua, fua_nv, group);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("READ16", sdev, task, status, key, ascq, num_ascq);
        if (data && task) {
                memcpy(data, task->datain.data, task->datain.size);
        }
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
readcapacity10(struct scsi_device *sdev, struct scsi_task **out_task, uint32_t lba, int pmi, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send READCAPACITY10 (Expecting %s) LBA:%d"
                " pmi:%d",
                scsi_status_str(status),
                lba, pmi);

        task = scsi_cdb_readcapacity10(lba, pmi);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("READCAPACITY10", sdev, task, status, key, ascq, num_ascq);
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
readcapacity16(struct scsi_device *sdev, struct scsi_task **out_task, int alloc_len, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send READCAPACITY16 (Expecting %s)",
                scsi_status_str(status));

        task = scsi_cdb_serviceactionin16(SCSI_READCAPACITY16, alloc_len);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("READCAPACITY16", sdev, task, status, key, ascq, num_ascq);
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
readdefectdata10(struct scsi_device *sdev, struct scsi_task **out_task,
                 int req_plist, int req_glist,
                 int defect_list_format, uint16_t alloc_len,
                 int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send READDEFECTDATA10 (Expecting %s)"
                " req_plist:%d req_glist:%d format:%d",
                scsi_status_str(status),
                req_plist, req_glist, defect_list_format);

        task = scsi_cdb_readdefectdata10(req_plist, req_glist,
                                         defect_list_format, alloc_len);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("READDEFECTDATA10", sdev, task,
                           status, key, ascq, num_ascq);
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
readdefectdata12(struct scsi_device *sdev, struct scsi_task **out_task,
                 int req_plist, int req_glist,
                 int defect_list_format,
                 uint32_t address_descriptor_index,
                 uint32_t alloc_len,
                 int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send READDEFECTDATA12 (Expecting %s)"
                " req_plist:%d req_glist:%d format:%d",
                scsi_status_str(status),
                req_plist, req_glist, defect_list_format);

        task = scsi_cdb_readdefectdata12(req_plist, req_glist,
                                         defect_list_format,
                                         address_descriptor_index,
                                         alloc_len);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("READDEFECTDATA12", sdev, task,
                           status, key, ascq, num_ascq);
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
release6(struct scsi_device *sdev)
{
        struct scsi_task *task;
        int i, res = -1;

        logging(LOG_VERBOSE, "Send RELEASE6");

        for (i = 0; i < 3 && res != 0; ++i) {
                task = scsi_cdb_release6();
                assert(task != NULL);

                task = send_scsi_command(sdev, task, NULL);
                if (task == NULL) {
                        logging(LOG_NORMAL,
                                "[FAILED] Failed to send RELEASE6 command: %s",
                                iscsi_get_error(sdev->iscsi_ctx));
                        res = -1;
                        break;
                }
                if (task->status != SCSI_STATUS_GOOD &&
                    !(task->status        == SCSI_STATUS_CHECK_CONDITION
                      && task->sense.key  == SCSI_SENSE_UNIT_ATTENTION
                      && task->sense.ascq == SCSI_SENSE_ASCQ_BUS_RESET)) {
                        logging(LOG_NORMAL, "[FAILED] RELEASE6 command: "
                                "failed with sense. %s",
                                iscsi_get_error(sdev->iscsi_ctx));
                        res = -1;
                } else {
                        res = 0;
                }
                scsi_free_scsi_task(task);
        }

        if (res == 0)
                logging(LOG_VERBOSE, "[OK] RELEASE6 returned SUCCESS.");
        return res;
}

int report_supported_opcodes(struct scsi_device *sdev, struct scsi_task **out_task, int rctd, int options, int opcode, int sa, int alloc_len, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send REPORT_SUPPORTED_OPCODE (Expecting %s) "
                "RCTD:%d OPTIONS:%d OPCODE:0x%02x SA:%d ALLOC_LEN:%d",
                scsi_status_str(status),
                rctd, options, opcode, sa, alloc_len);

        task = scsi_cdb_report_supported_opcodes(rctd, options, opcode, sa,
                                                 alloc_len);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("REPORT_SUPPORTED_OPCODES", sdev, task, status, key, ascq, num_ascq);
        if (out_task && ret != -2 /* Not Supported */) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
reserve6(struct scsi_device *sdev)
{
        struct scsi_task *task;
        int i, res = -1;

        logging(LOG_VERBOSE, "Send RESERVE6");

        for (i = 0; i < 3 && res != 0; ++i) {
                task = scsi_cdb_reserve6();
                assert(task != NULL);

                task = send_scsi_command(sdev, task, NULL);
                if (task == NULL) {
                        logging(LOG_NORMAL,
                                "[FAILED] Failed to send RESERVE6 command: %s",
                                iscsi_get_error(sdev->iscsi_ctx));
                        res = -1;
                        break;
                }
                if (status_is_invalid_opcode(task)) {
                        logging(LOG_NORMAL, "[SKIPPED] RESERVE6 is not "
                                "implemented on target");
                        res = -2;
                } else if (task->status != SCSI_STATUS_GOOD &&
                    !(task->status        == SCSI_STATUS_CHECK_CONDITION
                      && task->sense.key  == SCSI_SENSE_UNIT_ATTENTION
                      && task->sense.ascq == SCSI_SENSE_ASCQ_BUS_RESET)) {
                        logging(LOG_NORMAL, "[FAILED] RESERVE6 command: "
                                "failed with sense. %s",
                                iscsi_get_error(sdev->iscsi_ctx));
                        res = -1;
                } else {
                        res = 0;
                }
                scsi_free_scsi_task(task);
        }

        if (res == 0)
                logging(LOG_VERBOSE, "[OK] RESERVE6 returned SUCCESS.");
        return res;
}

int
reserve6_conflict(struct scsi_device *sdev)
{
        struct scsi_task *task;
        int i, res = -1;

        logging(LOG_VERBOSE, "Send RESERVE6 (Expecting RESERVATION_CONFLICT)");

        for (i = 0; i < 3 && res != 0; ++i) {
                task = scsi_cdb_reserve6();
                assert(task != NULL);

                task = send_scsi_command(sdev, task, NULL);
                if (task == NULL) {
                        logging(LOG_NORMAL,
                                "[FAILED] Failed to send RESERVE6 command: %s",
                                iscsi_get_error(sdev->iscsi_ctx));
                        res = -1;
                        break;
                }
                if (status_is_invalid_opcode(task)) {
                        logging(LOG_NORMAL, "[SKIPPED] RESERVE6 is not"
                                " implemented on target");
                        res = -2;
                } else if (task->status != SCSI_STATUS_RESERVATION_CONFLICT &&
                    !(task->status        == SCSI_STATUS_CHECK_CONDITION
                      && task->sense.key  == SCSI_SENSE_UNIT_ATTENTION
                      && task->sense.ascq == SCSI_SENSE_ASCQ_BUS_RESET)) {
                        logging(LOG_NORMAL, "[FAILED] RESERVE6 command: "
                                "should have failed with RESERVATION_CONFLICT");
                        res = -1;
                } else {
                        res = 0;
                }
                scsi_free_scsi_task(task);
        }

        if (res == 0)
                logging(LOG_VERBOSE,
                        "[OK] RESERVE6 returned RESERVATION_CONFLICT.");
        return res;
}

int
unmap(struct scsi_device *sdev, int anchor, struct unmap_list *list, int list_len, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        unsigned char *data;
        struct iscsi_data d;
        int xferlen;
        int i;
        int ret;

        logging(LOG_VERBOSE, "Send UNMAP (Expecting %s) list_len:%d anchor:%d",
                scsi_status_str(status),
                list_len, anchor);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping unmap\n");
                return -1;
        }

        xferlen = 8 + list_len * 16;

        task = scsi_cdb_unmap(anchor, 0, xferlen);
        assert(task != NULL);

        data = scsi_malloc(task, xferlen);
        if (data == NULL) {
                logging(LOG_NORMAL, "Out-of-memory: Failed to create "
                        "unmap parameters.");
                scsi_free_scsi_task(task);
                return -1;
        }

        scsi_set_uint16(&data[0], xferlen - 2);
        scsi_set_uint16(&data[2], xferlen - 8);
        for (i = 0; i < list_len; i++) {
                scsi_set_uint32(&data[8 + 16 * i], list[i].lba >> 32);
                scsi_set_uint32(&data[8 + 16 * i + 4], list[i].lba & 0xffffffff);
                scsi_set_uint32(&data[8 + 16 * i + 8], list[i].num);
        }

        d.data = data;
        d.size = xferlen;

        task = send_scsi_command(sdev, task, &d);

        ret = check_result("UNMAP", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
verify10(struct scsi_device *sdev, uint32_t lba, uint32_t datalen, int blocksize, int vprotect, int dpo, int bytchk, unsigned char *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)

{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send VERIFY10 (Expecting %s) LBA:%d "
                "blocks:%d vprotect:%d dpo:%d bytchk:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, vprotect, dpo, bytchk);

        task = scsi_cdb_verify10(lba, datalen, vprotect, dpo, bytchk, blocksize);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("VERIFY10", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
verify12(struct scsi_device *sdev, uint32_t lba, uint32_t datalen, int blocksize, int vprotect, int dpo, int bytchk, unsigned char *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send VERIFY12 (Expecting %s) LBA:%d "
                "blocks:%d vprotect:%d dpo:%d bytchk:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, vprotect, dpo, bytchk);

        task = scsi_cdb_verify12(lba, datalen, vprotect, dpo, bytchk, blocksize);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("VERIFY12", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
verify16(struct scsi_device *sdev, uint64_t lba, uint32_t datalen, int blocksize, int vprotect, int dpo, int bytchk, unsigned char *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send VERIFY16 (Expecting %s) LBA:%" PRIu64
                " blocks:%d vprotect:%d dpo:%d bytchk:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, vprotect, dpo, bytchk);

        task = scsi_cdb_verify16(lba, datalen, vprotect, dpo, bytchk, blocksize);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("VERIFY16", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
write10(struct scsi_device *sdev, uint32_t lba, uint32_t datalen, int blocksize, int wrprotect, int dpo, int fua, int fua_nv, int group, unsigned char *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send WRITE10 (Expecting %s) LBA:%d blocks:%d "
               "wrprotect:%d dpo:%d fua:%d fua_nv:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, wrprotect,
                dpo, fua, fua_nv, group);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_write10(lba, datalen, blocksize, wrprotect,
                                dpo, fua, fua_nv, group);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("WRITE10", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
write12(struct scsi_device *sdev, uint32_t lba, uint32_t datalen, int blocksize, int wrprotect, int dpo, int fua, int fua_nv, int group, unsigned char *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send WRITE12 (Expecting %s) LBA:%d blocks:%d "
               "wrprotect:%d dpo:%d fua:%d fua_nv:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, wrprotect,
                dpo, fua, fua_nv, group);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_write12(lba, datalen, blocksize, wrprotect,
                                dpo, fua, fua_nv, group);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("WRITE12", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
write16(struct scsi_device *sdev, uint64_t lba, uint32_t datalen, int blocksize, int wrprotect, int dpo, int fua, int fua_nv, int group, unsigned char *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send WRITE16 (Expecting %s) LBA:%" PRIu64
                " blocks:%d wrprotect:%d dpo:%d fua:%d fua_nv:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, wrprotect,
                dpo, fua, fua_nv, group);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_write16(lba, datalen, blocksize, wrprotect,
                                dpo, fua, fua_nv, group);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        send_scsi_command(sdev, task, &d);

        ret = check_result("WRITE16", sdev, task, status, key, ascq, num_ascq);
        scsi_free_scsi_task(task);

        return ret;
}

int
writeatomic16(struct scsi_device *sdev, uint64_t lba, uint32_t datalen, int blocksize, int wrprotect, int dpo, int fua, int group, unsigned char *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send WRITEATOMIC16 (Expecting %s) LBA:%" PRIu64
                " blocks:%d wrprotect:%d dpo:%d fua:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, wrprotect,
                dpo, fua, group);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_writeatomic16(lba, datalen, blocksize, wrprotect,
                                      dpo, fua, group);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        send_scsi_command(sdev, task, &d);

        ret = check_result("WRITEATOMIC16", sdev, task, status, key, ascq, num_ascq);
        scsi_free_scsi_task(task);

        return ret;
}

int
writesame10(struct scsi_device *sdev, uint32_t lba, uint32_t datalen, int num, int anchor, int unmap_flag, int wrprotect, int group, unsigned char *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send WRITESAME10 (Expecting %s) LBA:%d blocks:%d "
                "wrprotect:%d anchor:%d unmap:%d group:%d",
                scsi_status_str(status),
                lba, num, wrprotect, anchor, unmap_flag, group);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_writesame10(wrprotect, anchor, unmap_flag, lba, group,
                                    num, datalen);
        assert(task != NULL);

        if (data != NULL) {
                task->expxferlen = datalen;
        } else {
                task->expxferlen = 0;
                task->xfer_dir = SCSI_XFER_NONE;
        }

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("WRITESAME10", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
writesame16(struct scsi_device *sdev, uint64_t lba, uint32_t datalen, int num, int anchor, int unmap_flag, int wrprotect, int group, unsigned char *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send WRITESAME16 (Expecting %s) LBA:%" PRIu64
                " blocks:%d wrprotect:%d anchor:%d unmap:%d group:%d",
                scsi_status_str(status),
                lba, num, wrprotect, anchor, unmap_flag, group);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_writesame16(wrprotect, anchor, unmap_flag, lba, group,
                                    num, datalen);
        assert(task != NULL);

        if (data != NULL) {
                task->expxferlen = datalen;
        } else {
                task->expxferlen = 0;
                task->xfer_dir = SCSI_XFER_NONE;
        }

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("WRITESAME16", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
writeverify10(struct scsi_device *sdev, uint32_t lba,
              uint32_t datalen, int blocksize, int wrprotect,
              int dpo, int bytchk, int group, unsigned char *data,
              int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send WRITEVERIFY10 (Expecting %s) LBA:%d "
                "blocks:%d wrprotect:%d dpo:%d bytchk:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, wrprotect,
                dpo, bytchk, group);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_writeverify10(lba, datalen, blocksize, wrprotect,
                                      dpo, bytchk, group);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("WRITEVERIFY10", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
writeverify12(struct scsi_device *sdev, uint32_t lba,
              uint32_t datalen, int blocksize, int wrprotect,
              int dpo, int bytchk, int group, unsigned char *data,
              int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send WRITEVERIFY12 (Expecting %s) LBA:%d "
                "blocks:%d wrprotect:%d dpo:%d bytchk:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, wrprotect,
                dpo, bytchk, group);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_writeverify12(lba, datalen, blocksize, wrprotect,
                                      dpo, bytchk, group);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("WRITEVERIFY12", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
writeverify16(struct scsi_device *sdev, uint64_t lba,
              uint32_t datalen, int blocksize, int wrprotect,
              int dpo, int bytchk, int group, unsigned char *data,
              int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        struct iscsi_data d;
        int ret;

        logging(LOG_VERBOSE, "Send WRITEVERIFY16 (Expecting %s) LBA:%" PRIu64
                " blocks:%d wrprotect:%d dpo:%d bytchk:%d group:%d",
                scsi_status_str(status),
                lba, datalen / blocksize, wrprotect,
                dpo, bytchk, group);

        if (!data_loss) {
                printf("--dataloss flag is not set in. Skipping write\n");
                return -1;
        }

        task = scsi_cdb_writeverify16(lba, datalen, blocksize, wrprotect,
                                      dpo, bytchk, group);
        assert(task != NULL);

        d.data = data;
        d.size = datalen;
        task = send_scsi_command(sdev, task, &d);

        ret = check_result("WRITEVERIFY16", sdev, task, status, key, ascq, num_ascq);
        if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

int
inquiry(struct scsi_device *sdev, struct scsi_task **out_task, int evpd, int page_code, int maxsize, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send INQUIRY (Expecting %s) evpd:%d "
                "page_code:%02x alloc_len:%d",
                scsi_status_str(status),
                evpd, page_code, maxsize);

        task = scsi_cdb_inquiry(evpd, page_code, maxsize);
        assert(task != NULL);

        task = send_scsi_command(sdev, task, NULL);

        ret = check_result("INQUIRY", sdev, task, status, key, ascq, num_ascq);
        if (out_task) {
                *out_task = task;
        } else if (task) {
                scsi_free_scsi_task(task);
        }
        return ret;
}

struct scsi_command_descriptor *
get_command_descriptor(int opcode, int sa)
{
        int i;

        if (rsop == NULL) {
                return NULL;
        }

        for (i = 0; i < rsop->num_descriptors; i++) {
                if (rsop->descriptors[i].opcode == opcode
                && rsop->descriptors[i].sa == sa) {
                        return &rsop->descriptors[i];
                }
        }

        return NULL;
}

int set_swp(struct scsi_device *sdev)
{
        int ret;
        struct scsi_task *sense_task = NULL;
        struct scsi_mode_sense *ms;
        struct scsi_mode_page *mp;

        logging(LOG_VERBOSE, "Read CONTROL page");

        /* see if we can even change swp */
        ret = modesense6(sdev, &sense_task, 1, SCSI_MODESENSE_PC_CHANGEABLE,
                         SCSI_MODEPAGE_CONTROL, 0, 255,
                         EXPECT_STATUS_GOOD);
        if (ret) {
                logging(LOG_NORMAL, "Failed to read CONTROL mode page.");
                goto finished;
        }
        logging(LOG_VERBOSE, "[SUCCESS] CONTROL page fetched.");

        ms = scsi_datain_unmarshall(sense_task);
        if (ms == NULL || ms->pages == NULL) {
                logging(LOG_NORMAL, "failed to unmarshall mode sense datain "
                        "blob");
                ret = -1;
                goto finished;
        }
        /* if we cannot change swp, we are done here */
        if (ms->pages->control.swp == 0) {
                logging(LOG_NORMAL, "SWP is not changeable");
                ret = -2;
                goto finished;
        }

        /* get the current control page */
        ret = modesense6(sdev, &sense_task, 1, SCSI_MODESENSE_PC_CURRENT,
                         SCSI_MODEPAGE_CONTROL, 0, 255,
                         EXPECT_STATUS_GOOD);
        if (ret) {
                logging(LOG_NORMAL, "Failed to read CONTROL mode page.");
                goto finished;
        }
        logging(LOG_VERBOSE, "[SUCCESS] CONTROL page fetched.");

        ms = scsi_datain_unmarshall(sense_task);
        if (ms == NULL) {
                logging(LOG_NORMAL, "failed to unmarshall mode sense datain "
                        "blob");
                ret = -1;
                goto finished;
        }

        mp = scsi_modesense_get_page(ms, SCSI_MODEPAGE_CONTROL, 0);
        if (mp == NULL) {
                logging(LOG_NORMAL, "failed to read control mode page");
                ret = -1;
                goto finished;
        }

        /* For MODE SELECT PS is reserved and hence must be cleared */
        mp->ps = 0;

        logging(LOG_VERBOSE, "Turn SWP ON");
        mp->control.swp = 1;

        ret = modeselect6(sdev, 1, 0, mp,
                         EXPECT_STATUS_GOOD);
        if (ret) {
                logging(LOG_NORMAL, "Failed to write CONTROL mode page.");
                goto finished;
        }
        logging(LOG_VERBOSE, "[SUCCESS] CONTROL page written.");

finished:
        if (sense_task != NULL) {
                scsi_free_scsi_task(sense_task);
        }
        return ret;
}

int clear_swp(struct scsi_device *sdev)
{
        int ret;
        struct scsi_task *sense_task = NULL;
        struct scsi_mode_sense *ms;
        struct scsi_mode_page *mp;

        logging(LOG_VERBOSE, "Read CONTROL page");
        ret = modesense6(sdev, &sense_task, 1, SCSI_MODESENSE_PC_CURRENT,
                         SCSI_MODEPAGE_CONTROL, 0, 255,
                         EXPECT_STATUS_GOOD);
        if (ret) {
                logging(LOG_NORMAL, "Failed to read CONTROL mode page.");
                goto finished;
        }
        logging(LOG_VERBOSE, "[SUCCESS] CONTROL page fetched.");

        ms = scsi_datain_unmarshall(sense_task);
        if (ms == NULL) {
                logging(LOG_NORMAL, "failed to unmarshall mode sense datain "
                        "blob");
                ret = -1;
                goto finished;
        }
        mp = scsi_modesense_get_page(ms, SCSI_MODEPAGE_CONTROL, 0);
        if (mp == NULL) {
                logging(LOG_NORMAL, "failed to read control mode page");
                ret = -1;
                goto finished;
        }

        /* For MODE SELECT PS is reserved and hence must be cleared */
        mp->ps = 0;

        logging(LOG_VERBOSE, "Turn SWP OFF");
        mp->control.swp = 0;

        ret = modeselect6(sdev, 1, 0, mp,
                         EXPECT_STATUS_GOOD);
        if (ret) {
                logging(LOG_NORMAL, "Failed to write CONTROL mode page.");
                goto finished;
        }
        logging(LOG_VERBOSE, "[SUCCESS] CONTROL page written.");

finished:
        if (sense_task != NULL) {
                scsi_free_scsi_task(sense_task);
        }
        return ret;
}

/* Extended Copy */
int extendedcopy(struct scsi_device *sdev, struct iscsi_data *data, int status, enum scsi_sense_key key, int *ascq, int num_ascq)
{
        struct scsi_task *task;
        int ret;

        logging(LOG_VERBOSE, "Send EXTENDED COPY (Expecting %s)",
                        scsi_status_str(status));

        if (!data_loss) {
                logging(LOG_NORMAL, "--dataloss flag is not set in. Skipping extendedcopy\n");
                return -1;
        }

        task = scsi_cdb_extended_copy(data->size);

        assert(task != NULL);

        send_scsi_command(sdev, task, data);

        ret = check_result("EXTENDEDCOPY", sdev, task, status, key, ascq, num_ascq);
        scsi_free_scsi_task(task);

        return ret;
}

/* return the length for a give descriptor type, including any SD header */
int get_desc_len(enum ec_descr_type_code desc_type)
{
        int desc_len = 0;
        switch (desc_type) {
                /* Segment Descriptors */
                case BLK_TO_STRM_SEG_DESCR:
                case STRM_TO_BLK_SEG_DESCR:
                        desc_len = 0x14 + SEG_DESC_SRC_INDEX_OFFSET;
                        break;
                case BLK_TO_BLK_SEG_DESCR:
                        desc_len = 0x18 + SEG_DESC_SRC_INDEX_OFFSET;
                        break;
                case STRM_TO_STRM_SEG_DESCR:
                        desc_len = 0x10 + SEG_DESC_SRC_INDEX_OFFSET;
                        break;
                case BLK_TO_BLK_OFF_SEG_DESCR:
                        desc_len = 0x1C + SEG_DESC_SRC_INDEX_OFFSET;
                        break;

                /* Target Descriptors */
                case IPV6_TGT_DESCR:
                case IP_COPY_SVC_TGT_DESCR:
                        desc_len = 64;
                        break;
                case IDENT_DESCR_TGT_DESCR:
                default:
                        if (desc_type >= 0xE0 && desc_type <= 0xE9)
                                desc_len = 32;
        }

        return desc_len;
}

/*
 * Populate a 24 byte SCSI designator. See also Table 529 — Designation
 * descriptor in SPC-6.
 */
static int populate_ident_tgt_desc(uint8_t buf[24], struct scsi_device *dev)
{
        int ret;
        struct scsi_task *inq_di_task = NULL;
        struct scsi_inquiry_device_identification *inq_di = NULL;
        struct scsi_inquiry_device_designator *desig, *tgt_desig = NULL;
        enum scsi_designator_type prev_type = 0;

        ret = inquiry(dev, &inq_di_task, 1,
                      SCSI_INQUIRY_PAGECODE_DEVICE_IDENTIFICATION, 255,
                      EXPECT_STATUS_GOOD);
        if (ret < 0 || inq_di_task == NULL) {
                logging(LOG_NORMAL,
                        "Failed to read Device Identification page");
                goto err;
        } else {
                inq_di = scsi_datain_unmarshall(inq_di_task);
                if (inq_di == NULL) {
                        logging(LOG_NORMAL,
                                "Failed to unmarshall inquiry datain blob");
                        goto err;
                }
        }

        for (desig = inq_di->designators; desig; desig = desig->next) {
                switch (desig->designator_type) {
                case SCSI_DESIGNATOR_TYPE_VENDOR_SPECIFIC:
                case SCSI_DESIGNATOR_TYPE_T10_VENDORT_ID:
                case SCSI_DESIGNATOR_TYPE_EUI_64:
                case SCSI_DESIGNATOR_TYPE_NAA:
                        if (prev_type <= desig->designator_type &&
                            desig->designator_length <= 20) {
                                tgt_desig = desig;
                                prev_type = desig->designator_type;
                        }
                        continue;
                default:
                        continue;
                }
        }
        if (tgt_desig == NULL) {
                logging(LOG_NORMAL,
                        "No suitable target descriptor found");
                goto err;
        }

        memset(buf, 0, 24);
        buf[0] = tgt_desig->code_set;
        buf[1] = (tgt_desig->designator_type & 0xF) |
          ((tgt_desig->association & 3) << 4);
        buf[3] = tgt_desig->designator_length;
        memcpy(buf + 4, tgt_desig->designator, tgt_desig->designator_length);
        scsi_free_scsi_task(inq_di_task);
        return 0;

err:
        scsi_free_scsi_task(inq_di_task);
        return -1;
}

int populate_tgt_desc(uint8_t desc[32], enum ec_descr_type_code desc_type,
                      int luid_type, int nul, int peripheral_type,
                      uint16_t rel_init_port_id, int pad,
                      struct scsi_device *dev)
{
        assert(desc_type < 256);
        assert(luid_type < 4);
        assert(nul < 2);
        assert(peripheral_type < 32);

        desc[0] = desc_type;
        desc[1] = (luid_type << 6) | (nul << 5) | peripheral_type;
        scsi_set_uint16(&desc[2], rel_init_port_id);

        if (desc_type == IDENT_DESCR_TGT_DESCR &&
            populate_ident_tgt_desc(desc + 4, dev) < 0)
                return -1;

        if (peripheral_type == 0) {
                // Issue read capacity for each sd if testing with different LUs
                // If single LU, use block_size from prior readcapacity
                // invocation.
                desc[28] = pad << 2;
                desc[29] = (block_size >> 16) & 0xFF;
                desc[30] = (block_size >> 8) & 0xFF;
                desc[31] = block_size & 0xFF;
        }
        return get_desc_len(desc_type);
}

int populate_seg_desc_hdr(unsigned char *hdr, enum ec_descr_type_code desc_type, int dc, int cat, uint16_t src_index, uint16_t dst_index)
{
        int desc_len = get_desc_len(desc_type);

        hdr[0] = desc_type;
        hdr[1] = ((dc << 1) | cat) & 0xFF;
        /* don't account for the first 4 bytes in descriptor header */
        scsi_set_uint16(&hdr[2], (desc_len - SEG_DESC_SRC_INDEX_OFFSET));
        scsi_set_uint16(&hdr[4], src_index);
        scsi_set_uint16(&hdr[6], dst_index);

        return desc_len;
}

int populate_seg_desc_b2b(unsigned char *desc, int dc, int cat, int src_index, int dst_index, uint16_t num_blks, uint64_t src_lba, uint64_t dst_lba)
{
        int desc_len = populate_seg_desc_hdr(desc, BLK_TO_BLK_SEG_DESCR, dc, cat, src_index, dst_index);

        scsi_set_uint16(&desc[10], num_blks);
        scsi_set_uint64(&desc[12], src_lba);
        scsi_set_uint64(&desc[20], dst_lba);

        return desc_len;
}

int populate_seg_desc_b2b_off(unsigned char *desc, int cat, int src_index,
                              int dst_index, uint32_t num_bytes,
                              uint64_t src_lba, uint64_t dst_lba,
                              uint16_t src_byte_off, uint16_t dst_byte_off)
{
        int desc_len = populate_seg_desc_hdr(desc, BLK_TO_BLK_OFF_SEG_DESCR, 0,
                                             cat, src_index, dst_index);

        scsi_set_uint32(&desc[8], num_bytes);
        scsi_set_uint64(&desc[12], src_lba);
        scsi_set_uint64(&desc[20], dst_lba);
        scsi_set_uint16(&desc[28], src_byte_off);
        scsi_set_uint16(&desc[30], dst_byte_off);

        return desc_len;
}

void populate_param_header(unsigned char *buf, int list_id, int str, int list_id_usage, int prio, int tgt_desc_len, int seg_desc_len, int inline_data_len)
{
        buf[0] = list_id;
        buf[1] = ((str & 1) << 5) | ((list_id_usage & 3) << 3) | (prio & 7);
        scsi_set_uint16(&buf[2], tgt_desc_len);
        scsi_set_uint32(&buf[8], seg_desc_len);
        scsi_set_uint32(&buf[12], inline_data_len);
}

int receive_copy_results(struct scsi_task **task, struct scsi_device *sdev,
                         enum scsi_copy_results_sa sa, int list_id,
                         void **datap, int status, enum scsi_sense_key key,
                         int *ascq, int num_ascq)
{
        int ret;

        logging(LOG_VERBOSE, "Send RECEIVE COPY RESULTS");

        *task = scsi_cdb_receive_copy_results(sa, list_id, 1024);
        assert(task != NULL);

        *task = send_scsi_command(sdev, *task, NULL);

        ret = check_result("RECEIVECOPYRESULT", sdev, *task, status, key, ascq,
                           num_ascq);
        if (ret < 0)
                return ret;

        if ((*task)->status == SCSI_STATUS_GOOD && datap != NULL) {
                *datap = scsi_datain_unmarshall(*task);
                if (*datap == NULL) {
                        logging(LOG_NORMAL,
                                        "[FAIL] failed to unmarshall RECEIVE COPY RESULTS data. %s",
                                        iscsi_get_error(sdev->iscsi_ctx));
                        return -1;
                }
        }

        return check_result("RECEIVECOPYRESULT", sdev, *task, status, key, ascq,
                            num_ascq);
}

#define TEST_ISCSI_TUR_MAX_RETRIES 5

int
test_iscsi_tur_until_good(struct scsi_device *iscsi_sd, int *num_uas)
{
        int num_turs;

        if (iscsi_sd->iscsi_ctx == NULL) {
                logging(LOG_NORMAL, "invalid sd for tur_until_good");
                return -EINVAL;
        }

        *num_uas = 0;
        for (num_turs = 0; num_turs < TEST_ISCSI_TUR_MAX_RETRIES; num_turs++) {
                struct scsi_task *tsk;
                tsk = iscsi_testunitready_sync(iscsi_sd->iscsi_ctx,
                                               iscsi_sd->iscsi_lun);
                if (tsk->status == SCSI_STATUS_GOOD) {
                        logging(LOG_VERBOSE, "TUR good after %d retries",
                                num_turs);
                        return 0;
                } else if ((tsk->status == SCSI_STATUS_CHECK_CONDITION)
                        && (tsk->sense.key == SCSI_SENSE_UNIT_ATTENTION)) {
                        logging(LOG_VERBOSE, "Got UA for TUR");
                        (*num_uas)++;
                } else {
                        logging(LOG_NORMAL, "unexpected non-UA failure: %d,%d",
                                tsk->status, tsk->sense.key);
                }
        }

        return -ETIMEDOUT;
}

uint64_t
test_get_clock_sec(void)
{
	uint64_t secs;
	int res;

#ifdef HAVE_CLOCK_GETTIME
	struct timespec ts;
	res = clock_gettime(CLOCK_MONOTONIC, &ts);
	secs = ts.tv_sec;
#else
	struct timeval tv;
	res = gettimeofday(&tv, NULL);
	secs = tv.tv_sec;
#endif
	assert(res == 0);
	return secs;
}
