/*
 * Copyright (C) 2008-2012 Daisuke Aoyama <aoyama@peach.ne.jp>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <inttypes.h>
#include <stdint.h>

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <time.h>

#include <fcntl.h>
#include <unistd.h>

#ifdef HAVE_UUID_H
#include <uuid.h>
#endif

#include "istgt.h"
#include "istgt_ver.h"
#include "istgt_log.h"
#include "istgt_conf.h"
#include "istgt_sock.h"
#include "istgt_misc.h"
#include "istgt_crc32c.h"
#include "istgt_md5.h"
#include "istgt_iscsi.h"
#include "istgt_lu.h"
#include "istgt_proto.h"
#include "istgt_scsi.h"
#include "istgt_queue.h"

#if !defined(__GNUC__)
#undef __attribute__
#define __attribute__(x)
#endif

#ifndef O_FSYNC
#define O_FSYNC O_SYNC
#endif

//#define ISTGT_TRACE_DISK

typedef enum {
	ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE = 0x01,
	ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS = 0x03,
	ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY = 0x05,
	ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY = 0x06,
	ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS = 0x07,
	ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS = 0x08,
} ISTGT_LU_PR_TYPE;

#define PR_ALLOW(WE,EA,ALLRR,WERR,EARR) \
	((((WE)&1) << 4) | (((EA)&1) << 3) | (((ALLRR)&1) << 2) \
	 | (((WERR)&1) << 1) | (((EARR)&1) << 0))
#define PR_ALLOW_WE    0x0010
#define PR_ALLOW_EA    0x0008
#define PR_ALLOW_ALLRR 0x0004
#define PR_ALLOW_WERR  0x0002
#define PR_ALLOW_EARR  0x0001

#define BUILD_SENSE(SK,ASC,ASCQ)					\
	do {								\
		*sense_len =						\
			istgt_lu_disk_build_sense_data(spec, sense_data, \
			    ISTGT_SCSI_SENSE_ ## SK,			\
			    (ASC), (ASCQ));				\
	} while (0)
#define BUILD_SENSE2(SK,ASC,ASCQ)					\
	do {								\
		*sense_len =						\
			istgt_lu_disk_build_sense_data2(spec, sense_data, \
			    ISTGT_SCSI_SENSE_ ## SK,			\
			    (ASC), (ASCQ));				\
	} while (0)

static void istgt_lu_disk_free_pr_key(ISTGT_LU_PR_KEY *prkey);
static int istgt_lu_disk_build_sense_data(ISTGT_LU_DISK *spec, uint8_t *data, int sk, int asc, int ascq);
static int istgt_lu_disk_queue_abort_ITL(ISTGT_LU_DISK *spec, const char *initiator_port);

static int
istgt_lu_disk_open_raw(ISTGT_LU_DISK *spec, int flags, int mode)
{
	int rc;

	rc = open(spec->file, flags, mode);
	if (rc < 0) {
		return -1;
	}
	spec->fd = rc;
	spec->foffset = 0;
	return 0;
}

static int
istgt_lu_disk_close_raw(ISTGT_LU_DISK *spec)
{
	int rc;

	if (spec->fd == -1)
		return 0;
	rc = close(spec->fd);
	if (rc < 0) {
		return -1;
	}
	spec->fd = -1;
	spec->foffset = 0;
	return 0;
}

#if 0
static off_t
istgt_lu_disk_lseek_raw(ISTGT_LU_DISK *spec, off_t offset, int whence)
{
	off_t rc;

	rc = lseek(spec->fd, offset, whence);
	if (rc < 0) {
		return -1;
	}
	spec->foffset = offset;
	return rc;
}
#endif

static int64_t
istgt_lu_disk_seek_raw(ISTGT_LU_DISK *spec, uint64_t offset)
{
	off_t rc;

	rc = lseek(spec->fd, (off_t) offset, SEEK_SET);
	if (rc < 0) {
		return -1;
	}
	spec->foffset = offset;
	return 0;
}

static int64_t
istgt_lu_disk_read_raw(ISTGT_LU_DISK *spec, void *buf, uint64_t nbytes)
{
	int64_t rc;

	if (spec->lu->istgt->swmode >= ISTGT_SWMODE_EXPERIMENTAL) {
		if (spec->foffset + nbytes <= spec->fsize) {
			/* inside media */
			rc = (int64_t) read(spec->fd, buf, (size_t) nbytes);
		} else if (spec->foffset >= spec->fsize) {
			/* outside media */
			memset(buf, 0, nbytes);
			rc = nbytes;
			if (spec->foffset + nbytes >= spec->size) {
				rc = spec->size - spec->foffset;
			}
		} else if (spec->foffset + nbytes > spec->fsize) {
			/* both */
			uint64_t request = spec->fsize - spec->foffset;
			memset(buf, 0, nbytes);
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "read %"PRIu64" bytes at %"PRIu64"/%"PRIu64"\n",
				    request, spec->foffset, spec->fsize);
			rc = (int64_t) read(spec->fd, buf, (size_t) request);
			if (rc < 0) {
				return -1;
			}
			if ((uint64_t) rc != request) {
				/* read size < request */
				if (spec->foffset + rc >= spec->size) {
					rc = spec->size - spec->foffset;
				}
				spec->foffset += rc;
				return rc;
			}
			rc = nbytes;
			if (spec->foffset + nbytes >= spec->size) {
				rc = spec->size - spec->foffset;
			}
		} else {
			rc = -1;
		}
		if (rc < 0) {
			return -1;
		}
		spec->foffset += rc;
		return rc;
	}
	rc = (int64_t) read(spec->fd, buf, (size_t) nbytes);
	if (rc < 0) {
		return -1;
	}
	spec->foffset += rc;
	return rc;
}

static int64_t
istgt_lu_disk_write_raw(ISTGT_LU_DISK *spec, const void *buf, uint64_t nbytes)
{
	int64_t rc;

	if (spec->lu->istgt->swmode >= ISTGT_SWMODE_EXPERIMENTAL) {
		if (spec->foffset + nbytes <= spec->fsize) {
			/* inside media */
			rc = (int64_t) write(spec->fd, buf, (size_t) nbytes);
		} else if (spec->foffset + nbytes <= ISTGT_LU_MEDIA_SIZE_MIN) {
			/* allways write in minimum size */
			rc = (int64_t) write(spec->fd, buf, (size_t) nbytes);
		} else if (spec->foffset >= spec->fsize) {
			/* outside media */
			const uint8_t *p = (const uint8_t *) buf;
			uint64_t n;
			for (n = 0; n < nbytes; n++) {
				if (p[n] != 0)
					break;
			}
			if (n == nbytes) {
				/* write all zero (skip) */
				ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
				    "write zero %"PRIu64" bytes at %"PRIu64"/%"PRIu64"\n",
				    nbytes, spec->foffset, spec->fsize);
				rc = nbytes;
				spec->foffset += rc;
				return rc;
			}
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "write %"PRIu64" bytes at %"PRIu64"/%"PRIu64"\n",
			    nbytes, spec->foffset, spec->fsize);
			rc = (int64_t) write(spec->fd, buf, (size_t) nbytes);
		} else if (spec->foffset + nbytes > spec->fsize) {
			/* both */
			rc = (int64_t) write(spec->fd, buf, (size_t) nbytes);
		} else {
			rc = -1;
		}
		if (rc < 0) {
			return -1;
		}
		spec->foffset += rc;
		if (spec->foffset > spec->fsize) {
			spec->fsize = spec->foffset;
		}
		return rc;
	}
	rc = (int64_t) write(spec->fd, buf, (size_t) nbytes);
	if (rc < 0) {
		return -1;
	}
	spec->foffset += rc;
	if (spec->foffset > spec->fsize) {
		spec->fsize = spec->foffset;
	}
	return rc;
}

static int64_t
istgt_lu_disk_sync_raw(ISTGT_LU_DISK *spec, uint64_t offset, uint64_t nbytes)
{
	int64_t rc;

	rc = (int64_t) fsync(spec->fd);
	if (rc < 0) {
		return -1;
	}
	spec->foffset = offset + nbytes;
	return rc;
}

static int
istgt_lu_disk_allocate_raw(ISTGT_LU_DISK *spec)
{
	uint8_t *data;
	uint64_t fsize;
	uint64_t size;
	uint64_t blocklen;
	uint64_t offset;
	uint64_t nbytes;
	int64_t rc;

	size = spec->size;
	blocklen = spec->blocklen;
	nbytes = blocklen;
	data = xmalloc(nbytes);
	memset(data, 0, nbytes);

	fsize = istgt_lu_get_filesize(spec->file);
	if (fsize > size) {
		xfree(data);
		return 0;
	}
	spec->fsize = fsize;

	offset = size - nbytes;
	rc = istgt_lu_disk_seek_raw(spec, offset);
	if (rc == -1) {
		ISTGT_ERRLOG("lu_disk_seek() failed\n");
		xfree(data);
		return -1;
	}
	rc = istgt_lu_disk_read_raw(spec, data, nbytes);
	/* EOF is OK */
	if (rc == -1) {
		ISTGT_ERRLOG("lu_disk_read() failed\n");
		xfree(data);
		return -1;
	}
	if (spec->lu->istgt->swmode >= ISTGT_SWMODE_EXPERIMENTAL) {
		/* allocate minimum size */
		if (fsize < ISTGT_LU_MEDIA_SIZE_MIN) {
			fsize = ISTGT_LU_MEDIA_SIZE_MIN;
			if (size < ISTGT_LU_MEDIA_SIZE_MIN) {
				fsize = size;
			}
			offset = fsize - nbytes;
			rc = istgt_lu_disk_seek_raw(spec, offset);
			if (rc == -1) {
				ISTGT_ERRLOG("lu_disk_seek() failed\n");
				xfree(data);
				return -1;
			}
			rc = istgt_lu_disk_write_raw(spec, data, nbytes);
			if (rc == -1 || (uint64_t) rc != nbytes) {
				ISTGT_ERRLOG("lu_disk_write() failed\n");
				xfree(data);
				return -1;
			}
			spec->fsize = fsize;
			spec->foffset = fsize;
		}
	} else {
		/* allocate complete size */
		rc = istgt_lu_disk_seek_raw(spec, offset);
		if (rc == -1) {
			ISTGT_ERRLOG("lu_disk_seek() failed\n");
			xfree(data);
			return -1;
		}
		rc = istgt_lu_disk_write_raw(spec, data, nbytes);
		if (rc == -1 || (uint64_t) rc != nbytes) {
			ISTGT_ERRLOG("lu_disk_write() failed\n");
			xfree(data);
			return -1;
		}
		spec->foffset = size;
	}

	xfree(data);
	return 0;
}

static int
istgt_lu_disk_setcache_raw(ISTGT_LU_DISK *spec)
{
	int flags;
	int rc;
	int fd;

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "istgt_lu_disk_setcache\n");

	fd = spec->fd;
	if (spec->read_cache) {
		/* not implement */
	} else {
		/* not implement */
	}
	flags = fcntl(fd , F_GETFL, 0);
	if (flags != -1) {
		if (spec->write_cache) {
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "write cache enable\n");
			rc = fcntl(fd, F_SETFL, (flags & ~O_FSYNC));
			spec->write_cache = 1;
		} else {
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "write cache disable\n");
			rc = fcntl(fd, F_SETFL, (flags | O_FSYNC));
			spec->write_cache = 0;
		}
		if (rc == -1) {
#if 0
			ISTGT_ERRLOG("LU%d: LUN%d: fcntl(F_SETFL) failed(errno=%d)\n",
			    spec->num, spec->lun, errno);
#endif
		}
	} else {
		ISTGT_ERRLOG("LU%d: LUN%d: fcntl(F_GETFL) failed(errno=%d)\n",
		    spec->num, spec->lun, errno);
	}
	return 0;
}

static const char *
istgt_get_disktype_by_ext(const char *file)
{
	size_t n;

	if (file == NULL || file[0] == '\n')
		return "RAW";

	n = strlen(file);
	if (n > 4 && strcasecmp(file + (n - 4), ".vdi") == 0)
		return "VDI";
	if (n > 4 && strcasecmp(file + (n - 4), ".vhd") == 0)
		return "VHD";
	if (n > 5 && strcasecmp(file + (n - 5), ".vmdk") == 0)
		return "VMDK";

	if (n > 5 && strcasecmp(file + (n - 5), ".qcow") == 0)
		return "QCOW";
	if (n > 6 && strcasecmp(file + (n - 6), ".qcow2") == 0)
		return "QCOW";
	if (n > 4 && strcasecmp(file + (n - 4), ".qed") == 0)
		return "QED";
	if (n > 5 && strcasecmp(file + (n - 5), ".vhdx") == 0)
		return "VHDX";

	return "RAW";
}

int
istgt_lu_disk_init(ISTGT_Ptr istgt __attribute__((__unused__)), ISTGT_LU_Ptr lu)
{
	ISTGT_LU_DISK *spec;
	uint64_t gb_size;
	uint64_t mb_size;
#ifdef HAVE_UUID_H
	uint32_t status;
#endif /* HAVE_UUID_H */
	int mb_digit;
	int flags;
	int newfile;
	int rc;
	int i, j;

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "istgt_lu_disk_init\n");

	printf("LU%d HDD UNIT\n", lu->num);
	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "LU%d TargetName=%s\n",
	    lu->num, lu->name);
	for (i = 0; i < lu->maxlun; i++) {
		if (lu->lun[i].type == ISTGT_LU_LUN_TYPE_NONE) {
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "LU%d: LUN%d none\n",
			    lu->num, i);
			lu->lun[i].spec = NULL;
			continue;
		}
		if (lu->lun[i].type != ISTGT_LU_LUN_TYPE_STORAGE) {
			ISTGT_ERRLOG("LU%d: unsupported type\n", lu->num);
			return -1;
		}
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "LU%d: LUN%d storage\n",
		    lu->num, i);

		spec = xmalloc(sizeof *spec);
		memset(spec, 0, sizeof *spec);
		spec->lu = lu;
		spec->num = lu->num;
		spec->lun = i;
		spec->fd = -1;
		if (spec->lu->lun[i].readcache) {
			spec->read_cache = 1;
		} else {
			spec->read_cache = 0;
		}
		if (spec->lu->lun[i].writecache) {
			spec->write_cache = 1;
		} else {
			spec->write_cache = 0;
		}
		if (spec->lu->istgt->swmode >= ISTGT_SWMODE_EXPERIMENTAL) {
			spec->wbufsize = ISTGT_LU_MAX_WRITE_CACHE_SIZE;
			spec->wbuf = xmalloc(spec->wbufsize);
			memset(spec->wbuf, 0, spec->wbufsize);
		} else {
			spec->wbufsize = 0;
			spec->wbuf = NULL;
		}
		spec->woffset = 0;
		spec->wnbytes = 0;
		spec->req_write_cache = 0;
		spec->err_write_cache = 0;
		spec->thin_provisioning = 0;
		spec->watssize = 0;
		spec->watsbuf = NULL;

		rc = pthread_mutex_init(&spec->ats_mutex, NULL);
		if (rc != 0) {
			ISTGT_ERRLOG("LU%d: mutex_init() failed\n", lu->num);
			return -1;
		}

		spec->queue_depth = lu->queue_depth;
		rc = pthread_mutex_init(&spec->cmd_queue_mutex, &istgt->mutex_attr);
		if (rc != 0) {
			ISTGT_ERRLOG("LU%d: mutex_init() failed\n", lu->num);
			return -1;
		}
		istgt_queue_init(&spec->cmd_queue);
		rc = pthread_mutex_init(&spec->wait_lu_task_mutex, NULL);
		if (rc != 0) {
			ISTGT_ERRLOG("LU%d: mutex_init() failed\n", lu->num);
			return -1;
		}
		spec->wait_lu_task = NULL;

		spec->npr_keys = 0;
		for (j = 0; j < MAX_LU_RESERVE; j++) {
			spec->pr_keys[j].registered_initiator_port = NULL;
		}
		spec->pr_generation = 0;
		spec->rsv_port = NULL;
		spec->rsv_key = 0;
		spec->rsv_scope = 0;
		spec->rsv_type = 0;

		spec->sense = 0;
		{
			int sk, asc, ascq;
			/* POWER ON, RESET, OR BUS DEVICE RESET OCCURRED */
			sk = ISTGT_SCSI_SENSE_UNIT_ATTENTION;
			asc = 0x29;
			ascq = 0x00;
			spec->sense = (((sk & 0xffU) << 16)
			    | ((asc & 0xffU) << 8)
			    | ((ascq & 0xffU) << 0));
		}

#ifdef HAVE_UUID_H
		uuid_create(&spec->uuid, &status);
		if (status != uuid_s_ok) {
			ISTGT_ERRLOG("LU%d: LUN%d: uuid_create() failed\n", lu->num, i);
			(void) pthread_mutex_destroy(&spec->wait_lu_task_mutex);
			(void) pthread_mutex_destroy(&spec->cmd_queue_mutex);
			(void) pthread_mutex_destroy(&spec->ats_mutex);
			istgt_queue_destroy(&spec->cmd_queue);
			xfree(spec);
			return -1;
		}
#endif /* HAVE_UUID_H */

		spec->file = lu->lun[i].u.storage.file;
		spec->size = lu->lun[i].u.storage.size;
		spec->disktype = istgt_get_disktype_by_ext(spec->file);
		if (strcasecmp(spec->disktype, "VDI") == 0
		    || strcasecmp(spec->disktype, "VHD") == 0
		    || strcasecmp(spec->disktype, "VMDK") == 0
		    || strcasecmp(spec->disktype, "QCOW") == 0
		    || strcasecmp(spec->disktype, "QED") == 0
		    || strcasecmp(spec->disktype, "VHDX") == 0) {
			rc = istgt_lu_disk_vbox_lun_init(spec, istgt, lu);
			if (rc < 0) {
				ISTGT_ERRLOG("LU%d: LUN%d: lu_disk_vbox_lun_init() failed\n",
				    lu->num, i);
				goto error_return;
			}
		} else if (strcasecmp(spec->disktype, "RAW") == 0) {
			spec->open = istgt_lu_disk_open_raw;
			spec->close = istgt_lu_disk_close_raw;
			spec->seek = istgt_lu_disk_seek_raw;
			spec->read = istgt_lu_disk_read_raw;
			spec->write = istgt_lu_disk_write_raw;
			spec->sync = istgt_lu_disk_sync_raw;
			spec->allocate = istgt_lu_disk_allocate_raw;
			spec->setcache = istgt_lu_disk_setcache_raw;

			spec->blocklen = lu->blocklen;
			if (spec->blocklen != 512
			    && spec->blocklen != 1024
			    && spec->blocklen != 2048
			    && spec->blocklen != 4096
			    && spec->blocklen != 8192
			    && spec->blocklen != 16384
			    && spec->blocklen != 32768
			    && spec->blocklen != 65536
			    && spec->blocklen != 131072
			    && spec->blocklen != 262144
			    && spec->blocklen != 524288) {
				ISTGT_ERRLOG("LU%d: LUN%d: invalid blocklen %"PRIu64"\n",
				    lu->num, i, spec->blocklen);
			error_return:
				(void) pthread_mutex_destroy(&spec->wait_lu_task_mutex);
				(void) pthread_mutex_destroy(&spec->cmd_queue_mutex);
				(void) pthread_mutex_destroy(&spec->ats_mutex);
				istgt_queue_destroy(&spec->cmd_queue);
				xfree(spec);
				return -1;
			}
			spec->blockcnt = spec->size / spec->blocklen;
			if (spec->blockcnt == 0) {
				ISTGT_ERRLOG("LU%d: LUN%d: size zero\n", lu->num, i);
				goto error_return;
			}

#if 0
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "LU%d: LUN%d file=%s, size=%"PRIu64"\n",
			    lu->num, i, spec->file, spec->size);
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "LU%d: LUN%d %"PRIu64" blocks, %"
			    PRIu64" bytes/block\n",
			    lu->num, i, spec->blockcnt, spec->blocklen);
#endif
			printf("LU%d: LUN%d file=%s, size=%"PRIu64"\n",
			    lu->num, i, spec->file, spec->size);
			printf("LU%d: LUN%d %"PRIu64" blocks, %"PRIu64" bytes/block\n",
			    lu->num, i, spec->blockcnt, spec->blocklen);
			
			flags = lu->readonly ? O_RDONLY : O_RDWR;
			newfile = 0;
			rc = spec->open(spec, flags, 0666);
			if (rc < 0) {
				newfile = 1;
				flags = lu->readonly ? O_RDONLY : (O_CREAT | O_EXCL | O_RDWR);
				rc = spec->open(spec, flags, 0666);
				if (rc < 0) {
					ISTGT_ERRLOG("LU%d: LUN%d: open error(errno=%d)\n",
					    lu->num, i, errno);
					goto error_return;
				}
			}
			if (!lu->readonly) {
				rc = spec->allocate(spec);
				if (rc < 0) {
					ISTGT_ERRLOG("LU%d: LUN%d: allocate error\n",
					    lu->num, i);
					goto error_return;
				}
			}
			rc = spec->setcache(spec);
			if (rc < 0) {
				ISTGT_ERRLOG("LU%d: LUN%d: setcache error\n", lu->num, i);
				goto error_return;
			}
		} else {
			ISTGT_ERRLOG("LU%d: LUN%d: unsupported format\n", lu->num, i);
			goto error_return;
		}

		gb_size = spec->size / ISTGT_LU_1GB;
		mb_size = (spec->size % ISTGT_LU_1GB) / ISTGT_LU_1MB;
		if (gb_size > 0) {
			mb_digit = (int) (((mb_size * 100) / 1024) / 10);
#if 0
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "LU%d LUN%d %"PRIu64".%dGB %sstorage for %s\n",
			    lu->num, i, gb_size, mb_digit,
			    lu->readonly ? "readonly " : "", lu->name);
#endif
			printf("LU%d: LUN%d %"PRIu64".%dGB %sstorage for %s\n",
			    lu->num, i, gb_size, mb_digit,
			    lu->readonly ? "readonly " : "", lu->name);
		} else {
#if 0
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "LU%d: LUN%d %"PRIu64"MB %sstorage for %s\n",
			    lu->num, i, mb_size,
			    lu->readonly ? "readonly " : "", lu->name);
#endif
			printf("LU%d: LUN%d %"PRIu64"MB %sstorage for %s\n",
			    lu->num, i, mb_size,
			    lu->readonly ? "readonly " : "", lu->name);
		}
		if (spec->lu->lun[i].serial != NULL) {
			printf("LU%d: LUN%d serial %s\n",
			    lu->num, i, spec->lu->lun[i].serial);
		} else {
			printf("LU%d: LUN%d serial %s\n",
			    lu->num, i, spec->lu->inq_serial);
		}
		printf("LU%d: LUN%d ", lu->num, i);
		if (spec->read_cache) {
			printf("read cache enabled");
		} else {
			printf("read cache disabled");
		}
		printf(", ");
		if (spec->write_cache) {
			printf("write cache enabled");
		} else {
			printf("write cache disabled");
		}
		printf("\n");
		if (spec->queue_depth != 0) {
			printf("LU%d: LUN%d command queuing enabled, depth %d\n",
			    lu->num, i, spec->queue_depth);
		} else {
			printf("LU%d: LUN%d command queuing disabled\n",
			    lu->num, i);
		}
#if 0
		if (spec->write_cache && spec->wbufsize) {
			mb_size = (spec->wbufsize / ISTGT_LU_1MB);
			printf("LU%d: LUN%d write buffer %"PRIu64"MB\n",
			    lu->num, i, mb_size);
		}
#endif

		lu->lun[i].spec = spec;
	}

	return 0;
}

int
istgt_lu_disk_shutdown(ISTGT_Ptr istgt __attribute__((__unused__)), ISTGT_LU_Ptr lu)
{
	ISTGT_LU_DISK *spec;
	ISTGT_LU_PR_KEY *prkey;
	int rc;
	int i, j;

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "istgt_lu_disk_shutdown\n");

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "LU%d TargetName=%s\n",
	    lu->num, lu->name);
	for (i = 0; i < lu->maxlun; i++) {
		if (lu->lun[i].type == ISTGT_LU_LUN_TYPE_NONE) {
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "LU%d: LUN%d none\n",
			    lu->num, i);
			continue;
		}
		if (lu->lun[i].type != ISTGT_LU_LUN_TYPE_STORAGE) {
			ISTGT_ERRLOG("LU%d: unsupported type\n", lu->num);
			return -1;
		}
		spec = (ISTGT_LU_DISK *) lu->lun[i].spec;

		if (strcasecmp(spec->disktype, "VDI") == 0
		    || strcasecmp(spec->disktype, "VHD") == 0
		    || strcasecmp(spec->disktype, "VMDK") == 0
		    || strcasecmp(spec->disktype, "QCOW") == 0
		    || strcasecmp(spec->disktype, "QED") == 0
		    || strcasecmp(spec->disktype, "VHDX") == 0) {
			rc = istgt_lu_disk_vbox_lun_shutdown(spec, istgt, lu);
			if (rc < 0) {
				ISTGT_ERRLOG("LU%d: lu_disk_vbox_lun_shutdown() failed\n",
				    lu->num);
				/* ignore error */
			}
		} else if (strcasecmp(spec->disktype, "RAW") == 0) {
			if (!spec->lu->readonly) {
				rc = spec->sync(spec, 0, spec->size);
				if (rc < 0) {
					//ISTGT_ERRLOG("LU%d: lu_disk_sync() failed\n", lu->num);
					/* ignore error */
				}
			}
			rc = spec->close(spec);
			if (rc < 0) {
				//ISTGT_ERRLOG("LU%d: lu_disk_close() failed\n", lu->num);
				/* ignore error */
			}
		} else {
			ISTGT_ERRLOG("LU%d: LUN%d: unsupported format\n", lu->num, i);
			return -1;
		}

		for (j = 0; j < spec->npr_keys; j++) {
			prkey = &spec->pr_keys[j];
			istgt_lu_disk_free_pr_key(prkey);
		}
		if (spec->rsv_key != 0) {
			xfree(spec->rsv_port);
			spec->rsv_port = NULL;
		}

		rc = pthread_mutex_destroy(&spec->ats_mutex);
		if (rc != 0) {
			//ISTGT_ERRLOG("LU%d: mutex_destroy() failed\n", lu->num);
			/* ignore error */
		}

		istgt_queue_destroy(&spec->cmd_queue);
		rc = pthread_mutex_destroy(&spec->cmd_queue_mutex);
		if (rc != 0) {
			//ISTGT_ERRLOG("LU%d: mutex_destroy() failed\n", lu->num);
			/* ignore error */
		}
		rc = pthread_mutex_destroy(&spec->wait_lu_task_mutex);
		if (rc != 0) {
			//ISTGT_ERRLOG("LU%d: mutex_destroy() failed\n", lu->num);
			/* ignore error */
		}
		xfree(spec->watsbuf);
		xfree(spec->wbuf);
		xfree(spec);
		lu->lun[i].spec = NULL;
	}

	return 0;
}

void
istgt_scsi_dump_cdb(uint8_t *cdb)
{
	int group;
	int cdblen = 0;
	int i;

	if (cdb == NULL)
		return;

	group = (cdb[0] >> 5) & 0x07;
	switch (group) {
	case 0x00:
		/* 6byte commands */
		cdblen = 6;
		break;
	case 0x01:
		/* 10byte commands */
		cdblen = 10;
		break;
	case 0x02:
		/* 10byte commands */
		cdblen = 10;
		break;
	case 0x03:
		/* reserved */
		if (cdb[0] == 0x7f) {
			/* variable length */
			cdblen = 8 + (cdb[7] & 0xff);
		} else {
			/* XXX */
			cdblen = 6;
		}
		break;
	case 0x04:
		/* 16byte commands */
		cdblen = 16;
		break;
	case 0x05:
		/* 12byte commands */
		cdblen = 12;
		break;
	case 0x06:
	case 0x07:
		/* vendor specific */
		cdblen = 6;
		break;
	}

	printf("CDB=");
	for (i = 0; i < cdblen; i++) {
		printf("%2.2x ", cdb[i]);
	}
	printf("\n");
}

void
istgt_strcpy_pad(uint8_t *dst, size_t size, const char *src, int pad)
{
	size_t len;

	len = strlen(src);
	if (len < size) {
		memcpy(dst, src, len);
		memset(dst + len, pad, (size - len));
	} else {
		memcpy(dst, src, size);
	}
}

#ifdef HAVE_UUID_H
uint64_t
istgt_uuid2uint64(uuid_t *uuid)
{
	uint64_t low, mid, hi;
	uint64_t r;

	low = (uint64_t) uuid->time_low;
	mid = (uint64_t) uuid->time_mid;
	hi  = (uint64_t) uuid->time_hi_and_version;
	r = (hi & 0xffffULL) << 48;
	r |= (mid & 0xffffULL) << 32;
	r |= (low & 0xffffffffULL);
	return r;
}
#endif /* HAVE_UUID_H */

uint64_t
istgt_get_lui(const char *name, int lun)
{
	char buf[MAX_TMPBUF];
	uint32_t crc32c;
	uint64_t r;

	if (lun >= 0) {
		snprintf(buf, sizeof buf, "%s,%d",
		    name, lun);
	} else {
		snprintf(buf, sizeof buf, "%s",
		    name);
	}
	crc32c = istgt_crc32c((uint8_t *) buf, strlen(buf));
	r = (uint64_t) crc32c;
	return r;
}

uint64_t
istgt_get_rkey(const char *initiator_name, uint64_t lui)
{
	ISTGT_MD5CTX md5ctx;
	uint8_t rkeymd5[ISTGT_MD5DIGEST_LEN];
	char buf[MAX_TMPBUF];
	uint64_t rkey;
	int idx;
	int i;

	snprintf(buf, sizeof buf, "%s,%16.16" PRIx64,
	    initiator_name, lui);

	istgt_md5init(&md5ctx);
	istgt_md5update(&md5ctx, buf, strlen(buf));
	istgt_md5final(rkeymd5, &md5ctx);

	rkey = 0U;
	idx = ISTGT_MD5DIGEST_LEN - 8;
	if (idx < 0) {
		ISTGT_WARNLOG("missing MD5 length\n");
		idx = 0;
	}
	for (i = idx; i < ISTGT_MD5DIGEST_LEN; i++) {
		rkey |= (uint64_t) rkeymd5[i];
		rkey = rkey << 8;
	}
	return rkey;
}

/* XXX */
#define COMPANY_ID 0xACDE48U // 24bits

int
istgt_lu_set_lid(uint8_t *buf, uint64_t vid)
{
	uint64_t naa;
	uint64_t enc;
	int total;

	naa = 0x3; // Locally Assigned

	/* NAA + LOCALLY ADMINISTERED VALUE */
	enc = (naa & 0xfULL) << (64-4); // 4bits
	enc |= vid & 0xfffffffffffffffULL; //60bits
	DSET64(&buf[0], enc);

	total = 8;
	return total;
}

int
istgt_lu_set_id(uint8_t *buf, uint64_t vid)
{
	uint64_t naa;
	uint64_t cid;
	uint64_t enc;
	int total;

	naa = 0x5; // IEEE Registered
	cid = COMPANY_ID; //IEEE COMPANY_ID

	/* NAA + COMPANY_ID + VENDOR SPECIFIC IDENTIFIER */
	enc = (naa & 0xfULL) << (64-4); // 4bits
	enc |= (cid & 0xffffffULL) << (64-4-24); // 24bits
	enc |= vid & 0xfffffffffULL; //36bits
	DSET64(&buf[0], enc);

	total = 8;
	return total;
}

int
istgt_lu_set_extid(uint8_t *buf, uint64_t vid, uint64_t vide)
{
	uint64_t naa;
	uint64_t cid;
	uint64_t enc;
	int total;

	naa = 0x6; // IEEE Registered Extended
	cid = COMPANY_ID; //IEEE COMPANY_ID

	/* NAA + COMPANY_ID + VENDOR SPECIFIC IDENTIFIER */
	enc = (naa & 0xfULL) << (64-4); // 4bits
	enc |= (cid & 0xffffffULL) << (64-4-24); // 24bits
	enc |= vid & 0xfffffffffULL; //36bits
	DSET64(&buf[0], enc);
	/* VENDOR SPECIFIC IDENTIFIER EXTENSION */
	DSET64(&buf[8], vide);

	total = 16;
	return total;
}

static int
istgt_lu_disk_scsi_report_luns(ISTGT_LU_Ptr lu, CONN_Ptr conn __attribute__((__unused__)), uint8_t *cdb __attribute__((__unused__)), int sel, uint8_t *data, int alloc_len)
{
	uint64_t fmt_lun, lun, method;
	int hlen = 0, len = 0;
	int i;

	if (alloc_len < 8) {
		return -1;
	}

	if (sel == 0x00) {
		/* logical unit with addressing method */
	} else if (sel == 0x01) {
		/* well known logical unit */
	} else if (sel == 0x02) {
		/* logical unit */
	} else {
		return -1;
	}

	/* LUN LIST LENGTH */
	DSET32(&data[0], 0);
	/* Reserved */
	DSET32(&data[4], 0);
	hlen = 8;

	for (i = 0; i < lu->maxlun; i++) {
		if (lu->lun[i].type == ISTGT_LU_LUN_TYPE_NONE) {
#if 0
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "LU%d: LUN%d none\n",
			    lu->num, i);
#endif
			continue;
		}
		if (alloc_len - (hlen + len) < 8) {
			return -1;
		}
		lun = (uint64_t) i;
		if (lu->maxlun <= 0x0100) {
			/* below 256 */
			method = 0x00U;
			fmt_lun = (method & 0x03U) << 62;
			fmt_lun |= (lun & 0x00ffU) << 48;
		} else if (lu->maxlun <= 0x4000) {
			/* below 16384 */
			method = 0x01U;
			fmt_lun = (method & 0x03U) << 62;
			fmt_lun |= (lun & 0x3fffU) << 48;
		} else {
			/* XXX */
			fmt_lun = 0;
		}
		/* LUN */
		DSET64(&data[hlen + len], fmt_lun);
		len += 8;
	}
	/* LUN LIST LENGTH */
	DSET32(&data[0], len);
	return hlen + len;
}

static int
istgt_lu_disk_scsi_inquiry(ISTGT_LU_DISK *spec, CONN_Ptr conn, uint8_t *cdb, uint8_t *data, int alloc_len)
{
	uint64_t LUI;
	uint8_t *cp, *cp2;
	uint32_t blocks;
	int hlen = 0, len = 0, plen, plen2;
	int pc;
	int pq, pd;
	int rmb;
	int evpd;
	int pg_tag;
	int i, j;

	if (alloc_len < 0xff) {
		return -1;
	}

	pq = 0x00;
	pd = SPC_PERIPHERAL_DEVICE_TYPE_DISK;
	rmb = 0;

#if 0
	LUI = istgt_uuid2uint64(&spec->uuid);
#else
	LUI = istgt_get_lui(spec->lu->name, spec->lun & 0xffffU);
#endif

	pc = cdb[2];
	evpd = BGET8(&cdb[1], 0);
	if (evpd) {
		/* Vital product data */
		switch (pc) {
		case SPC_VPD_SUPPORTED_VPD_PAGES:
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], pq, 7, 3);
			BDADD8W(&data[0], pd, 4, 5);
			/* PAGE CODE */
			data[1] = pc;
			/* Reserved */
			data[2] = 0;
			/* PAGE LENGTH */
			data[3] = 0;
			hlen = 4;

			data[4] = SPC_VPD_SUPPORTED_VPD_PAGES;      /* 0x00 */
			data[5] = SPC_VPD_UNIT_SERIAL_NUMBER;       /* 0x80 */
			data[6] = SPC_VPD_DEVICE_IDENTIFICATION;    /* 0x83 */
			data[7] = SPC_VPD_MANAGEMENT_NETWORK_ADDRESSES; /* 0x85 */
			data[8] = SPC_VPD_EXTENDED_INQUIRY_DATA;    /* 0x86 */
			data[9] = SPC_VPD_MODE_PAGE_POLICY;         /* 0x87 */
			data[10]= SPC_VPD_SCSI_PORTS;               /* 0x88 */
			data[11]= 0xb0; /* SBC Block Limits */
			data[12]= 0xb1; /* SBC Block Device Characteristics */
			len = 13 - hlen;
			if (spec->thin_provisioning) {
				data[13]= 0xb2; /* SBC Thin Provisioning */
				len++;
			}

			/* PAGE LENGTH */
			data[3] = len;
			break;

		case SPC_VPD_UNIT_SERIAL_NUMBER:
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], pq, 7, 3);
			BDADD8W(&data[0], pd, 4, 5);
			/* PAGE CODE */
			data[1] = pc;
			/* Reserved */
			data[2] = 0;
			/* PAGE LENGTH */
			data[3] = 0;
			hlen = 4;

			/* PRODUCT SERIAL NUMBER */
			if (spec->lu->lun[spec->lun].serial != NULL) {
				len = strlen(spec->lu->lun[spec->lun].serial);
				if (len > MAX_LU_SERIAL_STRING) {
					len = MAX_LU_SERIAL_STRING;
				}
				istgt_strcpy_pad(&data[4], len,
				    spec->lu->lun[spec->lun].serial, ' ');
			} else {
				len = strlen(spec->lu->inq_serial);
				if (len > MAX_LU_SERIAL_STRING) {
					len = MAX_LU_SERIAL_STRING;
				}
				istgt_strcpy_pad(&data[4], len,
				    spec->lu->inq_serial, ' ');
			}

			/* PAGE LENGTH */
			data[3] = len;
			break;

		case SPC_VPD_DEVICE_IDENTIFICATION:
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], pq, 7, 3);
			BDADD8W(&data[0], pd, 4, 5);
			/* PAGE CODE */
			data[1] = pc;
			/* PAGE LENGTH */
			DSET16(&data[2], 0);
			hlen = 4;

			/* Identification descriptor 1 */
			/* Logical Unit */
			cp = &data[hlen + len];

			/* PROTOCOL IDENTIFIER(7-4) CODE SET(3-0) */
			BDSET8W(&cp[0], 0, 7, 4);
			BDADD8W(&cp[0], SPC_VPD_CODE_SET_BINARY, 3, 4);
			/* PIV(7) ASSOCIATION(5-4) IDENTIFIER TYPE(3-0) */
			BDSET8W(&cp[1], 0, 7, 1); /* PIV=0 */
			BDADD8W(&cp[1], SPC_VPD_ASSOCIATION_LOGICAL_UNIT, 5, 2);
			BDADD8W(&cp[1], SPC_VPD_IDENTIFIER_TYPE_NAA, 3, 4);
			/* Reserved */
			cp[2] = 0;
			/* IDENTIFIER LENGTH */
			cp[3] = 0;

			/* IDENTIFIER */
#if 0
			/* 16bytes ID */
			plen = istgt_lu_set_extid(&cp[4], 0, LUI);
#else
			plen = istgt_lu_set_lid(&cp[4], LUI);
#endif

			cp[3] = plen;
			len += 4 + plen;

			/* Identification descriptor 2 */
			/* T10 VENDOR IDENTIFICATION */
			cp = &data[hlen + len];

			/* PROTOCOL IDENTIFIER(7-4) CODE SET(3-0) */
			BDSET8W(&cp[0], 0, 7, 4);
			BDADD8W(&cp[0], SPC_VPD_CODE_SET_UTF8, 3, 4);
			/* PIV(7) ASSOCIATION(5-4) IDENTIFIER TYPE(3-0) */
			BDSET8W(&cp[1], 0, 7, 1); /* PIV=0 */
			BDADD8W(&cp[1], SPC_VPD_ASSOCIATION_LOGICAL_UNIT, 5, 2);
			BDADD8W(&cp[1], SPC_VPD_IDENTIFIER_TYPE_T10_VENDOR_ID, 3, 4);
			/* Reserved */
			cp[2] = 0;
			/* IDENTIFIER LENGTH */
			cp[3] = 0;

			/* IDENTIFIER */
			/* T10 VENDOR IDENTIFICATION */
			istgt_strcpy_pad(&cp[4], 8, spec->lu->inq_vendor, ' ');
			plen = 8;
			/* VENDOR SPECIFIC IDENTIFIER */
			/* PRODUCT IDENTIFICATION */
			istgt_strcpy_pad(&cp[12], 16, spec->lu->inq_product, ' ');
			/* PRODUCT SERIAL NUMBER */
			if (spec->lu->lun[spec->lun].serial != NULL) {
				istgt_strcpy_pad(&cp[28], MAX_LU_SERIAL_STRING,
				    spec->lu->lun[spec->lun].serial, ' ');
			} else {
				istgt_strcpy_pad(&cp[28], MAX_LU_SERIAL_STRING,
				    spec->lu->inq_serial, ' ');
			}
			plen += 16 + MAX_LU_SERIAL_STRING;

			cp[3] = plen;
			len += 4 + plen;

			/* Identification descriptor 3 */
			/* Target Device */
			cp = &data[hlen + len];

			/* PROTOCOL IDENTIFIER(7-4) CODE SET(3-0) */
			BDSET8W(&cp[0], SPC_PROTOCOL_IDENTIFIER_ISCSI, 7, 4);
			BDADD8W(&cp[0], SPC_VPD_CODE_SET_UTF8, 3, 4);
			/* PIV(7) ASSOCIATION(5-4) IDENTIFIER TYPE(3-0) */
			BDSET8W(&cp[1], 1, 7, 1); /* PIV */
			BDADD8W(&cp[1], SPC_VPD_ASSOCIATION_TARGET_DEVICE, 5, 2);
			BDADD8W(&cp[1], SPC_VPD_IDENTIFIER_TYPE_SCSI_NAME, 3, 4);
			/* Reserved */
			cp[2] = 0;
			/* IDENTIFIER LENGTH */
			cp[3] = 0;

			/* IDENTIFIER */
			plen = snprintf((char *) &cp[4], MAX_TARGET_NAME,
			    "%s", spec->lu->name);
			cp[3] = plen;
			len += 4 + plen;

			/* Identification descriptor 4 */
			/* Target Port */
			cp = &data[hlen + len];

			/* PROTOCOL IDENTIFIER(7-4) CODE SET(3-0) */
			BDSET8W(&cp[0], SPC_PROTOCOL_IDENTIFIER_ISCSI, 7, 4);
			BDADD8W(&cp[0], SPC_VPD_CODE_SET_UTF8, 3, 4);
			/* PIV(7) ASSOCIATION(5-4) IDENTIFIER TYPE(3-0) */
			BDSET8W(&cp[1], 1, 7, 1); /* PIV */
			BDADD8W(&cp[1], SPC_VPD_ASSOCIATION_TARGET_PORT, 5, 2);
			BDADD8W(&cp[1], SPC_VPD_IDENTIFIER_TYPE_SCSI_NAME, 3, 4);
			/* Reserved */
			cp[2] = 0;
			/* IDENTIFIER LENGTH */
			cp[3] = 0;

			/* IDENTIFIER */
			plen = snprintf((char *) &cp[4], MAX_TARGET_NAME,
			    "%s"",t,0x""%4.4x", spec->lu->name, conn->portal.tag);
			cp[3] = plen;
			len += 4 + plen;

			/* Identification descriptor 5 */
			/* Relative Target Port */
			cp = &data[hlen + len];

			/* PROTOCOL IDENTIFIER(7-4) CODE SET(3-0) */
			BDSET8W(&cp[0], SPC_PROTOCOL_IDENTIFIER_ISCSI, 7, 4);
			BDADD8W(&cp[0], SPC_VPD_CODE_SET_BINARY, 3, 4);
			/* PIV(7) ASSOCIATION(5-4) IDENTIFIER TYPE(3-0) */
			BDSET8W(&cp[1], 1, 7, 1); /* PIV */
			BDADD8W(&cp[1], SPC_VPD_ASSOCIATION_TARGET_PORT, 5, 2);
			BDADD8W(&cp[1], SPC_VPD_IDENTIFIER_TYPE_RELATIVE_TARGET_PORT,
			    3, 4);
			/* Reserved */
			cp[2] = 0;
			/* IDENTIFIER LENGTH */
			cp[3] = 0;

			/* IDENTIFIER */
			/* Obsolete */
			DSET16(&cp[4], 0);
			/* Relative Target Port Identifier */
			//DSET16(&cp[6], 1); /* port1 as port A */
			//DSET16(&cp[6], 2); /* port2 as port B */
			DSET16(&cp[6], (uint16_t) (1 + conn->portal.idx));
			plen = 4;

			cp[3] = plen;
			len += 4 + plen;

			/* Identification descriptor 6 */
			/* Target port group */
			cp = &data[hlen + len];

			/* PROTOCOL IDENTIFIER(7-4) CODE SET(3-0) */
			BDSET8W(&cp[0], SPC_PROTOCOL_IDENTIFIER_ISCSI, 7, 4);
			BDADD8W(&cp[0], SPC_VPD_CODE_SET_BINARY, 3, 4);
			/* PIV(7) ASSOCIATION(5-4) IDENTIFIER TYPE(3-0) */
			BDSET8W(&cp[1], 1, 7, 1); /* PIV */
			BDADD8W(&cp[1], SPC_VPD_ASSOCIATION_TARGET_PORT, 5, 2);
			BDADD8W(&cp[1], SPC_VPD_IDENTIFIER_TYPE_TARGET_PORT_GROUP,
			    3, 4);
			/* Reserved */
			cp[2] = 0;
			/* IDENTIFIER LENGTH */
			cp[3] = 0;

			/* IDENTIFIER */
			/* Reserved */
			DSET16(&cp[4], 0);
			/* TARGET PORT GROUP */
			DSET16(&cp[6], (uint16_t) (conn->portal.tag));
			plen = 4;

			cp[3] = plen;
			len += 4 + plen;

			/* Identification descriptor 7 */
			/* Logical unit group */
			cp = &data[hlen + len];

			/* PROTOCOL IDENTIFIER(7-4) CODE SET(3-0) */
			BDSET8W(&cp[0], SPC_PROTOCOL_IDENTIFIER_ISCSI, 7, 4);
			BDADD8W(&cp[0], SPC_VPD_CODE_SET_BINARY, 3, 4);
			/* PIV(7) ASSOCIATION(5-4) IDENTIFIER TYPE(3-0) */
			BDSET8W(&cp[1], 1, 7, 1); /* PIV */
			BDADD8W(&cp[1], SPC_VPD_ASSOCIATION_TARGET_PORT, 5, 2);
			BDADD8W(&cp[1], SPC_VPD_IDENTIFIER_TYPE_LOGICAL_UNIT_GROUP,
			    3, 4);
			/* Reserved */
			cp[2] = 0;
			/* IDENTIFIER LENGTH */
			cp[3] = 0;

			/* IDENTIFIER */
			/* Reserved */
			DSET16(&cp[4], 0);
			/* LOGICAL UNIT GROUP */
			DSET16(&cp[6], (uint16_t) (spec->lu->num));
			plen = 4;

			cp[3] = plen;
			len += 4 + plen;

			/* PAGE LENGTH */
			if (len > 0xffff) {
				len = 0xffff;
			}
			DSET16(&data[2], len);
			break;

		case SPC_VPD_EXTENDED_INQUIRY_DATA:
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], pq, 7, 3);
			BDADD8W(&data[0], pd, 4, 5);
			/* PAGE CODE */
			data[1] = pc;
			/* Reserved */
			data[2] = 0;
			/* PAGE LENGTH */
			data[3] = 0;
			hlen = 4;

			/* RTO(3) GRD_CHK(2) APP_CHK(1) REF_CHK(0) */
			data[4] = 0;
			/* GROUP_SUP(4) PRIOR_SUP(3) HEADSUP(2) ORDSUP(1) SIMPSUP(0) */
			data[5] = 0;
			if (spec->queue_depth != 0) {
				BDADD8(&data[5], 1, 2);     /* HEADSUP */
				//BDADD8(&data[5], 1, 1);     /* ORDSUP */
				BDADD8(&data[5], 1, 0);     /* SIMPSUP */
			}
			/* NV_SUP(1) V_SUP(0) */
			data[6] = 0;
			/* Reserved[7-63] */
			memset(&data[7], 0, (64 - 7));
			len = 64 - hlen;

			/* PAGE LENGTH */
			data[3] = len;
			break;

		case SPC_VPD_MANAGEMENT_NETWORK_ADDRESSES:
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], pq, 7, 3);
			BDADD8W(&data[0], pd, 4, 5);
			/* PAGE CODE */
			data[1] = pc;
			/* PAGE LENGTH */
			DSET16(&data[2], 0);
			hlen = 4;

#if 0
			/* Network services descriptor N */
			cp = &data[hlen + len];

			/* ASSOCIATION(6-5) SERVICE TYPE(4-0) */
			BDSET8W(&cp[0], 0x00, 6, 2);
			BDADD8W(&cp[0], 0x00, 4, 5);
			/* Reserved */
			cp[1] = 0;
			/* NETWORK ADDRESS LENGTH */
			DSET16(&cp[2], 0);
			/* NETWORK ADDRESS */
			cp[4] = 0;
			/* ... */
			plen = 0;
			DSET16(&cp[2], plen);
			len += 4 + plen;
#endif

			/* PAGE LENGTH */
			if (len > 0xffff) {
				len = 0xffff;
			}
			DSET16(&data[2], len);
			break;

		case SPC_VPD_MODE_PAGE_POLICY:
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], pq, 7, 3);
			BDADD8W(&data[0], pd, 4, 5);
			/* PAGE CODE */
			data[1] = pc;
			/* PAGE LENGTH */
			DSET16(&data[2], 0);
			hlen = 4;

			/* Mode page policy descriptor 1 */
			cp = &data[hlen + len];

			/* POLICY PAGE CODE(5-0) */
			BDSET8W(&cp[0], 0x3f, 5, 6);    /* all page code */
			/* POLICY SUBPAGE CODE */
			cp[1] = 0xff;                   /* all sub page */
			/* MLUS(7) MODE PAGE POLICY(1-0) */
			//BDSET8(&cp[2], 1, 7); /* multiple logical units share */
			BDSET8(&cp[2], 0, 7); /* own copy */
			BDADD8W(&cp[2], 0x00, 1, 2); /* Shared */
			//BDADD8W(&cp[2], 0x01, 1, 2); /* Per target port */
			//BDADD8W(&cp[2], 0x02, 1, 2); /* Per initiator port */
			//BDADD8W(&cp[2], 0x03, 1, 2); /* Per I_T nexus */
			/* Reserved */
			cp[3] = 0;
			len += 4;

			/* PAGE LENGTH */
			if (len > 0xffff) {
				len = 0xffff;
			}
			DSET16(&data[2], len);
			break;

		case SPC_VPD_SCSI_PORTS:
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], pq, 7, 3);
			BDADD8W(&data[0], pd, 4, 5);
			/* PAGE CODE */
			data[1] = pc;
			/* PAGE LENGTH */
			DSET16(&data[2], 0);
			hlen = 4;

			/* Identification descriptor list */
			for (i = 0; i < spec->lu->maxmap; i++) {
				pg_tag = spec->lu->map[i].pg_tag;
				/* skip same pg_tag */
				for (j = 0; j < i; j++) {
					if (spec->lu->map[j].pg_tag == pg_tag) {
						goto skip_pg_tag;
					}
				}

				/* Identification descriptor N */
				cp = &data[hlen + len];

				/* Reserved */
				DSET16(&cp[0], 0);
				/* RELATIVE PORT IDENTIFIER */
				DSET16(&cp[2], (uint16_t) (1 + pg_tag));
				/* Reserved */
				DSET16(&cp[4], 0);
				/* INITIATOR PORT TRANSPORTID LENGTH */
				DSET16(&cp[6], 0);
				/* Reserved */
				DSET16(&cp[8], 0);
				/* TARGET PORT DESCRIPTORS LENGTH */
				DSET16(&cp[10], 0);
				len += 12;

				plen2 = 0;
				/* Target port descriptor 1 */
				cp2 = &data[hlen + len + plen2];

				/* PROTOCOL IDENTIFIER(7-4) CODE SET(3-0) */
				BDSET8W(&cp2[0], SPC_PROTOCOL_IDENTIFIER_ISCSI, 7, 4);
				BDADD8W(&cp2[0], SPC_VPD_CODE_SET_UTF8, 3, 4);
				/* PIV(7) ASSOCIATION(5-4) IDENTIFIER TYPE(3-0) */
				BDSET8W(&cp2[1], 1, 7, 1); /* PIV */
				BDADD8W(&cp2[1], SPC_VPD_ASSOCIATION_TARGET_PORT, 5, 2);
				BDADD8W(&cp2[1], SPC_VPD_IDENTIFIER_TYPE_SCSI_NAME, 3, 4);
				/* Reserved */
				cp2[2] = 0;
				/* IDENTIFIER LENGTH */
				cp2[3] = 0;

				/* IDENTIFIER */
				plen = snprintf((char *) &cp2[4], MAX_TARGET_NAME,
				    "%s"",t,0x""%4.4x", spec->lu->name, pg_tag);
				cp2[3] = plen;
				plen2 += 4 + plen;

				/* TARGET PORT DESCRIPTORS LENGTH */
				DSET16(&cp[10], plen2);
				len += plen2;
			skip_pg_tag:
				;
			}

			/* PAGE LENGTH */
			if (len > 0xffff) {
				len = 0xffff;
			}
			DSET16(&data[2], len);
			break;

		case 0xb0: /* SBC Block Limits */
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], pq, 7, 3);
			BDADD8W(&data[0], pd, 4, 5);
			/* PAGE CODE */
			data[1] = pc;
			/* PAGE LENGTH */
			DSET16(&data[2], 0);
			hlen = 4;

			/* WSNZ(0) */
			BDSET8(&data[4], 0, 0); /* support zero length in WRITE SAME */
			/* MAXIMUM COMPARE AND WRITE LENGTH */
			blocks = ISTGT_LU_WORK_ATS_BLOCK_SIZE / (uint32_t) spec->blocklen;
			if (blocks > 0xff) {
				blocks = 0xff;
			}
			data[5] = (uint8_t) blocks;
			if (spec->lu->istgt->swmode == ISTGT_SWMODE_TRADITIONAL) {
				/* no support compare and write */
				data[5] = 0;
			}

			/* force align to 4KB */
			if (spec->blocklen < 4096) {
				blocks = 4096 / (uint32_t) spec->blocklen;
				/* OPTIMAL TRANSFER LENGTH GRANULARITY */
				DSET16(&data[6], blocks);
				/* MAXIMUM TRANSFER LENGTH */
				DSET32(&data[8], 0); /* no limit */
				/* OPTIMAL TRANSFER LENGTH */
				blocks = ISTGT_LU_WORK_BLOCK_SIZE / (uint32_t) spec->blocklen;
				DSET32(&data[12], blocks);
				/* MAXIMUM PREFETCH XDREAD XDWRITE TRANSFER LENGTH */
				DSET32(&data[16], 0);
			} else {
				blocks = 1;
				/* OPTIMAL TRANSFER LENGTH GRANULARITY */
				DSET16(&data[6], blocks);
				/* MAXIMUM TRANSFER LENGTH */
				DSET32(&data[8], 0); /* no limit */
				/* OPTIMAL TRANSFER LENGTH */
				blocks = ISTGT_LU_WORK_BLOCK_SIZE / (uint32_t) spec->blocklen;
				DSET32(&data[12], blocks);
				/* MAXIMUM PREFETCH XDREAD XDWRITE TRANSFER LENGTH */
				DSET32(&data[16], 0);
			}
			len = 20 - hlen;

			if (1 || spec->thin_provisioning) {
				/* MAXIMUM UNMAP LBA COUNT */
				DSET32(&data[20], 0); /* not implement UNMAP */
				/* MAXIMUM UNMAP BLOCK DESCRIPTOR COUNT */
				DSET32(&data[24], 0); /* not implement UNMAP */
				/* OPTIMAL UNMAP GRANULARITY */
				DSET32(&data[28], 0); /* not specified */
				/* UNMAP GRANULARITY ALIGNMENT */
				DSET32(&data[32], (0 & 0x7fffffffU));
				/* UGAVALID(7) */
				BDADD8(&data[32], 0, 7); /* not valid ALIGNMENT */
				/* MAXIMUM WRITE SAME LENGTH */
				DSET64(&data[36], 0); /* no limit */
				/* Reserved */
				memset(&data[44], 0x00, 64-44);
				len = 64 - hlen;
			}

			DSET16(&data[2], len);
			break;

		case 0xb1: /* SBC Block Device Characteristics */
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], pq, 7, 3);
			BDADD8W(&data[0], pd, 4, 5);
			/* PAGE CODE */
			data[1] = pc;
			/* PAGE LENGTH */
			DSET16(&data[2], 0);
			hlen = 4;

			/* MEDIUM ROTATION RATE */
			//DSET16(&data[4], 0x0000); /* not reported */
			//DSET16(&data[4], 0x0001); /* Non-rotating medium (solid state) */
			//DSET16(&data[4], 5400); /* rotation rate (5400rpm) */
			//DSET16(&data[4], 7200); /* rotation rate (7200rpm) */
			//DSET16(&data[4], 10000); /* rotation rate (10000rpm) */
			//DSET16(&data[4], 15000); /* rotation rate (15000rpm) */
			DSET16(&data[4], spec->lu->lun[spec->lun].rotationrate);
			/* Reserved */
			data[6] = 0;
			/* NOMINAL FORM FACTOR(3-0) */
			//BDSET8W(&data[7], 0x00, 3, 4); /* not reported */
			//BDSET8W(&data[7], 0x01, 3, 4); /* 5.25 inch */
			//BDSET8W(&data[7], 0x02, 3, 4); /* 3.5 inch */
			//BDSET8W(&data[7], 0x03, 3, 4); /* 2.5 inch */
			//BDSET8W(&data[7], 0x04, 3, 4); /* 1.8 inch */
			//BDSET8W(&data[7], 0x05, 3, 4); /* less 1.8 inch */
			BDSET8W(&data[7], spec->lu->lun[spec->lun].formfactor, 3, 4);
			/* Reserved */
			memset(&data[8], 0x00, 64-8);

			len = 64 - hlen;
			DSET16(&data[2], len);
			break;

		case 0xb2: /* SBC Thin Provisioning */
			if (!spec->thin_provisioning) {
				ISTGT_ERRLOG("unsupported INQUIRY VPD page 0x%x\n", pc);
				return -1;
			}

			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], pq, 7, 3);
			BDADD8W(&data[0], pd, 4, 5);
			/* PAGE CODE */
			data[1] = pc;
			/* PAGE LENGTH */
			DSET16(&data[2], 0);
			hlen = 4;

			/* THRESHOLD EXPONENT */
			data[4] = 0;
			/* DP(0) */
			BDSET8(&data[5], 0, 0);
			/* Reserved */
			DSET16(&data[6], 0);
			len = 6 - hlen;
#if 0
			/* XXX not yet */
			/* PROVISIONING GROUP DESCRIPTOR ... */
			DSET16(&data[8], 0);
			len = 8 - hlen;
#endif

			DSET16(&data[2], len);
			break;

		default:
			if (pc >= 0xc0 && pc <= 0xff) {
				ISTGT_WARNLOG("Vendor specific INQUIRY VPD page 0x%x\n", pc);
			} else {
				ISTGT_ERRLOG("unsupported INQUIRY VPD page 0x%x\n", pc);
			}
			return -1;
		}
	} else {
		/* Standard INQUIRY data */
		/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
		BDSET8W(&data[0], pq, 7, 3);
		BDADD8W(&data[0], pd, 4, 5);
		/* RMB(7) */
		BDSET8W(&data[1], rmb, 7, 1);
		/* VERSION */
		/* See SPC3/SBC2/MMC4/SAM2 for more details */
		data[2] = SPC_VERSION_SPC3;
		/* NORMACA(5) HISUP(4) RESPONSE DATA FORMAT(3-0) */
		BDSET8W(&data[3], 2, 3, 4);		/* format 2 */
		BDADD8(&data[1], 1, 4);         /* hierarchical support */
		/* ADDITIONAL LENGTH */
		data[4] = 0;
		hlen = 5;

		/* SCCS(7) ACC(6) TPGS(5-4) 3PC(3) PROTECT(0) */
		data[5] = 0;
		//BDADD8W(&data[5], 1, 7, 1); /* storage array controller */
		BDADD8W(&data[5], 0x00, 5, 2); /* Not support TPGS */
		//BDADD8W(&data[5], 0x01, 5, 2); /* Only implicit */
		//BDADD8W(&data[5], 0x02, 5, 2); /* Only explicit */
		//BDADD8W(&data[5], 0x03, 5, 2); /* Both explicit and implicit */
		/* BQUE(7) ENCSERV(6) VS(5) MULTIP(4) MCHNGR(3) ADDR16(0) */
		data[6] = 0;
		BDADD8W(&data[6], 1, 4, 1); /* MULTIP */
		/* WBUS16(5) SYNC(4) LINKED(3) CMDQUE(1) VS(0) */
		data[7] = 0;
		if (spec->queue_depth != 0) {
			BDADD8(&data[7], 1, 1);     /* CMDQUE */
		}
		/* T10 VENDOR IDENTIFICATION */
		istgt_strcpy_pad(&data[8], 8, spec->lu->inq_vendor, ' ');
		/* PRODUCT IDENTIFICATION */
		istgt_strcpy_pad(&data[16], 16, spec->lu->inq_product, ' ');
		/* PRODUCT REVISION LEVEL */
		istgt_strcpy_pad(&data[32], 4, spec->lu->inq_revision, ' ');
		/* Vendor specific */
		memset(&data[36], 0x20, 20);
		/* CLOCKING(3-2) QAS(1) IUS(0) */
		data[56] = 0;
		/* Reserved */
		data[57] = 0;
		/* VERSION DESCRIPTOR 1-8 */
		DSET16(&data[58], 0x0960); /* iSCSI (no version claimed) */
		DSET16(&data[60], 0x0300); /* SPC-3 (no version claimed) */
		DSET16(&data[62], 0x0320); /* SBC-2 (no version claimed) */
		DSET16(&data[64], 0x0040); /* SAM-2 (no version claimed) */
		DSET16(&data[66], 0x0000);
		DSET16(&data[68], 0x0000);
		DSET16(&data[70], 0x0000);
		DSET16(&data[72], 0x0000);
		/* Reserved[74-95] */
		memset(&data[74], 0, (96 - 74));
		/* Vendor specific parameters[96-n] */
		//data[96] = 0;
		len = 96 - hlen;

		/* ADDITIONAL LENGTH */
		data[4] = len;
	}

	return hlen + len;
}

#define MODE_SENSE_PAGE_INIT(B,L,P,SP)					\
	do {								\
		memset((B), 0, (L));					\
		if ((SP) != 0x00) {					\
			(B)[0] = (P) | 0x40; /* PAGE + SPF=1 */		\
			(B)[1] = (SP);					\
			DSET16(&(B)[2], (L) - 4);			\
		} else {						\
			(B)[0] = (P);					\
			(B)[1] = (L) - 2;				\
		}							\
	} while (0)

static int
istgt_lu_disk_scsi_mode_sense_page(ISTGT_LU_DISK *spec, CONN_Ptr conn, uint8_t *cdb, int pc, int page, int subpage, uint8_t *data, int alloc_len)
{
	uint8_t *cp;
	int len = 0;
	int plen;
	int rc;
	int i;

#if 0
	printf("pc=%d, page=%2.2x, subpage=%2.2x\n", pc, page, subpage);
#endif
#if 0
	ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE: pc=%d, page=%2.2x, subpage=%2.2x\n", pc, page, subpage);
#endif

	if (pc == 0x00) {
		/* Current values */
	} else if (pc == 0x01) {
		/* Changeable values */
		if (page != 0x08) {
			/* not supported */
			return -1;
		}
	} else if (pc == 0x02) {
		/* Default values */
	} else {
		/* Saved values */
	}

	cp = &data[len];
	switch (page) {
	case 0x00:
		/* Vendor specific */
		break;
	case 0x01:
		/* Read-Write Error Recovery */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE Read-Write Error Recovery\n");
		if (subpage != 0x00)
			break;
		plen = 0x0a + 2;
		MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
		len += plen;
		break;
	case 0x02:
		/* Disconnect-Reconnect */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE Disconnect-Reconnect\n");
		if (subpage != 0x00)
			break;
		plen = 0x0e + 2;
		MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
		len += plen;
		break;
	case 0x03:
		/* Obsolete (Format Device) */
		break;
	case 0x04:
		/* Obsolete (Rigid Disk Geometry) */
		break;
	case 0x05:
		/* Obsolete (Rigid Disk Geometry) */
		break;
	case 0x06:
		/* Reserved */
		break;
	case 0x07:
		/* Verify Error Recovery */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE Verify Error Recovery\n");
		if (subpage != 0x00)
			break;
		plen = 0x0a + 2;
		MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
		len += plen;
		break;
	case 0x08:
		/* Caching */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE Caching\n");
		if (subpage != 0x00)
			break;

		plen = 0x12 + 2;
		MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
		BDADD8(&cp[0], 1, 7); /* PS */
		if (pc == 0x01) {
			// Changeable values
			BDADD8(&cp[2], 1, 2); /* WCE */
			BDADD8(&cp[2], 1, 0); /* RCD */
			len += plen;
			break;
		}
		BDADD8(&cp[2], 1, 2); /* WCE */
		//BDADD8(&cp[2], 1, 0); /* RCD */
		{
			int fd;
			fd = spec->fd;
			rc = fcntl(fd , F_GETFL, 0);
			if (rc != -1 && !(rc & O_FSYNC)) {
				BDADD8(&cp[2], 1, 2); /* WCE=1 */
			} else {
				BDADD8(&cp[2], 0, 2); /* WCE=0 */
			}
		}
		if (spec->read_cache == 0) {
			BDADD8(&cp[2], 1, 0); /* RCD=1 */
		} else {
			BDADD8(&cp[2], 0, 0); /* RCD=0 */
		}
		len += plen;
		break;
	case 0x09:
		/* Obsolete */
		break;
	case 0x0a:
		switch (subpage) {
		case 0x00:
			/* Control */
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE Control\n");
			plen = 0x0a + 2;
			MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
			len += plen;
			break;
		case 0x01:
			/* Control Extension */
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE Control Extension\n");
			plen = 0x1c + 4;
			MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
			len += plen;
			break;
		case 0xff:
			/* All subpages */
			len += istgt_lu_disk_scsi_mode_sense_page(spec, conn, cdb, pc, page, 0x00, &data[len], alloc_len);
			len += istgt_lu_disk_scsi_mode_sense_page(spec, conn, cdb, pc, page, 0x01, &data[len], alloc_len);
			break;
		default:
			/* 0x02-0x3e: Reserved */
			break;
		}
		break;
	case 0x0b:
		/* Obsolete (Medium Types Supported) */
		break;
	case 0x0c:
		/* Obsolete (Notch And Partitio) */
		break;
	case 0x0d:
		/* Obsolete */
		break;
	case 0x0e:
	case 0x0f:
		/* Reserved */
		break;
	case 0x10:
		/* XOR Control */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE XOR Control\n");
		if (subpage != 0x00)
			break;
		plen = 0x16 + 2;
		MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
		len += plen;
		break;
	case 0x11:
	case 0x12:
	case 0x13:
		/* Reserved */
		break;
	case 0x14:
		/* Enclosure Services Management */
		break;
	case 0x15:
	case 0x16:
	case 0x17:
		/* Reserved */
		break;
	case 0x18:
		/* Protocol-Specific LUN */
#if 0
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE Protocol-Specific LUN\n");
		if (subpage != 0x00)
			break;
		plen = 0x04 + 0x00 + 2;
		MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
		len += plen;
#endif
		break;
	case 0x19:
		/* Protocol-Specific Port */
#if 0
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE Protocol-Specific Port\n");
		if (subpage != 0x00)
			break;
		plen = 0x04 + 0x00 + 2;
		MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
		len += plen;
#endif
		break;
	case 0x1a:
		/* Power Condition */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE Power Condition\n");
		if (subpage != 0x00)
			break;
		plen = 0x0a + 2;
		MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
		len += plen;
		break;
	case 0x1b:
		/* Reserved */
		break;
	case 0x1c:
		/* Informational Exceptions Control */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SENSE Informational Exceptions Control\n");
		if (subpage != 0x00)
			break;

		plen = 0x0a + 2;
		MODE_SENSE_PAGE_INIT(cp, plen, page, subpage);
		len += plen;
		break;
	case 0x1d:
	case 0x1e:
	case 0x1f:
		/* Reserved */
		break;
	case 0x20:
	case 0x21:
	case 0x22:
	case 0x23:
	case 0x24:
	case 0x25:
	case 0x26:
	case 0x27:
	case 0x28:
	case 0x29:
	case 0x2a:
	case 0x2b:
	case 0x2c:
	case 0x2d:
	case 0x2e:
	case 0x2f:
	case 0x30:
	case 0x31:
	case 0x32:
	case 0x33:
	case 0x34:
	case 0x35:
	case 0x36:
	case 0x37:
	case 0x38:
	case 0x39:
	case 0x3a:
	case 0x3b:
	case 0x3c:
	case 0x3d:
	case 0x3e:
		/* Vendor-specific */
		break;
	case 0x3f:
		switch (subpage) {
		case 0x00:
			/* All mode pages */
			for (i = 0x00; i < 0x3e; i ++) {
				len += istgt_lu_disk_scsi_mode_sense_page(spec, conn, cdb, pc, i, 0x00, &cp[len], alloc_len);
			}
			break;
		case 0xff:
			/* All mode pages and subpages */
			for (i = 0x00; i < 0x3e; i ++) {
				len += istgt_lu_disk_scsi_mode_sense_page(spec, conn, cdb, pc, i, 0x00, &cp[len], alloc_len);
			}
			for (i = 0x00; i < 0x3e; i ++) {
				len += istgt_lu_disk_scsi_mode_sense_page(spec, conn, cdb, pc, i, 0xff, &cp[len], alloc_len);
			}
			break;
		default:
			/* 0x01-0x3e: Reserved */
			break;
		}
	}

	return len;
}

static int
istgt_lu_disk_scsi_mode_sense6(ISTGT_LU_DISK *spec, CONN_Ptr conn, uint8_t *cdb, int dbd, int pc, int page, int subpage, uint8_t *data, int alloc_len)
{
	uint8_t *cp;
	int hlen = 0, len = 0, plen;
	int total;
	int llbaa = 0;

	data[0] = 0;                    /* Mode Data Length */
	data[1] = 0;                    /* Medium Type */
	data[2] = 0;                    /* Device-Specific Parameter */
	if (spec->lu->readonly) {
		BDADD8(&data[2], 1, 7);     /* WP */
	}
	data[3] = 0;                    /* Block Descripter Length */
	hlen = 4;

	cp = &data[4];
	if (dbd) {                      /* Disable Block Descripters */
		len = 0;
	} else {
		if (llbaa) {
			/* Number of Blocks */
			DSET64(&cp[0], spec->blockcnt);
			/* Reserved */
			DSET32(&cp[8], 0);
			/* Block Length */
			DSET32(&cp[12], (uint32_t) spec->blocklen);
			len = 16;
		} else {
			/* Number of Blocks */
			if (spec->blockcnt > 0xffffffffULL) {
				DSET32(&cp[0], 0xffffffffUL);
			} else {
				DSET32(&cp[0], (uint32_t) spec->blockcnt);
			}
			/* Block Length */
			DSET32(&cp[4], (uint32_t) spec->blocklen);
			len = 8;
		}
		cp += len;
	}
	data[3] = len;                  /* Block Descripter Length */

	plen = istgt_lu_disk_scsi_mode_sense_page(spec, conn, cdb, pc, page, subpage, &cp[0], alloc_len);
	if (plen < 0) {
		return -1;
	}
	cp += plen;

	total = hlen + len + plen;
	data[0] = total - 1;            /* Mode Data Length */

	return total;
}

static int
istgt_lu_disk_scsi_mode_sense10(ISTGT_LU_DISK *spec, CONN_Ptr conn, uint8_t *cdb, int dbd, int llbaa, int pc, int page, int subpage, uint8_t *data, int alloc_len)
{
	uint8_t *cp;
	int hlen = 0, len = 0, plen;
	int total;

	DSET16(&data[0], 0);            /* Mode Data Length */
	data[2] = 0;                    /* Medium Type */
	data[3] = 0;                    /* Device-Specific Parameter */
	if (spec->lu->readonly) {
		BDADD8(&data[3], 1, 7);     /* WP */
	}
	if (llbaa) {
		BDSET8(&data[4], 1, 1);      /* Long LBA */
	} else {
		BDSET8(&data[4], 0, 1);      /* Short LBA */
	}
	data[5] = 0;                    /* Reserved */
	DSET16(&data[6], 0);  		    /* Block Descripter Length */
	hlen = 8;

	cp = &data[8];
	if (dbd) {                      /* Disable Block Descripters */
		len = 0;
	} else {
		if (llbaa) {
			/* Number of Blocks */
			DSET64(&cp[0], spec->blockcnt);
			/* Reserved */
			DSET32(&cp[8], 0);
			/* Block Length */
			DSET32(&cp[12], (uint32_t) spec->blocklen);
			len = 16;
		} else {
			/* Number of Blocks */
			if (spec->blockcnt > 0xffffffffULL) {
				DSET32(&cp[0], 0xffffffffUL);
			} else {
				DSET32(&cp[0], (uint32_t) spec->blockcnt);
			}
			/* Block Length */
			DSET32(&cp[4], (uint32_t) spec->blocklen);
			len = 8;
		}
		cp += len;
	}
	DSET16(&data[6], len);          /* Block Descripter Length */

	plen = istgt_lu_disk_scsi_mode_sense_page(spec, conn, cdb, pc, page, subpage, &cp[0], alloc_len);
	if (plen < 0) {
		return -1;
	}
	cp += plen;

	total = hlen + len + plen;
	DSET16(&data[0], total - 2);	/* Mode Data Length */

	return total;
}

static int
istgt_lu_disk_transfer_data(CONN_Ptr conn, ISTGT_LU_CMD_Ptr lu_cmd, uint8_t *buf, size_t bufsize, size_t len)
{
	int rc;

	if (lu_cmd->lu->queue_depth == 0) {
		if (len > bufsize) {
			ISTGT_ERRLOG("bufsize(%zd) too small\n", bufsize);
			return -1;
		}
		rc = istgt_iscsi_transfer_out(conn, lu_cmd, buf, bufsize, len);
		if (rc < 0) {
			ISTGT_ERRLOG("iscsi_transfer_out()\n");
			return -1;
		}
	}
	return 0;
}

static int
istgt_lu_disk_scsi_mode_select_page(ISTGT_LU_DISK *spec, CONN_Ptr conn, uint8_t *cdb, int pf, int sp, uint8_t *data, size_t len)
{
	size_t hlen, plen;
	int ps, spf, page, subpage;
	int rc;

	if (pf == 0) {
		/* vendor specific */
		return 0;
	}

	if (len < 1)
		return 0;
	ps = BGET8(&data[0], 7);
	spf = BGET8(&data[0], 6);
	page = data[0] & 0x3f;
	if (spf) {
		/* Sub_page mode page format */
		hlen = 4;
		if (len < hlen)
			return 0;
		subpage = data[1];

		plen = DGET16(&data[2]);
	} else {
		/* Page_0 mode page format */
		hlen = 2;
		if (len < hlen)
			return 0;
		subpage = 0;
		plen = data[1];
	}
	plen += hlen;
	if (len < plen)
		return 0;

#if 0
	printf("ps=%d, page=%2.2x, subpage=%2.2x\n", ps, page, subpage);
#endif
	switch (page) {
	case 0x08:
		/* Caching */
		{
			int wce, rcd;

			ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SELECT Caching\n");
			if (subpage != 0x00)
				break;
			if (plen != 0x12 + hlen) {
				/* unknown format */
				break;
			}
			wce = BGET8(&data[2], 2); /* WCE */
			rcd = BGET8(&data[2], 0); /* RCD */

			{
				int fd;
				fd = spec->fd;
				rc = fcntl(fd , F_GETFL, 0);
				if (rc != -1) {
					if (wce) {
						ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SELECT Writeback cache enable\n");
						rc = fcntl(fd, F_SETFL, (rc & ~O_FSYNC));
						spec->write_cache = 1;
					} else {
						rc = fcntl(fd, F_SETFL, (rc | O_FSYNC));
						spec->write_cache = 0;
					}
					if (rc == -1) {
						/* XXX */
						//ISTGT_ERRLOG("fcntl(F_SETFL) failed\n");
					}
				}
			}
			if (rcd) {
				ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MODE_SELECT Read cache disable\n");
				spec->read_cache = 0;
			} else {
				spec->read_cache = 1;
			}
		}
		break;
	default:
		/* not supported */
		break;
	}

	len -= plen;
	if (len != 0) {
		rc = istgt_lu_disk_scsi_mode_select_page(spec, conn, cdb,  pf, sp, &data[plen], len);
		if (rc < 0) {
			return rc;
		}
	}
	return 0;
}

static int
istgt_lu_disk_scsi_read_defect10(ISTGT_LU_DISK *spec __attribute__((__unused__)), CONN_Ptr conn __attribute__((__unused__)), uint8_t *cdb __attribute__((__unused__)), int req_plist, int req_glist, int list_format, uint8_t *data, int alloc_len)
{
	uint8_t *cp;
	int hlen = 0, len = 0;
	int total;

	if (alloc_len < 4) {
		return -1;
	}

	data[0] = 0;				/* Reserved */
	data[1] = 0;
	if (req_plist) {
		BDADD8(&data[1], 1, 4);		/* PLISTV */
	}
	if (req_glist) {
		BDADD8(&data[1], 1, 3);		/* GLISTV */
	}
	BDADD8W(&data[1], list_format, 2, 3);	/* DEFECT LIST FORMAT */
	DSET16(&data[2], 0);			/* DEFECT LIST LENGTH */
	hlen = 4;

	cp = &data[4];
	/* defect list (if any) */
	len = 0;

	total = hlen + len;
	DSET16(&data[2], total - hlen);		/* DEFECT LIST LENGTH */
	return total;
}

static int
istgt_lu_disk_scsi_read_defect12(ISTGT_LU_DISK *spec __attribute__((__unused__)), CONN_Ptr conn __attribute__((__unused__)), uint8_t *cdb __attribute__((__unused__)), int req_plist, int req_glist, int list_format, uint8_t *data, int alloc_len)
{
	uint8_t *cp;
	int hlen = 0, len = 0;
	int total;

	if (alloc_len < 8) {
		return -1;
	}

	data[0] = 0;				/* Reserved */
	data[1] = 0;
	if (req_plist) {
		BDADD8(&data[1], 1, 4);		/* PLISTV */
	}
	if (req_glist) {
		BDADD8(&data[1], 1, 3);		/* GLISTV */
	}
	BDADD8W(&data[1], list_format, 2, 3);	/* DEFECT LIST FORMAT */
	data[2] = 0;				/* Reserved */
	data[3] = 0;				/* Reserved */
	DSET32(&data[4], 0);			/* DEFECT LIST LENGTH */
	hlen = 8;

	cp = &data[8];
	/* defect list (if any) */
	len = 0;

	total = hlen + len;
	DSET32(&data[4], total - hlen);		/* DEFECT LIST LENGTH */
	return total;
}

#if 0
static int
istgt_lu_disk_scsi_request_sense(ISTGT_LU_DISK *spec, CONN_Ptr conn, uint8_t *cdb, int desc, uint8_t *data, int alloc_len)
{
	int len = 0, plen;

	if (alloc_len < 18) {
		ISTGT_ERRLOG("alloc_len(%d) too small\n", alloc_len);
		return -1;
	}

	/* XXX TODO: fix */
	if (desc == 0) {
		/* fixed format */
		/* NO ADDITIONAL SENSE INFORMATION */
		/* BUILD_SENSE(NO_SENSE, 0x00, 0x00); */

		/* VALID(7) RESPONSE CODE(6-0) */
		BDSET8(&data[0], 0, 7);
		BDADD8W(&data[0], 0x70, 6, 7);
		/* Obsolete */
		data[1] = 0;
		/* FILEMARK(7) EOM(6) ILI(5) SENSE KEY(3-0) */
		BDSET8W(&data[2], ISTGT_SCSI_SENSE_NO_SENSE, 3, 4);
		/* INFORMATION */
		memset(&data[3], 0, 4);
		/* ADDITIONAL SENSE LENGTH */
		data[7] = 0;
		len = 8;

		/* COMMAND-SPECIFIC INFORMATION */
		memset(&data[8], 0, 4);
		/* ADDITIONAL SENSE CODE */
		data[12] = 0x00;
		/* ADDITIONAL SENSE CODE QUALIFIER */
		data[13] = 0x00;
		/* FIELD REPLACEABLE UNIT CODE */
		data[14] = 0;
		/* SKSV(7) SENSE KEY SPECIFIC(6-0,7-0,7-0) */
		data[15] = 0;
		data[16] = 0;
		data[17] = 0;
		plen = 18 - len;

		/* ADDITIONAL SENSE LENGTH */
		data[7] = plen;
	} else {
		/* descriptor format */
		/* NO ADDITIONAL SENSE INFORMATION */
		/* BUILD_SENSE(NO_SENSE, 0x00, 0x00); */

		/* RESPONSE CODE(6-0) */
		BDSET8W(&data[0], 0x72, 6, 7);
		/* SENSE KEY(3-0) */
		BDSET8W(&data[1], ISTGT_SCSI_SENSE_NO_SENSE, 3, 4);
		/* ADDITIONAL SENSE CODE */
		data[2] = 0x00;
		/* ADDITIONAL SENSE CODE QUALIFIER */
		data[3] = 0x00;
		/* Reserved */
		data[4] = 0;
		data[5] = 0;
		data[6] = 0;
		/* ADDITIONAL SENSE LENGTH */
		data[7] = 0;
		len = 8;

		/* Sense data descriptor(s) */
		plen = 8 - len;

		/* ADDITIONAL SENSE LENGTH */
		data[7] = plen;
	}
	return len;
}
#endif

static int
istgt_lu_disk_scsi_report_target_port_groups(ISTGT_LU_DISK *spec, CONN_Ptr conn, uint8_t *cdb __attribute__((__unused__)), uint8_t *data, int alloc_len)
{
	ISTGT_Ptr istgt;
	ISTGT_LU_Ptr lu;
	uint8_t *cp;
	uint8_t *cp_count;
	int hlen = 0, len = 0, plen;
	int total;
	int pg_tag;
	int nports;
	int i, j, k;
	int ridx;

	if (alloc_len < 0xfff) {
		return -1;
	}

	istgt = conn->istgt;
	lu = spec->lu;

	/* RETURN DATA LENGTH */
	DSET32(&data[0], 0);
	hlen = 4;

	MTX_LOCK(&istgt->mutex);
	for (i = 0; i < lu->maxmap; i++) {
		pg_tag = lu->map[i].pg_tag;
		/* skip same pg_tag */
		for (j = 0; j < i; j++) {
			if (lu->map[j].pg_tag == pg_tag) {
				goto skip_pg_tag;
			}
		}

		/* Target port group descriptor N */
		cp = &data[hlen + len];

		/* PREF(7) ASYMMETRIC ACCESS STATE(3-0) */
		cp[0] = 0;
		BDSET8(&cp[0], 1, 7); /* PREF */
		switch (lu->map[j].pg_aas & 0x0f) {
		case AAS_ACTIVE_OPTIMIZED:
			BDADD8W(&cp[0], AAS_ACTIVE_OPTIMIZED, 3, 4);
			break;
		case AAS_ACTIVE_NON_OPTIMIZED:
			BDADD8W(&cp[0], AAS_ACTIVE_NON_OPTIMIZED, 3, 4);
			break;
		case AAS_STANDBY:
			BDADD8W(&cp[0], AAS_STANDBY, 3, 4);
			break;
		case AAS_UNAVAILABLE:
			BDADD8W(&cp[0], AAS_UNAVAILABLE, 3, 4);
			break;
		case AAS_TRANSITIONING:
			BDADD8W(&cp[0], AAS_TRANSITIONING, 3, 4);
			break;
		default:
			ISTGT_ERRLOG("unsupported AAS\n");
			break;
		}
		/* T_SUP(7) U_SUP(3) S_SUP(2) S_SUP AN_SUP(1) AO_SUP(0) */
		cp[1] = 0;
		//BDADD8(&cp[1], 1, 7); /* transitioning supported */
		//BDADD8(&cp[1], 1, 3); /* unavailable supported */
		//BDADD8(&cp[1], 1, 2); /* standby supported */
		BDADD8(&cp[1], 1, 1); /* active/non-optimized supported */
		BDADD8(&cp[1], 1, 0); /* active/optimized supported */
		/* TARGET PORT GROUP */
		DSET16(&cp[2], pg_tag);
		/* Reserved */
		cp[4] = 0;
		/* STATUS CODE */
		if (lu->map[j].pg_aas & AAS_STATUS_IMPLICIT) {
			cp[5] = 0x02; /* by implicit */
		} else if (lu->map[j].pg_aas & AAS_STATUS_STPG) {
			cp[5] = 0x01; /* by SET TARGET PORT GROUPS */
		} else {
			cp[5] = 0;    /* No status */
		}
		/* Vendor specific */
		cp[6] = 0;
		/* TARGET PORT COUNT */
		cp[7] = 0;
		cp_count = &cp[7];
		plen = 8;
		len += plen;

		nports = 0;
		ridx = 0;
		MTX_LOCK(&istgt->mutex);
		for (j = 0; j < istgt->nportal_group; j++) {
			if (istgt->portal_group[j].tag == pg_tag) {
				for (k = 0; k < istgt->portal_group[j].nportals; k++) {
					/* Target port descriptor(s) */
					cp = &data[hlen + len];
					/* Obsolete */
					DSET16(&cp[0], 0);
					/* RELATIVE TARGET PORT IDENTIFIER */
					DSET16(&cp[2], (uint16_t) (1 + ridx));
					plen = 4;
					len += plen;
					nports++;
					ridx++;
				}
			} else {
				ridx += istgt->portal_group[j].nportals;
			}
		}
		MTX_UNLOCK(&istgt->mutex);

		if (nports > 0xff) {
			ISTGT_ERRLOG("too many portals in portal group\n");
			MTX_UNLOCK(&istgt->mutex);
			return -1;
		}

		/* TARGET PORT COUNT */
		cp_count[0] = nports;

	skip_pg_tag:
		;
	}
	MTX_UNLOCK(&istgt->mutex);

	total = hlen + len;
	if (total > alloc_len) {
		ISTGT_ERRLOG("alloc_len(%d) too small\n", alloc_len);
		return -1;
	}

	/* RETURN DATA LENGTH */
	DSET32(&data[0], total - 4);

	return total;
}

static int
istgt_lu_disk_scsi_set_target_port_groups(ISTGT_LU_DISK *spec, CONN_Ptr conn, uint8_t *cdb, uint8_t *data, int len)
{
	ISTGT_LU_Ptr lu;
	int pg_tag;
	int aas;
	int pg;
	int rc;
	int i;

	if (len < 4) {
		return -1;
	}

	lu = spec->lu;

	aas = BGET8W(&data[0], 3, 4);
	pg = DGET16(&data[2]);

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "AAS=0x%x, PG=0x%4.4x\n", aas, pg);

	for (i = 0; i < lu->maxmap; i++) {
		pg_tag = lu->map[i].pg_tag;
		if (pg != pg_tag)
			continue;

		switch (aas) {
		case AAS_ACTIVE_OPTIMIZED:
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "Active/optimized\n");
			break;
		case AAS_ACTIVE_NON_OPTIMIZED:
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "Active/non-optimized\n");
			break;
#if 0
		case AAS_STANDBY:
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "Standby\n");
			break;
		case AAS_UNAVAILABLE:
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "Unavailable\n");
			break;
#endif
		case AAS_TRANSITIONING:
			return -1;
		default:
			ISTGT_ERRLOG("unsupported AAS 0x%x\n", aas);
			return -1;
		}
		lu->map[i].pg_aas = aas;
		lu->map[i].pg_aas |= AAS_STATUS_STPG;
	}

	len -=4;
	if (len != 0) {
		rc = istgt_lu_disk_scsi_set_target_port_groups(spec, conn, cdb, data, len);
		if (rc < 0) {
			return rc;
		}
	}
	return 0;
}

static void
istgt_lu_disk_free_pr_key(ISTGT_LU_PR_KEY *prkey)
{
	int i;

	if (prkey == NULL)
		return;
	xfree(prkey->registered_initiator_port);
	prkey->registered_initiator_port = NULL;
	xfree(prkey->registered_target_port);
	prkey->registered_target_port = NULL;
	prkey->pg_idx = 0;
	prkey->pg_tag = 0;
	for (i = 0; i < prkey->ninitiator_ports; i++) {
		xfree(prkey->initiator_ports[i]);
		prkey->initiator_ports[i] = NULL;
	}
	xfree(prkey->initiator_ports);
	prkey->initiator_ports = NULL;
	prkey->all_tpg = 0;
}

static ISTGT_LU_PR_KEY *
istgt_lu_disk_find_pr_key(ISTGT_LU_DISK *spec, const char *initiator_port, const char *target_port, uint64_t key)
{
	ISTGT_LU_PR_KEY *prkey;
	int i;

	/* return pointer if I_T nexus is registered */
#ifdef ISTGT_TRACE_DISK
	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
	    "find prkey=0x%16.16"PRIx64", port=%s\n",
	    key, ((initiator_port != NULL) ? initiator_port : "N/A"));
#endif /* ISTGT_TRACE_DISK */

	if (initiator_port == NULL)
		return NULL;
	for (i = 0; i < spec->npr_keys; i++) {
		prkey = &spec->pr_keys[i];
		if (prkey == NULL)
			continue;
#ifdef ISTGT_TRACE_DISK
		if (key != 0) {
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "prkey=0x%16.16"PRIx64"\n",
			    prkey->key);
		}
#endif /* ISTGT_TRACE_DISK */
		if (key != 0 && prkey->key != key)
			continue;
#ifdef ISTGT_TRACE_DISK
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "pript=%s, ipt=%s\n",
		    prkey->registered_initiator_port,
		    initiator_port);
#endif /* ISTGT_TRACE_DISK */
		if (strcmp(prkey->registered_initiator_port,
			initiator_port) == 0) {
#ifdef ISTGT_TRACE_DISK
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "prtpt=%s, tpt=%s\n",
			    prkey->registered_target_port,
			    target_port);
#endif /* ISTGT_TRACE_DISK */
			if (prkey->all_tpg != 0
			    || target_port == NULL
			    || strcmp(prkey->registered_target_port,
				target_port) == 0) {
				return prkey;
			}
		}
	}
	return NULL;
}

static int
istgt_lu_disk_remove_other_pr_key(ISTGT_LU_DISK *spec, CONN_Ptr conn __attribute__((__unused__)), const char *initiator_port, const char *target_port, uint64_t key)
{
	ISTGT_LU_PR_KEY *prkey, *prkey1, *prkey2;
	int i, j;

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
	    "remove other prkey=0x%16.16"PRIx64", port=%s\n",
	    key, ((initiator_port != NULL) ? initiator_port : "N/A"));

	for (i = 0; i < spec->npr_keys; i++) {
		prkey = &spec->pr_keys[i];
		if (prkey == NULL)
			continue;
		if (key == 0 || prkey->key == key)
			continue;
		if (initiator_port == NULL ||
		    strcasecmp(prkey->registered_initiator_port,
			initiator_port) == 0)
			continue;
		if (prkey->all_tpg != 0
		    || target_port == NULL
		    || strcasecmp(prkey->registered_target_port,
			target_port) == 0)
			continue;

		istgt_lu_disk_free_pr_key(prkey);
		for (j = i; j < spec->npr_keys - 1; j++) {
			prkey1 = &spec->pr_keys[j];
			prkey2 = &spec->pr_keys[j+1];

			prkey1->registered_initiator_port
				= prkey2->registered_initiator_port;
			prkey2->registered_initiator_port = NULL;
			prkey1->registered_target_port
				= prkey2->registered_target_port;
			prkey2->registered_target_port = NULL;
			prkey1->pg_idx = prkey2->pg_idx;
			prkey2->pg_idx = 0;
			prkey1->pg_tag = prkey2->pg_tag;
			prkey2->pg_tag = 0;
			prkey1->ninitiator_ports = prkey2->ninitiator_ports;
			prkey2->ninitiator_ports = 0;
			prkey1->initiator_ports = prkey2->initiator_ports;
			prkey2->initiator_ports = NULL;
			prkey1->all_tpg = prkey2->all_tpg;
			prkey2->all_tpg = 0;
		}
		spec->npr_keys--;
	}
	return 0;
}

static int
istgt_lu_disk_remove_pr_key(ISTGT_LU_DISK *spec, CONN_Ptr conn __attribute__((__unused__)), const char *initiator_port, const char *target_port, uint64_t key)
{
	ISTGT_LU_PR_KEY *prkey, *prkey1, *prkey2;
	int i, j;

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
	    "remove prkey=0x%16.16"PRIx64", port=%s\n",
	    key, ((initiator_port != NULL) ? initiator_port : "N/A"));

	for (i = 0; i < spec->npr_keys; i++) {
		prkey = &spec->pr_keys[i];
		if (prkey == NULL)
			continue;
		if (key != 0 && prkey->key != key)
			continue;
		if (initiator_port != NULL
		    && strcasecmp(prkey->registered_initiator_port,
			initiator_port) != 0)
			continue;
		if (prkey->all_tpg == 0
		    && target_port != NULL
		    && strcasecmp(prkey->registered_target_port,
			target_port) != 0)
			continue;

		istgt_lu_disk_free_pr_key(prkey);
		for (j = i; j < spec->npr_keys - 1; j++) {
			prkey1 = &spec->pr_keys[j];
			prkey2 = &spec->pr_keys[j+1];

			prkey1->registered_initiator_port
				= prkey2->registered_initiator_port;
			prkey2->registered_initiator_port = NULL;
			prkey1->registered_target_port
				= prkey2->registered_target_port;
			prkey2->registered_target_port = NULL;
			prkey1->pg_idx = prkey2->pg_idx;
			prkey2->pg_idx = 0;
			prkey1->pg_tag = prkey2->pg_tag;
			prkey2->pg_tag = 0;
			prkey1->ninitiator_ports = prkey2->ninitiator_ports;
			prkey2->ninitiator_ports = 0;
			prkey1->initiator_ports = prkey2->initiator_ports;
			prkey2->initiator_ports = NULL;
			prkey1->all_tpg = prkey2->all_tpg;
			prkey2->all_tpg = 0;
		}
		spec->npr_keys--;
	}
	return 0;
}

static int
istgt_lu_parse_transport_id(char **tid, uint8_t *data, int len)
{
	int fc, pi;
	int hlen, plen;

	if (tid == NULL)
		return -1;
	if (data == NULL)
		return -1;

	fc = BGET8W(&data[0], 7, 2);
	pi = BGET8W(&data[0], 3, 4);
	if (fc != 0) {
		ISTGT_ERRLOG("FORMAT CODE != 0\n");
		return -1;
	}
	if (pi != SPC_VPD_IDENTIFIER_TYPE_SCSI_NAME) {
		ISTGT_ERRLOG("PROTOCOL IDENTIFIER != ISCSI\n");
		return -1;
	}

	/* PROTOCOL IDENTIFIER = 0x05 */
	hlen = 4;
	/* ADDITIONAL LENGTH */
	plen = DGET16(&data[2]);
	if (plen > len) {
		ISTGT_ERRLOG("invalid length %d (expected %d)\n",
		    plen, len);
		return -1;
	}
	if (plen > MAX_ISCSI_NAME) {
		ISTGT_ERRLOG("invalid length %d (expected %d)\n",
		    plen, MAX_ISCSI_NAME);
		return -1;
	}

	/* ISCSI NAME */
	*tid = xmalloc(plen + 1);
	memcpy(*tid, data, plen);
	(*tid)[plen] = '\0';
	strlwr(*tid);

	return hlen + plen;
}

static int
istgt_lu_disk_scsi_persistent_reserve_in(ISTGT_LU_DISK *spec, CONN_Ptr conn __attribute__((__unused__)), ISTGT_LU_CMD_Ptr lu_cmd, int sa, uint8_t *data, int alloc_len __attribute__((__unused__)))
{
	ISTGT_LU_PR_KEY *prkey;
	size_t hlen = 0, len = 0, plen;
	uint8_t *sense_data;
	size_t *sense_len;
	uint8_t *cp;
	int total;
	int i;

	sense_data = lu_cmd->sense_data;
	sense_len = &lu_cmd->sense_data_len;
	*sense_len = 0;

	cp = &data[hlen + len];
	total = 0;
	switch (sa) {
	case 0x00: /* READ KEYS */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "READ KEYS\n");

		/* PRGENERATION */
		DSET32(&data[0], spec->pr_generation);
		/* ADDITIONAL LENGTH  */
		DSET32(&data[4], 0);
		hlen = 8;

		for (i = 0; i < spec->npr_keys; i++) {
			prkey = &spec->pr_keys[i];
			/* reservation key N */
			cp = &data[hlen + len];
			DSET64(&cp[0], prkey->key);
			len += 8;
		}
		total = hlen + len;
		/* ADDITIONAL LENGTH  */
		DSET32(&data[4], total - hlen);
		break;

	case 0x01: /* READ RESERVATION */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "READ RESERVATION\n");

		/* PRGENERATION */
		DSET32(&data[0], spec->pr_generation);
		/* ADDITIONAL LENGTH  */
		DSET32(&data[4], 0);
		hlen = 8;

		if (spec->rsv_key != 0) {
			/* RESERVATION KEY */
			DSET64(&data[8], spec->rsv_key);
			/* Obsolete */
			DSET32(&data[16], 0);
			/* Reserved */
			data[20] = 0;
			/* SCOPE(7-4) TYPE(3-0) */
			BDSET8W(&data[21], spec->rsv_scope, 7, 4);
			BDADD8W(&data[21], spec->rsv_type, 3, 4);
			/* Obsolete */
			DSET16(&data[22], 0);
			len = 24 - hlen;
		}

		total = hlen + len;
		/* ADDITIONAL LENGTH  */
		DSET32(&data[4], total - hlen);
		break;

	case 0x02: /* REPORT CAPABILITIES */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "REPORT CAPABILITIES\n");

		/* LENGTH */
		DSET16(&data[0], 0x0008);
		/* CRH(4) SIP_C(3) ATP_C(2) PTPL_C(0) */
		data[2] = 0;
		//BDADD8(&data[2], 1, 4); /* Compatible Reservation Handling */
		BDADD8(&data[2], 1, 3); /* Specify Initiator Ports Capable */
		BDADD8(&data[2], 1, 2); /* All Target Ports Capable */
		//BDADD8(&data[2], 1, 0); /* Persist Through Power Loss Capable */
		/* TMV(7) PTPL_A(0) */
		data[3] = 0;
		//BDADD8(&data[2], 1, 7); /* Type Mask Valid */
		//BDADD8(&data[2], 1, 0); /* Persist Through Power Loss Activated */
		/* PERSISTENT RESERVATION TYPE MASK */
		DSET16(&data[4], 0);
		/* Reserved */
		DSET16(&data[6], 0);
		hlen = 8;

		total = hlen + len;
		break;

	case 0x03: /* READ FULL STATUS */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "READ FULL STATUS\n");

		/* PRGENERATION */
		DSET32(&data[0], spec->pr_generation);
		/* ADDITIONAL LENGTH  */
		DSET32(&data[4], 0);
		hlen = 8;

		for (i = 0; i < spec->npr_keys; i++) {
			prkey = &spec->pr_keys[i];
			/* Full status descriptors N */
			cp = &data[hlen + len];

			/* RESERVATION KEY */
			DSET64(&cp[0], prkey->key);
			/* Reserved */
			DSET64(&cp[8], 0);
			/* ALL_TG_PT(1) R_HOLDER(0) */
			cp[12] = 0;
			if (prkey->all_tpg) {
				BDADD8(&cp[12], 1, 1);
			}
			/* SCOPE(7-4) TYPE(3-0) */
			cp[13] = 0;
			if (spec->rsv_key != 0) {
				if (spec->rsv_key == prkey->key) {
					BDADD8(&cp[12], 1, 0);
					BDADD8W(&cp[13], spec->rsv_scope & 0x0f, 7, 4);
					BDADD8W(&cp[13], spec->rsv_type & 0x0f, 3, 4);
				}
			}
			/* Reserved */
			DSET32(&cp[14], 0);
			/* RELATIVE TARGET PORT IDENTIFIER */
			DSET16(&cp[18], 1 + prkey->pg_idx);
			/* ADDITIONAL DESCRIPTOR LENGTH */
			DSET32(&cp[20], 0);

			/* TRANSPORTID */
			plen = snprintf((char *) &cp[24], MAX_INITIATOR_NAME,
			    "%s",
			    prkey->registered_initiator_port);
			
			/* ADDITIONAL DESCRIPTOR LENGTH */
			DSET32(&cp[20], plen);
			len += 24 + plen;
		}

		total = hlen + len;
		/* ADDITIONAL LENGTH  */
		DSET32(&data[4], total - hlen);
		break;

	default:
		ISTGT_ERRLOG("unsupported service action 0x%x\n", sa);
		/* INVALID FIELD IN CDB */
		BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return -1;
	}

	lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
	return total;
}

static int
istgt_lu_disk_scsi_persistent_reserve_out(ISTGT_LU_DISK *spec, CONN_Ptr conn, ISTGT_LU_CMD_Ptr lu_cmd, int sa, int scope, int type, uint8_t *data, int len)
{
	ISTGT_LU_PR_KEY *prkey;
	uint8_t *sense_data;
	size_t *sense_len;
	char *old_rsv_port = NULL;
	char **initiator_ports;
	int maxports, nports;
	int plen, total;
	uint64_t rkey;
	uint64_t sarkey;
	int spec_i_pt, all_tg_pt, aptpl;
	int task_abort;
	int idx;
	int rc;
	int i;

	sense_data = lu_cmd->sense_data;
	sense_len = &lu_cmd->sense_data_len;
	*sense_len = 0;

	rkey = DGET64(&data[0]);
	sarkey = DGET64(&data[8]);
	spec_i_pt = BGET8(&data[20], 3);
	all_tg_pt = BGET8(&data[20], 2);
	aptpl = BGET8(&data[20], 0);

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
	    "sa=0x%2.2x, key=0x%16.16"PRIx64", sakey=0x%16.16"PRIx64
	    ", ipt=%d, tgpt=%d, aptpl=%d\n",
	    sa, rkey, sarkey, spec_i_pt, all_tg_pt, aptpl);
	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "port=%s\n",
	    conn->initiator_port);

	switch (sa) {
	case 0x00: /* REGISTER */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "REGISTER\n");

		if (aptpl != 0) {
			/* Activate Persist Through Power Loss */
			ISTGT_ERRLOG("unsupport Activate Persist Through Power Loss\n");
			/* INVALID FIELD IN PARAMETER LIST */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x26, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		/* lost reservations if daemon restart */

		prkey = istgt_lu_disk_find_pr_key(spec, conn->initiator_port,
		    conn->target_port, 0);
		if (prkey == NULL) {
			/* unregistered port */
			if (rkey != 0) {
				lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
				return -1;
			}
			if (sarkey != 0) {
				/* XXX check spec_i_pt */
			}
		} else {
			/* registered port */
			if (spec_i_pt) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			prkey = istgt_lu_disk_find_pr_key(spec,
			    conn->initiator_port, conn->target_port, rkey);
			if (prkey == NULL) {
				/* not found key */
				lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
				return -1;
			}
			/* remove existing keys */
			rc = istgt_lu_disk_remove_pr_key(spec, conn,
			    conn->initiator_port, conn->target_port, 0);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_remove_pr_key() failed\n");
				/* INTERNAL TARGET FAILURE */
				BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
		}

		/* unregister? */
		if (sarkey == 0) {
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			return 0;
		}

		goto do_register;

	case 0x01: /* RESERVE */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "RESERVE\n");

		prkey = istgt_lu_disk_find_pr_key(spec, conn->initiator_port,
		    conn->target_port, 0);
		if (prkey == NULL) {
			/* unregistered port */
			lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
			return -1;
		}

		prkey = istgt_lu_disk_find_pr_key(spec, conn->initiator_port,
		    conn->target_port, rkey);
		if (prkey == NULL) {
			/* not found key */
			lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
			return -1;
		}
		if (spec->rsv_key == 0) {
			/* no reservation */
		} else {
			if (prkey->key != spec->rsv_key) {
				lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
				return -1;
			}
			if (strcasecmp(spec->rsv_port, conn->initiator_port) != 0) {
				lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
				return -1;
			}
			if (g_trace_flag) {
				ISTGT_WARNLOG("LU%d: duplicate reserve\n", spec->lu->num);
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			return 0;
		}

		if (scope != 0x00) { // !LU_SCOPE
			/* INVALID FIELD IN CDB */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		if (type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE
		    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS
		    && type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY
		    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY
		    && type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS
		    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS) {
			ISTGT_ERRLOG("unsupported type 0x%x\n", type);
			/* INVALID FIELD IN CDB */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}

		/* establish reservation by key */
		xfree(spec->rsv_port);
		spec->rsv_port = xstrdup(conn->initiator_port);
		strlwr(spec->rsv_port);
		spec->rsv_key = rkey;
		spec->rsv_scope = scope;
		spec->rsv_type = type;

		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
		    "LU%d: reserved (scope=%d, type=%d) by key=0x%16.16"
		    PRIx64"\n",
		    spec->lu->num, scope, type, rkey);
		break;

	case 0x02: /* RELEASE */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "RELEASE\n");

		prkey = istgt_lu_disk_find_pr_key(spec, conn->initiator_port,
		    conn->target_port, 0);
		if (prkey == NULL) {
			/* unregistered port */
			lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
			return -1;
		}

		prkey = istgt_lu_disk_find_pr_key(spec, conn->initiator_port,
		    conn->target_port, rkey);
		if (prkey == NULL) {
			/* not found key */
			lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
			return -1;
		}
		if (spec->rsv_key == 0) {
			/* no reservation */
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			return 0;
		}
		if (prkey->key != spec->rsv_key) {
			/* INVALID RELEASE OF PERSISTENT RESERVATION */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x26, 0x04);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		if (strcasecmp(spec->rsv_port, conn->initiator_port) != 0) {
			/* INVALID RELEASE OF PERSISTENT RESERVATION */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x26, 0x04);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}

		if (scope != 0x00) { // !LU_SCOPE
			/* INVALID FIELD IN CDB */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		if (type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE
		    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS
		    && type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY
		    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY
		    && type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS
		    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS) {
			ISTGT_ERRLOG("unsupported type 0x%x\n", type);
			/* INVALID FIELD IN CDB */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		if (spec->rsv_scope != scope || spec->rsv_type != type) {
			/* INVALID RELEASE OF PERSISTENT RESERVATION */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x26, 0x04);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}

		/* release reservation by key */
		xfree(spec->rsv_port);
		spec->rsv_port = NULL;
		spec->rsv_key = 0;
		spec->rsv_scope = 0;
		spec->rsv_type = 0;

		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
		    "LU%d: released (scope=%d, type=%d) by key=0x%16.16"
		    PRIx64"\n",
		    spec->lu->num, scope, type, rkey);
		break;

	case 0x03: /* CLEAR */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "CLEAR\n");

		prkey = istgt_lu_disk_find_pr_key(spec, conn->initiator_port,
		    conn->target_port, 0);
		if (prkey == NULL) {
			/* unregistered port */
			lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
			return -1;
		}

		/* release reservation */
		xfree(spec->rsv_port);
		spec->rsv_port = NULL;
		spec->rsv_key = 0;
		spec->rsv_scope = 0;
		spec->rsv_type = 0;

		/* remove all registrations */
		for (i = 0; i < spec->npr_keys; i++) {
			prkey = &spec->pr_keys[i];
			istgt_lu_disk_free_pr_key(prkey);
		}
		spec->npr_keys = 0;
		break;

	case 0x04: /* PREEMPT */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "PREEMPT\n");

		task_abort = 0;
	do_preempt:
		prkey = istgt_lu_disk_find_pr_key(spec, conn->initiator_port,
		    conn->target_port, 0);
		if (prkey == NULL) {
			/* unregistered port */
			lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
			return -1;
		}

		if (spec->rsv_key == 0) {
			/* no reservation */
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "no reservation\n");
			/* remove registration */
			rc = istgt_lu_disk_remove_pr_key(spec, conn,
			    NULL, NULL, sarkey);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_remove_pr_key() failed\n");
				/* INTERNAL TARGET FAILURE */
				BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			/* update generation */
			spec->pr_generation++;

			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "rsv_key=0x%16.16"PRIx64"\n",
		    spec->rsv_key);

		if (spec->rsv_type == ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS
		    || spec->rsv_type == ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS) {
			if (sarkey != 0) {
				/* remove registration */
				rc = istgt_lu_disk_remove_pr_key(spec, conn,
				    NULL, NULL, sarkey);
				if (rc < 0) {
					ISTGT_ERRLOG("lu_disk_remove_pr_key() failed\n");
					/* INTERNAL TARGET FAILURE */
					BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
					lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
					return -1;
				}

				/* update generation */
				spec->pr_generation++;

				lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
				break;
			} else {
				/* remove other registrations */
				rc = istgt_lu_disk_remove_other_pr_key(spec, conn,
				    conn->initiator_port,
				    conn->target_port,
				    rkey);
				if (rc < 0) {
					ISTGT_ERRLOG("lu_disk_remove_other_pr_key() failed\n");
					/* INTERNAL TARGET FAILURE */
					BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
					lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
					return -1;
				}

				if (scope != 0x00) { // !LU_SCOPE
					/* INVALID FIELD IN CDB */
					BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
					lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
					return -1;
				}
				if (type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE
				    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS
				    && type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY
				    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY
				    && type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS
				    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS) {
					ISTGT_ERRLOG("unsupported type 0x%x\n", type);
					/* INVALID FIELD IN CDB */
					BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
					lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
					return -1;
				}

				/* release reservation */
				//xfree(spec->rsv_port);
				old_rsv_port = spec->rsv_port;
				spec->rsv_port = NULL;
				spec->rsv_key = 0;
				spec->rsv_scope = 0;
				spec->rsv_type = 0;
				/* establish new reservation */
				spec->rsv_port = xstrdup(conn->initiator_port);
				strlwr(spec->rsv_port);
				spec->rsv_key = rkey;
				spec->rsv_scope = scope;
				spec->rsv_type = type;

				ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
				    "LU%d: reserved (scope=%d, type=%d)"
				    "by key=0x%16.16"PRIx64"\n",
				    spec->lu->num, scope, type, rkey);

				/* update generation */
				spec->pr_generation++;

				/* XXX TODO fix */
				if (task_abort) {
					/* abort all tasks for preempted I_T nexus */
					if (old_rsv_port != NULL) {
						rc = istgt_lu_disk_queue_abort_ITL(spec, old_rsv_port);
						xfree(old_rsv_port);
						old_rsv_port = NULL;
						if (rc < 0) {
							/* INTERNAL TARGET FAILURE */
							BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
							lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
							return -1;
						}
					}
				}
				if (old_rsv_port != NULL) {
					xfree(old_rsv_port);
					old_rsv_port = NULL;
				}

				lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
				break;
			}
		}

		prkey = istgt_lu_disk_find_pr_key(spec, conn->initiator_port,
		    conn->target_port, rkey);

		if (prkey == NULL) {
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "prkey == NULL\n");
		} else {
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "prkey key=%16.16"PRIx64"\n",
			    prkey->key);
		}

		if (prkey == NULL
		    || sarkey != spec->rsv_key) {
			if (sarkey != 0) {
				/* remove registration */
				rc = istgt_lu_disk_remove_pr_key(spec, conn,
				    NULL, NULL, sarkey);
				if (rc < 0) {
					ISTGT_ERRLOG("lu_disk_remove_pr_key() failed\n");
					/* INTERNAL TARGET FAILURE */
					BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
					lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
					return -1;
				}
				lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
				break;
			} else {
				/* INVALID FIELD IN PARAMETER LIST */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x26, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
		}

		/* remove registration */
		rc = istgt_lu_disk_remove_pr_key(spec, conn,
		    NULL, NULL, sarkey);
		if (rc < 0) {
			ISTGT_ERRLOG("lu_disk_remove_pr_key() failed\n");
			/* INTERNAL TARGET FAILURE */
			BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}

		if (scope != 0x00) { // !LU_SCOPE
			/* INVALID FIELD IN CDB */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		if (type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE
		    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS
		    && type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY
		    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY
		    && type != ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS
		    && type != ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS) {
			ISTGT_ERRLOG("unsupported type 0x%x\n", type);
			/* INVALID FIELD IN CDB */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}

		/* release reservation */
		//xfree(spec->rsv_port);
		old_rsv_port = spec->rsv_port;
		spec->rsv_port = NULL;
		spec->rsv_key = 0;
		spec->rsv_scope = 0;
		spec->rsv_type = 0;
		/* establish new reservation */
		spec->rsv_port = xstrdup(conn->initiator_port);
		strlwr(spec->rsv_port);
		spec->rsv_key = rkey;
		spec->rsv_scope = scope;
		spec->rsv_type = type;

		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
		    "LU%d: reserved (scope=%d, type=%d) by key=0x%16.16"
		    PRIx64"\n",
		    spec->lu->num, scope, type, rkey);

		/* update generation */
		spec->pr_generation++;

		/* XXX TODO fix */
		if (task_abort) {
			/* abort all tasks for preempted I_T nexus */
			if (old_rsv_port != NULL) {
				rc = istgt_lu_disk_queue_abort_ITL(spec, old_rsv_port);
				xfree(old_rsv_port);
				old_rsv_port = NULL;
				if (rc < 0) {
					/* INTERNAL TARGET FAILURE */
					BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
					lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
					return -1;
				}
			}
		}
		if (old_rsv_port != NULL) {
			xfree(old_rsv_port);
			old_rsv_port = NULL;
		}

		lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		break;

	case 0x05: /* PREEMPT AND ABORT */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "PREEMPT AND ABORT\n");

		task_abort = 1;
		goto do_preempt;

	case 0x06: /* REGISTER AND IGNORE EXISTING KEY */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "REGISTER AND IGNORE EXISTING KEY\n");

		if (aptpl != 0) {
			/* Activate Persist Through Power Loss */
			ISTGT_ERRLOG("unsupport Activate Persist Through Power Loss\n");
			/* INVALID FIELD IN PARAMETER LIST */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x26, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		/* lost reservations if daemon restart */

		prkey = istgt_lu_disk_find_pr_key(spec, conn->initiator_port,
		    conn->target_port, 0);
		if (prkey == NULL) {
			/* unregistered port */
			if (sarkey != 0) {
				if (spec_i_pt) {
					/* INVALID FIELD IN CDB */
					BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
					lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
					return -1;
				}
			}
			/* unregister? */
			if (sarkey == 0) {
				lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
				return 0;
			}
		} else {
			/* registered port */
			if (spec_i_pt) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
		}

		/* remove existing keys */
		rc = istgt_lu_disk_remove_pr_key(spec, conn,
		    conn->initiator_port,
		    conn->target_port, 0);
		if (rc < 0) {
			ISTGT_ERRLOG("lu_disk_remove_pr_key() failed\n");
			/* INTERNAL TARGET FAILURE */
			BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}

		/* unregister? */
		if (sarkey == 0) {
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			return 0;
		}

	do_register:
		/* specified port? */
		nports = 0;
		initiator_ports = NULL;
		if (spec_i_pt) {
			if (len < 28) {
				/* INVALID FIELD IN PARAMETER LIST */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x26, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			/* TRANSPORTID PARAMETER DATA LENGTH */
			plen = DGET32(&data[24]);
			if (28 + plen > len) {
				ISTGT_ERRLOG("invalid length %d (expect %d)\n",
				    len, 28 + plen);
				/* INVALID FIELD IN PARAMETER LIST */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x26, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "TransportID parameter data length %d\n",
			    plen);
			if (plen != 0) {
				maxports = MAX_LU_RESERVE_IPT;
				initiator_ports = xmalloc(sizeof (char *) * maxports);
				memset(initiator_ports, 0, sizeof (char *) * maxports);
				nports = 0;
				total = 0;
				while (total < plen) {
					if (nports >= MAX_LU_RESERVE_IPT) {
						ISTGT_ERRLOG("maximum transport IDs\n");
						/* INSUFFICIENT REGISTRATION RESOURCES */
						BUILD_SENSE(ILLEGAL_REQUEST, 0x55, 0x04);
						lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
						return -1;
					}
					rc = istgt_lu_parse_transport_id
						(&initiator_ports[nports],
						 &data[24] + total, plen - total);
					if (rc < 0) {
						/* INVALID FIELD IN PARAMETER LIST */
						BUILD_SENSE(ILLEGAL_REQUEST, 0x26, 0x00);
						lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
						return -1;
					}
					ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "got TransportID %s\n",
					    initiator_ports[nports]);
					total += rc;
					nports++;
				}
			}
			/* check all port unregistered? */
			for (i = 0; i < nports; i++) {
				prkey = istgt_lu_disk_find_pr_key(spec,
				    initiator_ports[i], NULL, 0);
				if (prkey != NULL) {
					/* registered port */
					/* INVALID FIELD IN CDB */
					BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
					lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
					return -1;
				}
			}
			/* OK, all port unregistered */
			idx = spec->npr_keys;
			if (idx + nports >= MAX_LU_RESERVE) {
				/* INSUFFICIENT REGISTRATION RESOURCES */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x55, 0x04);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			/* register each I_T nexus */
			for (i = 0; i < nports; i++) {
				prkey = &spec->pr_keys[idx + i];

				/* register new key */
				prkey->key = sarkey;

				/* command received port */
				prkey->registered_initiator_port
					= xstrdup(conn->initiator_port);
				strlwr(prkey->registered_initiator_port);
				prkey->registered_target_port
					= xstrdup(conn->target_port);
				strlwr(prkey->registered_target_port);
				prkey->pg_idx = conn->portal.idx;
				prkey->pg_tag = conn->portal.tag;

				/* specified ports */
				prkey->ninitiator_ports = 0;
				prkey->initiator_ports = NULL;
				prkey->all_tpg = (all_tg_pt) ? 1 : 0;
			}
			spec->npr_keys = idx + nports;
		}

		idx = spec->npr_keys;
		if (idx >= MAX_LU_RESERVE) {
			/* INSUFFICIENT REGISTRATION RESOURCES */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x55, 0x04);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		prkey = &spec->pr_keys[idx];

		/* register new key */
		prkey->key = sarkey;

		/* command received port */
		prkey->registered_initiator_port = xstrdup(conn->initiator_port);
		strlwr(prkey->registered_initiator_port);
		prkey->registered_target_port = xstrdup(conn->target_port);
		strlwr(prkey->registered_target_port);
		prkey->pg_idx = conn->portal.idx;
		prkey->pg_tag = conn->portal.tag;

		/* specified ports */
		prkey->ninitiator_ports = nports;
		prkey->initiator_ports = initiator_ports;
		prkey->all_tpg = (all_tg_pt) ? 1 : 0;

		/* count up keys */
		idx++;
		spec->npr_keys = idx;

		/* update generation */
		spec->pr_generation++;
		break;

	case 0x07: /* REGISTER AND MOVE */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "REGISTER AND MOVE\n");
		/* INVALID FIELD IN CDB */
		BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return -1;

	default:
		ISTGT_ERRLOG("unsupported service action 0x%x\n", sa);
		/* INVALID FIELD IN CDB */
		BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return -1;
	}

	lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
	return 0;
}

static int
istgt_lu_disk_check_pr(ISTGT_LU_DISK *spec, CONN_Ptr conn, int pr_allow)
{
	ISTGT_LU_PR_KEY *prkey;

#ifdef ISTGT_TRACE_DISK
	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
	    "RSV_KEY=0x%16.16"PRIx64", RSV_TYPE=0x%x, PR_ALLOW=0x%x\n",
	    spec->rsv_key, spec->rsv_type, pr_allow);
#endif /* ISTGT_TRACE_DISK */

	prkey = istgt_lu_disk_find_pr_key(spec, conn->initiator_port,
	    conn->target_port, 0);
	if (prkey != NULL) {
#ifdef ISTGT_TRACE_DISK
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
		    "PRKEY(0x%16.16"PRIx64") found for %s\n",
		    prkey->key, conn->initiator_port);
#endif /* ISTGT_TRACE_DISK */

		if (spec->rsv_key == prkey->key) {
			/* reservation holder */
			return 0;
		}

		switch (spec->rsv_type) {
		case ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS:
			if (pr_allow & PR_ALLOW_ALLRR)
				return 0;
			return -1;
		case ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS:
			if (pr_allow & PR_ALLOW_ALLRR)
				return 0;
			return -1;
		case ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY:
			if (pr_allow & PR_ALLOW_ALLRR)
				return 0;
			return -1;
		case ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY:
			if (pr_allow & PR_ALLOW_ALLRR)
				return 0;
			return -1;
		}
	} else {
#ifdef ISTGT_TRACE_DISK
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
		    "PRKEY not found for %s\n",
		    conn->initiator_port);
#endif /* ISTGT_TRACE_DISK */

		switch (spec->rsv_type) {
		case ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_ALL_REGISTRANTS:
			if (pr_allow & PR_ALLOW_WERR)
				return 0;
			return -1;
		case ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE_REGISTRANTS_ONLY:
			if (pr_allow & PR_ALLOW_WERR)
				return 0;
			return -1;
		case ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_ALL_REGISTRANTS:
			if (pr_allow & PR_ALLOW_EARR)
				return 0;
			return -1;
		case ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS_REGISTRANTS_ONLY:
			if (pr_allow & PR_ALLOW_EARR)
				return 0;
			return -1;
		}
	}

#ifdef ISTGT_TRACE_DISK
	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "non registrans type\n");
#endif /* ISTGT_TRACE_DISK */
	/* any I_T nexus */
	switch (spec->rsv_type) {
	case ISTGT_LU_PR_TYPE_WRITE_EXCLUSIVE:
		if (pr_allow & PR_ALLOW_WE)
			return 0;
		return -1;
	case ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS:
		if (pr_allow & PR_ALLOW_EA)
			return 0;
		return -1;
	}

	/* NG */
	return -1;
}

static int
istgt_lu_disk_scsi_release(ISTGT_LU_DISK *spec, CONN_Ptr conn, ISTGT_LU_CMD_Ptr lu_cmd)
{
	ISTGT_LU_CMD lu_cmd2;
	uint8_t *sense_data;
	size_t *sense_len;
	uint64_t LUI;
	uint64_t rkey;
	uint8_t cdb[10];
	uint8_t PRO_data[24];
	int parameter_len;
	int rc;

	sense_data = lu_cmd->sense_data;
	sense_len = &lu_cmd->sense_data_len;
	*sense_len = 0;

	memset(&lu_cmd2, 0, sizeof lu_cmd2);
	lu_cmd2.sense_data = lu_cmd->sense_data;
	lu_cmd2.sense_data_len = lu_cmd->sense_data_len;
	memset(&cdb, 0, sizeof cdb);
	parameter_len = sizeof PRO_data;

	LUI = istgt_get_lui(spec->lu->name, spec->lun & 0xffffU);
	rkey = istgt_get_rkey(conn->initiator_name, LUI);

	/* issue release action of PERSISTENT RESERVE OUT */
	cdb[0] = SPC_PERSISTENT_RESERVE_OUT;
	BDSET8W(&cdb[1], 0x02, 4, 5); /* RELEASE */
	BDSET8W(&cdb[2], 0x00, 7, 4); /* LU_SCOPE */
	BDADD8W(&cdb[2], 0x03, 3, 4); /* ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS */
	cdb[3] = 0;
	cdb[4] = 0;
	DSET32(&cdb[5], parameter_len);
	cdb[9] = 0;
	lu_cmd2.cdb = &cdb[0];

	memset(&PRO_data, 0, sizeof PRO_data);
	DSET64(&PRO_data[0], rkey); // RESERVATION KEY
	DSET64(&PRO_data[8], 0);

	rc = istgt_lu_disk_scsi_persistent_reserve_out(spec, conn, &lu_cmd2,
	    0x02, 0x00, 0x03,
	    PRO_data, parameter_len);
	if (rc < 0) {
		lu_cmd->status = lu_cmd2.status;
		if (lu_cmd->status == ISTGT_SCSI_STATUS_RESERVATION_CONFLICT) {
			return -1;
		}
		/* INTERNAL TARGET FAILURE */
		BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return -1;
	}

	/* issue unregister action of PERSISTENT RESERVE OUT */
	cdb[0] = SPC_PERSISTENT_RESERVE_OUT;
	BDSET8W(&cdb[1], 0x06, 4, 5); /* REGISTER AND IGNORE EXISTING KEY */
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 0;
	DSET32(&cdb[5], parameter_len);
	cdb[9] = 0;
	lu_cmd2.cdb = &cdb[0];

	memset(&PRO_data, 0, sizeof PRO_data);
	DSET64(&PRO_data[0], rkey); // RESERVATION KEY
	DSET64(&PRO_data[8], 0); // unregister

	rc = istgt_lu_disk_scsi_persistent_reserve_out(spec, conn, &lu_cmd2,
	    0x06, 0, 0,
	    PRO_data, parameter_len);
	if (rc < 0) {
		lu_cmd->status = lu_cmd2.status;
		if (lu_cmd->status == ISTGT_SCSI_STATUS_RESERVATION_CONFLICT) {
			return -1;
		}
		/* INTERNAL TARGET FAILURE */
		BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return -1;
	}

	lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
	return 0;
}

static int
istgt_lu_disk_scsi_reserve(ISTGT_LU_DISK *spec, CONN_Ptr conn, ISTGT_LU_CMD_Ptr lu_cmd)
{
	ISTGT_LU_CMD lu_cmd2;
	uint8_t *sense_data;
	size_t *sense_len;
	uint64_t LUI;
	uint64_t rkey;
	uint8_t cdb[10];
	uint8_t PRO_data[24];
	int parameter_len;
	int rc;

	sense_data = lu_cmd->sense_data;
	sense_len = &lu_cmd->sense_data_len;
	*sense_len = 0;

	memset(&lu_cmd2, 0, sizeof lu_cmd2);
	lu_cmd2.sense_data = lu_cmd->sense_data;
	lu_cmd2.sense_data_len = lu_cmd->sense_data_len;
	memset(&cdb, 0, sizeof cdb);
	parameter_len = sizeof PRO_data;

	LUI = istgt_get_lui(spec->lu->name, spec->lun & 0xffffU);
	rkey = istgt_get_rkey(conn->initiator_name, LUI);

	/* issue register action of PERSISTENT RESERVE OUT */
	cdb[0] = SPC_PERSISTENT_RESERVE_OUT;
	BDSET8W(&cdb[1], 0x06, 4, 5); /* REGISTER AND IGNORE EXISTING KEY */
	cdb[2] = 0;
	cdb[3] = 0;
	cdb[4] = 0;
	DSET32(&cdb[5], parameter_len);
	cdb[9] = 0;
	lu_cmd2.cdb = &cdb[0];

	memset(&PRO_data, 0, sizeof PRO_data);
	DSET64(&PRO_data[0], 0);
	DSET64(&PRO_data[8], rkey); // SERVICE ACTION RESERVATION KEY

	rc = istgt_lu_disk_scsi_persistent_reserve_out(spec, conn, &lu_cmd2,
	    0x06, 0, 0,
	    PRO_data, parameter_len);
	if (rc < 0) {
		lu_cmd->status = lu_cmd2.status;
		if (lu_cmd->status == ISTGT_SCSI_STATUS_RESERVATION_CONFLICT) {
			return -1;
		}
		/* INTERNAL TARGET FAILURE */
		BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return -1;
	}

	/* issue reserve action of PERSISTENT RESERVE OUT */
	cdb[0] = SPC_PERSISTENT_RESERVE_OUT;
	BDSET8W(&cdb[1], 0x01, 4, 5); /* RESERVE */
	BDSET8W(&cdb[2], 0x00, 7, 4); /* LU_SCOPE */
	BDADD8W(&cdb[2], 0x03, 3, 4); /* ISTGT_LU_PR_TYPE_EXCLUSIVE_ACCESS */
	cdb[3] = 0;
	cdb[4] = 0;
	DSET32(&cdb[5], parameter_len);
	cdb[9] = 0;
	lu_cmd2.cdb = &cdb[0];

	memset(&PRO_data, 0, sizeof PRO_data);
	DSET64(&PRO_data[0], rkey); // RESERVATION KEY
	DSET64(&PRO_data[8], 0);

	rc = istgt_lu_disk_scsi_persistent_reserve_out(spec, conn, &lu_cmd2,
	    0x01, 0x00, 0x03,
	    PRO_data, parameter_len);
	if (rc < 0) {
		lu_cmd->status = lu_cmd2.status;
		if (lu_cmd->status == ISTGT_SCSI_STATUS_RESERVATION_CONFLICT) {
			return -1;
		}
		/* INTERNAL TARGET FAILURE */
		BUILD_SENSE(HARDWARE_ERROR, 0x44, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return -1;
	}

	lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
	return 0;
}

static int
istgt_lu_disk_lbread(ISTGT_LU_DISK *spec, CONN_Ptr conn __attribute__((__unused__)), ISTGT_LU_CMD_Ptr lu_cmd, uint64_t lba, uint32_t len)
{
	uint8_t *data;
	uint64_t maxlba;
	uint64_t llen;
	uint64_t blen;
	uint64_t offset;
	uint64_t nbytes;
	int64_t rc;

	if (len == 0) {
		lu_cmd->data = NULL;
		lu_cmd->data_len = 0;
		return 0;
	}

	maxlba = spec->blockcnt;
	llen = (uint64_t) len;
	blen = spec->blocklen;
	offset = lba * blen;
	nbytes = llen * blen;

	ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
	    "Read: max=%"PRIu64", lba=%"PRIu64", len=%u\n",
	    maxlba, lba, len);

	if (lba >= maxlba || llen > maxlba || lba > (maxlba - llen)) {
		ISTGT_ERRLOG("end of media\n");
		return -1;
	}

	if (nbytes > lu_cmd->iobufsize) {
		ISTGT_ERRLOG("nbytes(%zu) > iobufsize(%zu)\n",
		    (size_t) nbytes, lu_cmd->iobufsize);
		return -1;
	}
	data = lu_cmd->iobuf;

	rc = spec->seek(spec, offset);
	if (rc < 0) {
		ISTGT_ERRLOG("lu_disk_seek() failed\n");
		return -1;
	}

	rc = spec->read(spec, data, nbytes);
	if (rc < 0) {
		ISTGT_ERRLOG("lu_disk_read() failed\n");
		return -1;
	}
	ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "Read %"PRId64"/%"PRIu64" bytes\n",
	    rc, nbytes);

	lu_cmd->data = data;
	lu_cmd->data_len = rc;

	return 0;
}

static int
istgt_lu_disk_lbwrite(ISTGT_LU_DISK *spec, CONN_Ptr conn, ISTGT_LU_CMD_Ptr lu_cmd, uint64_t lba, uint32_t len)
{
	uint8_t *data;
	uint64_t maxlba;
	uint64_t llen;
	uint64_t blen;
	uint64_t offset;
	uint64_t nbytes;
	int64_t rc;

	if (len == 0) {
		lu_cmd->data_len = 0;
		return 0;
	}

	maxlba = spec->blockcnt;
	llen = (uint64_t) len;
	blen = spec->blocklen;
	offset = lba * blen;
	nbytes = llen * blen;

	ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
	    "Write: max=%"PRIu64", lba=%"PRIu64", len=%u\n",
	    maxlba, lba, len);

	if (lba >= maxlba || llen > maxlba || lba > (maxlba - llen)) {
		ISTGT_ERRLOG("end of media\n");
		return -1;
	}

	if (nbytes > lu_cmd->iobufsize) {
		ISTGT_ERRLOG("nbytes(%zu) > iobufsize(%zu)\n",
		    (size_t) nbytes, lu_cmd->iobufsize);
		return -1;
	}
	data = lu_cmd->iobuf;

	rc = istgt_lu_disk_transfer_data(conn, lu_cmd, lu_cmd->iobuf,
	    lu_cmd->iobufsize, nbytes);
	if (rc < 0) {
		ISTGT_ERRLOG("lu_disk_transfer_data() failed\n");
		return -1;
	}

	if (spec->lu->readonly) {
		ISTGT_ERRLOG("LU%d: readonly unit\n", spec->lu->num);
		return -1;
	}

	spec->req_write_cache = 0;
	rc = spec->seek(spec, offset);
	if (rc < 0) {
		ISTGT_ERRLOG("lu_disk_seek() failed\n");
		return -1;
	}

	rc = spec->write(spec, data, nbytes);
	if (rc < 0 || (uint64_t) rc != nbytes) {
		ISTGT_ERRLOG("lu_disk_write() failed\n");
		return -1;
	}
	ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "Wrote %"PRId64"/%"PRIu64" bytes\n",
	    rc, nbytes);

	lu_cmd->data_len = rc;

	return 0;
}

static int
istgt_lu_disk_lbwrite_same(ISTGT_LU_DISK *spec, CONN_Ptr conn, ISTGT_LU_CMD_Ptr lu_cmd, uint64_t lba, uint32_t len)
{
	uint8_t *data;
	uint64_t maxlba;
	uint64_t llen;
	uint64_t blen;
	uint64_t offset;
	uint64_t nbytes;
	uint64_t nblocks;
	uint64_t wblocks;
	int64_t rc;

	maxlba = spec->blockcnt;
	llen = (uint64_t) len;
	if (llen == 0) {
		if (lba >= maxlba) {
			ISTGT_ERRLOG("end of media\n");
			return -1;
		}
		llen = maxlba - lba;
	}
	blen = spec->blocklen;
	offset = lba * blen;
	nbytes = 1 * blen;

	ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
	    "Write Same: max=%"PRIu64", lba=%"PRIu64", len=%u\n",
	    maxlba, lba, len);

	if (lba >= maxlba || llen > maxlba || lba > (maxlba - llen)) {
		ISTGT_ERRLOG("end of media\n");
		return -1;
	}

	if (nbytes > lu_cmd->iobufsize) {
		ISTGT_ERRLOG("nbytes(%zu) > iobufsize(%zu)\n",
		    (size_t) nbytes, lu_cmd->iobufsize);
		return -1;
	}
	data = lu_cmd->iobuf;

	rc = istgt_lu_disk_transfer_data(conn, lu_cmd, lu_cmd->iobuf,
	    lu_cmd->iobufsize, nbytes);
	if (rc < 0) {
		ISTGT_ERRLOG("lu_disk_transfer_data() failed\n");
		return -1;
	}

	if (spec->lu->readonly) {
		ISTGT_ERRLOG("LU%d: readonly unit\n", spec->lu->num);
		return -1;
	}

	if (conn->workbuf == NULL) {
		conn->worksize = ISTGT_LU_WORK_BLOCK_SIZE;
		conn->workbuf = xmalloc(conn->worksize);
	}
	wblocks = (int64_t)conn->worksize / nbytes;
	if (wblocks == 0) {
		ISTGT_ERRLOG("work buffer is too small\n");
		return -1;
	}

	spec->req_write_cache = 0;
	rc = spec->seek(spec, offset);
	if (rc < 0) {
		ISTGT_ERRLOG("lu_disk_seek() failed\n");
		return -1;
	}

#if 0
	nblocks = 0;
	while (nblocks < llen) {
		rc = spec->write(spec, data, nbytes);
		if (rc < 0 || rc != nbytes) {
			ISTGT_ERRLOG("lu_disk_write() failed\n");
			return -1;
		}
		nblocks++;
	}
#else
	nblocks = 0;
	while (nblocks < wblocks) {
		memcpy(conn->workbuf + (nblocks * nbytes), data, nbytes);
		nblocks++;
	}

	nblocks = 0;
	while (nblocks < llen) {
		uint64_t reqblocks = DMIN64(wblocks, (llen - nblocks));
		rc = spec->write(spec, conn->workbuf, (reqblocks * nbytes));
		if (rc < 0 || (uint64_t) rc != (reqblocks * nbytes)) {
			ISTGT_ERRLOG("lu_disk_write() failed\n");
			return -1;
		}
		nblocks += reqblocks;
	}
#endif
	ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "Wrote %"PRId64"/%"PRIu64" bytes\n",
	    (nblocks * nbytes), (llen * nbytes));

	lu_cmd->data_len = nbytes;

	return 0;
}

static int
istgt_lu_disk_lbwrite_ats(ISTGT_LU_DISK *spec, CONN_Ptr conn, ISTGT_LU_CMD_Ptr lu_cmd, uint64_t lba, uint32_t len)
{
	uint8_t *data;
	uint64_t maxlba;
	uint64_t llen;
	uint64_t blen;
	uint64_t offset;
	uint64_t nbytes;
	int64_t rc;
	uint8_t *sense_data;
	size_t *sense_len;

	if (len == 0) {
		lu_cmd->data_len = 0;
		return 0;
	}

	sense_data = lu_cmd->sense_data;
	sense_len = &lu_cmd->sense_data_len;
	*sense_len = 0;

	maxlba = spec->blockcnt;
	llen = (uint64_t) len;
	blen = spec->blocklen;
	offset = lba * blen;
	nbytes = llen * blen;

	ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
	    "Write ATS: max=%"PRIu64", lba=%"PRIu64", len=%u\n",
	    maxlba, lba, len);

	if (lba >= maxlba || llen > maxlba || lba > (maxlba - llen)) {
		ISTGT_ERRLOG("end of media\n");
		return -1;
	}

	if (nbytes > lu_cmd->iobufsize) {
		ISTGT_ERRLOG("nbytes(%zu) > iobufsize(%zu)\n",
		    (size_t) nbytes, lu_cmd->iobufsize);
		return -1;
	}
	data = lu_cmd->iobuf;

	rc = istgt_lu_disk_transfer_data(conn, lu_cmd, lu_cmd->iobuf,
	    lu_cmd->iobufsize, nbytes * 2);
	if (rc < 0) {
		ISTGT_ERRLOG("lu_disk_transfer_data() failed\n");
		return -1;
	}

	if (spec->lu->readonly) {
		ISTGT_ERRLOG("LU%d: readonly unit\n", spec->lu->num);
		return -1;
	}

	if (spec->watsbuf == NULL) {
		spec->watssize = ISTGT_LU_WORK_ATS_BLOCK_SIZE;
		spec->watsbuf = xmalloc(spec->watssize);
	}
	if (nbytes > (uint64_t) spec->watssize) {
		ISTGT_ERRLOG("nbytes(%zu) > watssize(%zu)\n",
		    (size_t) nbytes, (size_t) spec->watssize);
		return -1;
	}

	spec->req_write_cache = 0;
	/* start atomic test and set */
	MTX_LOCK(&spec->ats_mutex);

	rc = spec->seek(spec, offset);
	if (rc < 0) {
		MTX_UNLOCK(&spec->ats_mutex);
		ISTGT_ERRLOG("lu_disk_seek() failed\n");
		return -1;
	}

	rc = spec->read(spec, spec->watsbuf, nbytes);
	if (rc < 0 || (uint64_t) rc != nbytes) {
		MTX_UNLOCK(&spec->ats_mutex);
		ISTGT_ERRLOG("lu_disk_read() failed\n");
		return -1;
	}

#if 0
	ISTGT_TRACEDUMP(ISTGT_TRACE_DEBUG, "ATS VERIFY", data, nbytes);
	ISTGT_TRACEDUMP(ISTGT_TRACE_DEBUG, "ATS WRITE", data + nbytes, nbytes);
	ISTGT_TRACEDUMP(ISTGT_TRACE_DEBUG, "ATS DATA", spec->watsbuf, nbytes);
#endif
	if (memcmp(spec->watsbuf, data, nbytes) != 0) {
		MTX_UNLOCK(&spec->ats_mutex);
		//ISTGT_ERRLOG("compare failed\n");
		/* MISCOMPARE DURING VERIFY OPERATION */
		BUILD_SENSE(MISCOMPARE, 0x1d, 0x00);
		return -1;
	}

	rc = spec->seek(spec, offset);
	if (rc < 0) {
		MTX_UNLOCK(&spec->ats_mutex);
		ISTGT_ERRLOG("lu_disk_seek() failed\n");
		return -1;
	}
	rc = spec->write(spec, data + nbytes, nbytes);
	if (rc < 0 || (uint64_t) rc != nbytes) {
		MTX_UNLOCK(&spec->ats_mutex);
		ISTGT_ERRLOG("lu_disk_write() failed\n");
		return -1;
	}
	ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "Wrote %"PRId64"/%"PRIu64" bytes\n",
	    rc, nbytes);

	MTX_UNLOCK(&spec->ats_mutex);
	/* end atomic test and set */

	lu_cmd->data_len = nbytes * 2;

	return 0;
}

static int
istgt_lu_disk_lbsync(ISTGT_LU_DISK *spec, CONN_Ptr conn __attribute__((__unused__)), ISTGT_LU_CMD_Ptr lu_cmd __attribute__((__unused__)), uint64_t lba, uint32_t len)
{
	uint64_t maxlba;
	uint64_t llen;
	uint64_t blen;
	uint64_t offset;
	uint64_t nbytes;
	int64_t rc;

	if (len == 0) {
		return 0;
	}

	maxlba = spec->blockcnt;
	llen = (uint64_t) len;
	blen = spec->blocklen;
	offset = lba * blen;
	nbytes = llen * blen;

	ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
	    "Sync: max=%"PRIu64", lba=%"PRIu64", len=%u\n",
	    maxlba, lba, len);

	if (lba >= maxlba || llen > maxlba || lba > (maxlba - llen)) {
		ISTGT_ERRLOG("end of media\n");
		return -1;
	}

	rc = spec->sync(spec, offset, nbytes);
	if (rc < 0) {
		ISTGT_ERRLOG("lu_disk_sync() failed\n");
		return -1;
	}

	return 0;
}

int
istgt_lu_scsi_build_sense_data(uint8_t *data, int sk, int asc, int ascq)
{
	uint8_t *cp;
	int resp_code;
	int hlen = 0, len = 0, plen;
	int total;

	resp_code = 0x70; /* Current + Fixed format */

	/* SenseLength */
	DSET16(&data[0], 0);
	hlen = 2;

	/* Sense Data */
	cp = &data[hlen + len];

	/* VALID(7) RESPONSE CODE(6-0) */
	BDSET8(&cp[0], 1, 7);
	BDADD8W(&cp[0], resp_code, 6, 7);
	/* Obsolete */
	cp[1] = 0;
	/* FILEMARK(7) EOM(6) ILI(5) SENSE KEY(3-0) */
	BDSET8W(&cp[2], sk, 3, 4);
	/* INFORMATION */
	memset(&cp[3], 0, 4);
	/* ADDITIONAL SENSE LENGTH */
	cp[7] = 0;
	len = 8;

	/* COMMAND-SPECIFIC INFORMATION */
	memset(&cp[8], 0, 4);
	/* ADDITIONAL SENSE CODE */
	cp[12] = asc;
	/* ADDITIONAL SENSE CODE QUALIFIER */
	cp[13] = ascq;
	/* FIELD REPLACEABLE UNIT CODE */
	cp[14] = 0;
	/* SKSV(7) SENSE KEY SPECIFIC(6-0,7-0,7-0) */
	cp[15] = 0;
	cp[16] = 0;
	cp[17] = 0;
	/* Additional sense bytes */
	//data[18] = 0;
	plen = 18 - len;

	/* ADDITIONAL SENSE LENGTH */
	cp[7] = plen;

	total = hlen + len + plen;

	/* SenseLength */
	DSET16(&data[0], total - 2);

	return total;
}

static int
istgt_lu_disk_build_sense_data(ISTGT_LU_DISK *spec __attribute__((__unused__)), uint8_t *data, int sk, int asc, int ascq)
{
	int rc;

	rc = istgt_lu_scsi_build_sense_data(data, sk, asc, ascq);
	if (rc < 0) {
		return -1;
	}
	return rc;
}

int
istgt_lu_scsi_build_sense_data2(uint8_t *data, int sk, int asc, int ascq)
{
	uint8_t *cp;
	int resp_code;
	int hlen = 0, len = 0, plen;
	int total;

	resp_code = 0x71; /* Deferred + Fixed format */

	/* SenseLength */
	DSET16(&data[0], 0);
	hlen = 2;

	/* Sense Data */
	cp = &data[hlen + len];

	/* VALID(7) RESPONSE CODE(6-0) */
	BDSET8(&cp[0], 1, 7);
	BDADD8W(&cp[0], resp_code, 6, 7);
	/* Obsolete */
	cp[1] = 0;
	/* FILEMARK(7) EOM(6) ILI(5) SENSE KEY(3-0) */
	BDSET8W(&cp[2], sk, 3, 4);
	/* INFORMATION */
	memset(&cp[3], 0, 4);
	/* ADDITIONAL SENSE LENGTH */
	cp[7] = 0;
	len = 8;

	/* COMMAND-SPECIFIC INFORMATION */
	memset(&cp[8], 0, 4);
	/* ADDITIONAL SENSE CODE */
	cp[12] = asc;
	/* ADDITIONAL SENSE CODE QUALIFIER */
	cp[13] = ascq;
	/* FIELD REPLACEABLE UNIT CODE */
	cp[14] = 0;
	/* SKSV(7) SENSE KEY SPECIFIC(6-0,7-0,7-0) */
	cp[15] = 0;
	cp[16] = 0;
	cp[17] = 0;
	/* Additional sense bytes */
	//data[18] = 0;
	plen = 18 - len;

	/* ADDITIONAL SENSE LENGTH */
	cp[7] = plen;

	total = hlen + len + plen;

	/* SenseLength */
	DSET16(&data[0], total - 2);

	return total;
}

static int
istgt_lu_disk_build_sense_data2(ISTGT_LU_DISK *spec __attribute__((__unused__)), uint8_t *data, int sk, int asc, int ascq)
{
	int rc;

	rc = istgt_lu_scsi_build_sense_data2(data, sk, asc, ascq);
	if (rc < 0) {
		return -1;
	}
	return rc;
}

int
istgt_lu_disk_reset(ISTGT_LU_Ptr lu, int lun)
{
	ISTGT_LU_DISK *spec;
	int flags;
	int rc;

	if (lu == NULL) {
		return -1;
	}
	if (lun >= lu->maxlun) {
		return -1;
	}
	if (lu->lun[lun].type == ISTGT_LU_LUN_TYPE_NONE) {
		return -1;
	}
	if (lu->lun[lun].type != ISTGT_LU_LUN_TYPE_STORAGE) {
		return -1;
	}
	spec = (ISTGT_LU_DISK *) lu->lun[lun].spec;

#if 0
	if (spec->lock) {
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "unlock by reset\n");
		spec->lock = 0;
	}
#endif

	if (lu->queue_depth != 0) {
		rc = istgt_lu_disk_queue_clear_all(lu, lun);
		if (rc < 0) {
			ISTGT_ERRLOG("lu_disk_queue_clear_all() failed\n");
			return -1;
		}
	}

	/* re-open file */
	if (!spec->lu->readonly) {
		rc = spec->sync(spec, 0, spec->size);
		if (rc < 0) {
			ISTGT_ERRLOG("LU%d: LUN%d: lu_disk_sync() failed\n",
			    lu->num, lun);
			/* ignore error */
		}
	}
	rc = spec->close(spec);
	if (rc < 0) {
		ISTGT_ERRLOG("LU%d: LUN%d: lu_disk_close() failed\n",
		    lu->num, lun);
		/* ignore error */
	}
	flags = lu->readonly ? O_RDONLY : O_RDWR;
	rc = spec->open(spec, flags, 0666);
	if (rc < 0) {
		ISTGT_ERRLOG("LU%d: LUN%d: lu_disk_open() failed\n",
		    lu->num, lun);
		return -1;
	}

	return 0;
}

static int
istgt_lu_disk_queue_clear_internal(ISTGT_LU_DISK *spec, const char *initiator_port, int all_cmds, uint32_t CmdSN)
{
	ISTGT_LU_TASK_Ptr lu_task;
	ISTGT_QUEUE saved_queue;
	time_t now;
	int rc;

	if (spec == NULL)
		return -1;

	if (all_cmds != 0) {
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "queue clear by port=%s\n",
		    initiator_port);
	} else {
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "queue clear by port=%s, CmdSN=%u\n",
		    initiator_port, CmdSN);
	}

	istgt_queue_init(&saved_queue);

	now = time(NULL);
	MTX_LOCK(&spec->cmd_queue_mutex);
	while (1) {
		lu_task = istgt_queue_dequeue(&spec->cmd_queue);
		if (lu_task == NULL)
			break;
		if (((all_cmds != 0) || (lu_task->lu_cmd.CmdSN == CmdSN))
		    && (strcasecmp(lu_task->initiator_port,
			    initiator_port) == 0)) {
			ISTGT_LOG("CmdSN(%u), OP=0x%x, ElapsedTime=%lu cleared\n",
			    lu_task->lu_cmd.CmdSN,
			    lu_task->lu_cmd.cdb[0],
			    (unsigned long) (now - lu_task->create_time));
			rc = istgt_lu_destroy_task(lu_task);
			if (rc < 0) {
				MTX_UNLOCK(&spec->cmd_queue_mutex);
				ISTGT_ERRLOG("lu_destory_task() failed\n");
				goto error_return;
			}
			continue;
		}
		rc = istgt_queue_enqueue(&saved_queue, lu_task);
		if (rc < 0) {
			MTX_UNLOCK(&spec->cmd_queue_mutex);
			ISTGT_ERRLOG("queue_enqueue() failed\n");
			goto error_return;
		}
	}
	while (1) {
		lu_task = istgt_queue_dequeue(&saved_queue);
		if (lu_task == NULL)
			break;
		rc = istgt_queue_enqueue(&spec->cmd_queue, lu_task);
		if (rc < 0) {
			MTX_UNLOCK(&spec->cmd_queue_mutex);
			ISTGT_ERRLOG("queue_enqueue() failed\n");
			goto error_return;
		}
	}
	MTX_UNLOCK(&spec->cmd_queue_mutex);

	/* check wait task */
	MTX_LOCK(&spec->wait_lu_task_mutex);
	lu_task = spec->wait_lu_task;
	if (lu_task != NULL) {
		if (((all_cmds != 0) || (lu_task->lu_cmd.CmdSN == CmdSN))
		    && (strcasecmp(lu_task->initiator_port,
			    initiator_port) == 0)) {
			/* conn had gone? */
			rc = pthread_mutex_trylock(&lu_task->trans_mutex);
			if (rc == 0) {
				ISTGT_LOG("CmdSN(%u), OP=0x%x, ElapsedTime=%lu aborted\n",
				    lu_task->lu_cmd.CmdSN,
				    lu_task->lu_cmd.cdb[0],
				    (unsigned long) (now - lu_task->create_time));
				/* force error */
				lu_task->error = 1;
				lu_task->abort = 1;
				rc = pthread_cond_broadcast(&lu_task->trans_cond);
				if (rc != 0) {
					/* ignore error */
				}
				MTX_UNLOCK(&lu_task->trans_mutex);
			}
		}
	}
	MTX_UNLOCK(&spec->wait_lu_task_mutex);

	rc = istgt_queue_count(&saved_queue);
	if (rc != 0) {
		ISTGT_ERRLOG("temporary queue is not empty\n");
		goto error_return;
	}

	istgt_queue_destroy(&saved_queue);
	return 0;

 error_return:
	istgt_queue_destroy(&saved_queue);
	return -1;
}

static int
istgt_lu_disk_queue_abort_ITL(ISTGT_LU_DISK *spec, const char *initiator_port)
{
	int rc;

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "queue abort by port=%s\n",
	    initiator_port);

	rc = istgt_lu_disk_queue_clear_internal(spec, initiator_port,
	    1, 0U); /* ALL, CmdSN=0 */
	return rc;
}

int
istgt_lu_disk_queue_clear_IT(CONN_Ptr conn, ISTGT_LU_Ptr lu)
{
	ISTGT_LU_DISK *spec;
	int rc;
	int i;

	if (lu == NULL)
		return -1;

	for (i = 0; i < lu->maxlun; i++) {
		if (lu->lun[i].type == ISTGT_LU_LUN_TYPE_NONE) {
#if 0
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "LU%d: LUN%d none\n",
						   lu->num, i);
#endif
			continue;
		}
		if (lu->lun[i].type != ISTGT_LU_LUN_TYPE_STORAGE) {
			ISTGT_ERRLOG("LU%d: unsupported type\n", lu->num);
			return -1;
		}
		spec = (ISTGT_LU_DISK *) lu->lun[i].spec;
		if (spec == NULL) {
			continue;
		}

		rc = istgt_lu_disk_queue_clear_ITL(conn, lu, i);
		if (rc < 0) {
			return -1;
		}
	}

	return 0;
}

int
istgt_lu_disk_queue_clear_ITL(CONN_Ptr conn, ISTGT_LU_Ptr lu, int lun)
{
	ISTGT_LU_DISK *spec;
	int rc;

	if (lu == NULL)
		return -1;
	if (lun >= lu->maxlun)
		return -1;

	spec = (ISTGT_LU_DISK *) lu->lun[lun].spec;
	if (spec == NULL)
		return -1;

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "queue clear by name=%s, port=%s\n",
	    conn->initiator_name, conn->initiator_port);

	rc = istgt_lu_disk_queue_clear_internal(spec, conn->initiator_port,
	    1, 0U); /* ALL, CmdSN=0 */
	return rc;
}

int
istgt_lu_disk_queue_clear_ITLQ(CONN_Ptr conn, ISTGT_LU_Ptr lu, int lun, uint32_t CmdSN)
{
	ISTGT_LU_DISK *spec;
	int rc;

	if (lu == NULL)
		return -1;
	if (lun >= lu->maxlun)
		return -1;

	spec = (ISTGT_LU_DISK *) lu->lun[lun].spec;
	if (spec == NULL)
		return -1;

	ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "queue clear by name=%s, port=%s\n",
	    conn->initiator_name, conn->initiator_port);

	rc = istgt_lu_disk_queue_clear_internal(spec, conn->initiator_port,
	    0, CmdSN);
	return rc;
}

int
istgt_lu_disk_queue_clear_all(ISTGT_LU_Ptr lu, int lun)
{
	ISTGT_LU_TASK_Ptr lu_task;
	ISTGT_LU_DISK *spec;
	time_t now;
	int rc;

	if (lu == NULL)
		return -1;
	if (lun >= lu->maxlun)
		return -1;

	if (lu->lun[lun].type == ISTGT_LU_LUN_TYPE_NONE) {
		return -1;
	}
	if (lu->lun[lun].type != ISTGT_LU_LUN_TYPE_STORAGE) {
		return -1;
	}
	spec = (ISTGT_LU_DISK *) lu->lun[lun].spec;
	if (spec == NULL)
		return -1;

	now = time(NULL);
	MTX_LOCK(&spec->cmd_queue_mutex);
	while (1) {
		lu_task = istgt_queue_dequeue(&spec->cmd_queue);
		if (lu_task == NULL)
			break;
		ISTGT_LOG("CmdSN(%u), OP=0x%x, ElapsedTime=%lu cleared\n",
		    lu_task->lu_cmd.CmdSN,
		    lu_task->lu_cmd.cdb[0],
		    (unsigned long) (now - lu_task->create_time));
		rc = istgt_lu_destroy_task(lu_task);
		if (rc < 0) {
			MTX_UNLOCK(&spec->cmd_queue_mutex);
			ISTGT_ERRLOG("lu_destory_task() failed\n");
			return -1;
		}
	}
	MTX_UNLOCK(&spec->cmd_queue_mutex);

	/* check wait task */
	MTX_LOCK(&spec->wait_lu_task_mutex);
	lu_task = spec->wait_lu_task;
	if (lu_task != NULL) {
		/* conn had gone? */
		rc = pthread_mutex_trylock(&lu_task->trans_mutex);
		if (rc == 0) {
			ISTGT_LOG("CmdSN(%u), OP=0x%x, ElapsedTime=%lu aborted\n",
			    lu_task->lu_cmd.CmdSN,
			    lu_task->lu_cmd.cdb[0],
			    (unsigned long) (now - lu_task->create_time));
			/* force error */
			lu_task->error = 1;
			lu_task->abort = 1;
			rc = pthread_cond_broadcast(&lu_task->trans_cond);
			if (rc != 0) {
				/* ignore error */
			}
			MTX_UNLOCK(&lu_task->trans_mutex);
		}
	}
	MTX_UNLOCK(&spec->wait_lu_task_mutex);

	MTX_LOCK(&spec->cmd_queue_mutex);
	rc = istgt_queue_count(&spec->cmd_queue);
	MTX_UNLOCK(&spec->cmd_queue_mutex);
	if (rc != 0) {
		ISTGT_ERRLOG("cmd queue is not empty\n");
		return -1;
	}

	return 0;
}

int
istgt_lu_disk_queue(CONN_Ptr conn, ISTGT_LU_CMD_Ptr lu_cmd)
{
	ISTGT_LU_TASK_Ptr lu_task;
	ISTGT_LU_Ptr lu;
	ISTGT_LU_DISK *spec;
	uint8_t *data;
	uint8_t *cdb;
	uint32_t allocation_len;
	int data_len;
	int data_alloc_len;
	uint8_t *sense_data;
	size_t *sense_len;
	int lun_i;
	int maxq;
	int qcnt;
	int rc;

	if (lu_cmd == NULL)
		return -1;
	lu = lu_cmd->lu;
	if (lu == NULL) {
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return -1;
	}
	spec = NULL;
	cdb = lu_cmd->cdb;
	data = lu_cmd->data;
	data_alloc_len = lu_cmd->alloc_len;
	sense_data = lu_cmd->sense_data;
	sense_len = &lu_cmd->sense_data_len;
	*sense_len = 0;

	lun_i = istgt_lu_islun2lun(lu_cmd->lun);
	if (lun_i >= lu->maxlun) {
#ifdef ISTGT_TRACE_DISK
		ISTGT_ERRLOG("LU%d: LUN%4.4"PRIx64" invalid\n",
		    lu->num, lun_i);
#endif /* ISTGT_TRACE_DISK */
		if (cdb[0] == SPC_INQUIRY) {
			allocation_len = DGET16(&cdb[3]);
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			memset(data, 0, allocation_len);
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], 0x03, 7, 3);
			BDADD8W(&data[0], 0x1f, 4, 5);
			data_len = 96;
			memset(&data[1], 0, data_len - 1);
			/* ADDITIONAL LENGTH */
			data[4] = data_len - 5;
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			return ISTGT_LU_TASK_RESULT_IMMEDIATE;
		} else {
			/* LOGICAL UNIT NOT SUPPORTED */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x25, 0x00);
			lu_cmd->data_len = 0;
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return ISTGT_LU_TASK_RESULT_IMMEDIATE;
		}
	}
	spec = (ISTGT_LU_DISK *) lu->lun[lun_i].spec;
	if (spec == NULL) {
		/* LOGICAL UNIT NOT SUPPORTED */
		BUILD_SENSE(ILLEGAL_REQUEST, 0x25, 0x00);
		lu_cmd->data_len = 0;
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return ISTGT_LU_TASK_RESULT_IMMEDIATE;
	}
	/* ready to enqueue, spec is valid for LUN access */

	/* allocate task and copy LU_CMD(PDU) */
	lu_task = xmalloc(sizeof *lu_task);
	memset(lu_task, 0, sizeof *lu_task);
	rc = istgt_lu_create_task(conn, lu_cmd, lu_task, lun_i);
	if (rc < 0) {
		ISTGT_ERRLOG("lu_create_task() failed\n");
		xfree(lu_task);
		return -1;
	}

	/* enqueue SCSI command */
	MTX_LOCK(&spec->cmd_queue_mutex);
	rc = istgt_queue_count(&spec->cmd_queue);
	maxq = spec->queue_depth * lu->istgt->MaxSessions;
	if (rc > maxq) {
		MTX_UNLOCK(&spec->cmd_queue_mutex);
		lu_cmd->data_len = 0;
		lu_cmd->status = ISTGT_SCSI_STATUS_TASK_SET_FULL;
		rc = istgt_lu_destroy_task(lu_task);
		if (rc < 0) {
			ISTGT_ERRLOG("lu_destroy_task() failed\n");
			return -1;
		}
		return ISTGT_LU_TASK_RESULT_QUEUE_FULL;
	}
	qcnt = rc;
	ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
	    "Queue(%d), CmdSN=%u, OP=0x%x, LUN=0x%16.16"PRIx64"\n",
	    qcnt, lu_cmd->CmdSN, lu_cmd->cdb[0], lu_cmd->lun);

	/* enqueue task to LUN */
	switch (lu_cmd->Attr_bit) {
	case 0x03: /* Head of Queue */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "insert Head of Queue\n");
		rc = istgt_queue_enqueue_first(&spec->cmd_queue, lu_task);
		break;
	case 0x00: /* Untagged */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "insert Untagged\n");
		rc = istgt_queue_enqueue(&spec->cmd_queue, lu_task);
		break;
	case 0x01: /* Simple */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "insert Simple\n");
		rc = istgt_queue_enqueue(&spec->cmd_queue, lu_task);
		break;
	case 0x02: /* Ordered */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "insert Ordered\n");
		rc = istgt_queue_enqueue(&spec->cmd_queue, lu_task);
		break;
	case 0x04: /* ACA */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "insert ACA\n");
		rc = istgt_queue_enqueue(&spec->cmd_queue, lu_task);
		break;
	default: /* Reserved */
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "insert Reserved Attribute\n");
		rc = istgt_queue_enqueue(&spec->cmd_queue, lu_task);
		break;
	}
	MTX_UNLOCK(&spec->cmd_queue_mutex);
	if (rc < 0) {
		ISTGT_ERRLOG("queue_enqueue() failed\n");
	error_return:
		rc = istgt_lu_destroy_task(lu_task);
		if (rc < 0) {
			ISTGT_ERRLOG("lu_destroy_task() failed\n");
			return -1;
		}
		return -1;
	}

	/* notify LUN thread */
	MTX_LOCK(&lu->queue_mutex);
	lu->queue_check = 1;
	rc = pthread_cond_broadcast(&lu->queue_cond);
	MTX_UNLOCK(&lu->queue_mutex);
	if (rc != 0) {
		ISTGT_ERRLOG("LU%d: cond_broadcast() failed\n", lu->num);
		goto error_return;
	}

	return ISTGT_LU_TASK_RESULT_QUEUE_OK;
}

int
istgt_lu_disk_queue_count(ISTGT_LU_Ptr lu, int *lun)
{
	ISTGT_LU_DISK *spec;
	int qcnt;
	int luns;
	int i;

	if (lun == NULL)
		return -1;

	i = *lun;
	if (i >= lu->maxlun) {
		*lun = 0;
		i = 0;
	}

	qcnt = 0;
	for (luns = lu->maxlun; luns >= 0 ; luns--) {
		if (lu->lun[i].type == ISTGT_LU_LUN_TYPE_NONE) {
			goto next_lun;
		}
		if (lu->lun[i].type != ISTGT_LU_LUN_TYPE_STORAGE) {
			ISTGT_ERRLOG("LU%d: unsupported type\n", lu->num);
			goto next_lun;
		}
		spec = (ISTGT_LU_DISK *) lu->lun[i].spec;
		if (spec == NULL) {
			goto next_lun;
		}

		MTX_LOCK(&spec->cmd_queue_mutex);
		qcnt = istgt_queue_count(&spec->cmd_queue);
		MTX_UNLOCK(&spec->cmd_queue_mutex);
		if (qcnt > 0) {
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "LU%d: LUN%d queue(%d)\n",
			    lu->num, i, qcnt);
			*lun = spec->lun;
			break;
		}

	next_lun:
		i++;
		if (i >= lu->maxlun) {
			i = 0;
		}
	}
	return qcnt;
}

int
istgt_lu_disk_queue_start(ISTGT_LU_Ptr lu, int lun)
{
	ISTGT_Ptr istgt;
	ISTGT_LU_DISK *spec;
	ISTGT_LU_TASK_Ptr lu_task;
	CONN_Ptr conn;
	ISTGT_LU_CMD_Ptr lu_cmd;
	struct timespec abstime;
	time_t start, now;
	uint8_t *iobuf;
	char tmp[1];
	int abort_task = 0;
	int rc;

	if (lun < 0 || lun >= lu->maxlun) {
		return -1;
	}

	ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "LU%d: LUN%d queue start\n",
	    lu->num, lun);
	spec = (ISTGT_LU_DISK *) lu->lun[lun].spec;
	if (spec == NULL)
		return -1;

	MTX_LOCK(&spec->cmd_queue_mutex);
	lu_task = istgt_queue_dequeue(&spec->cmd_queue);
	MTX_UNLOCK(&spec->cmd_queue_mutex);
	if (lu_task == NULL) {
		/* cleared or empty queue */
		return 0;
	}
	lu_task->thread = pthread_self();
	conn = lu_task->conn;
	istgt = conn->istgt;
	lu_cmd = &lu_task->lu_cmd;

	/* XXX need pre-allocate? */
#if 0
	/* allocated in istgt_lu_create_task() */
	lu_task->data = xmalloc(lu_cmd->alloc_len);
	lu_task->sense_data = xmalloc(lu_cmd->sense_alloc_len);
	lu_task->iobuf = NULL;
#endif
	lu_cmd->data = lu_task->data;
	lu_cmd->data_len = 0;
	lu_cmd->sense_data = lu_task->sense_data;
	lu_cmd->sense_data_len = 0;

	tmp[0] = 'Q';
	if (lu_cmd->W_bit) {
		if (lu_cmd->pdu->data_segment_len >= lu_cmd->transfer_len) {
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "LU%d: LUN%d Task Write Immediate Start\n",
			    lu->num, lun);
#if 0
			iobuf = xmalloc(lu_cmd->pdu->data_segment_len);
			memcpy(iobuf, lu_cmd->pdu->data,
			    lu_cmd->pdu->data_segment_len);
			lu_task->iobuf = iobuf;
#else
			iobuf = lu_cmd->pdu->data;
			lu_task->dup_iobuf = 1;
#endif
			lu_cmd->iobuf = iobuf;

			MTX_LOCK(&lu_cmd->lu->mutex);
			rc = istgt_lu_disk_execute(conn, lu_cmd);
			MTX_UNLOCK(&lu_cmd->lu->mutex);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_execute() failed\n");
			error_return:
				rc = istgt_lu_destroy_task(lu_task);
				if (rc < 0) {
					ISTGT_ERRLOG("lu_destroy_task() failed\n");
					return -1;
				}
				return -1;
			}
			lu_task->execute = 1;

			/* response */
			if (conn->use_sender == 0) {
				MTX_LOCK(&conn->task_queue_mutex);
				rc = istgt_queue_enqueue(&conn->task_queue, lu_task);
				MTX_UNLOCK(&conn->task_queue_mutex);
				if (rc < 0) {
					ISTGT_ERRLOG("queue_enqueue() failed\n");
					goto error_return;
				}
				rc = write(conn->task_pipe[1], tmp, 1);
				if(rc < 0 || rc != 1) {
					ISTGT_ERRLOG("write() failed\n");
					goto error_return;
				}
			} else {
				MTX_LOCK(&conn->result_queue_mutex);
				rc = istgt_queue_enqueue(&conn->result_queue, lu_task);
				if (rc < 0) {
					MTX_UNLOCK(&conn->result_queue_mutex);
					ISTGT_ERRLOG("queue_enqueue() failed\n");
					goto error_return;
				}
				rc = pthread_cond_broadcast(&conn->result_queue_cond);
				MTX_UNLOCK(&conn->result_queue_mutex);
				if (rc != 0) {
					ISTGT_ERRLOG("cond_broadcast() failed\n");
					goto error_return;
				}
			}

#if 0
			/* write cache */
			if (spec->req_write_cache) {
				MTX_LOCK(&lu->mutex);
				rc = istgt_lu_disk_write_cache(spec, conn);
				MTX_UNLOCK(&lu->mutex);
				if (rc < 0) {
					ISTGT_ERRLOG("disk_write_cache() failed\n");
					return -1;
				}
			}
#endif
		} else {
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "LU%d: LUN%d Task Write Start\n",
			    lu->num, lun);

#if 0
			MTX_LOCK(&spec->wait_lu_task_mutex);
			spec->wait_lu_task = NULL;
			MTX_UNLOCK(&spec->wait_lu_task_mutex);
#endif
			rc = pthread_mutex_init(&lu_task->trans_mutex, NULL);
			if (rc != 0) {
				ISTGT_ERRLOG("mutex_init() failed\n");
				goto error_return;
			}
			rc = pthread_cond_init(&lu_task->trans_cond, NULL);
			if (rc != 0) {
				ISTGT_ERRLOG("cond_init() failed\n");
				goto error_return;
			}
			rc = pthread_cond_init(&lu_task->exec_cond, NULL);
			if (rc != 0) {
				ISTGT_ERRLOG("cond_init() failed\n");
				goto error_return;
			}
			lu_task->use_cond = 1;
#if 0
			lu_cmd->iobufsize = lu_cmd->transfer_len + 65536;
			iobuf = xmalloc(lu_cmd->iobufsize);
			lu_task->iobuf = iobuf;
#else
			lu_cmd->iobufsize = lu_task->lu_cmd.iobufsize;
			iobuf = lu_task->iobuf;
#endif
			lu_cmd->iobuf = iobuf;
			lu_task->req_transfer_out = 1;
			memset(&abstime, 0, sizeof abstime);
			abstime.tv_sec = 0;
			abstime.tv_nsec = 0;

			MTX_LOCK(&conn->task_queue_mutex);
			rc = istgt_queue_enqueue(&conn->task_queue, lu_task);
			MTX_UNLOCK(&conn->task_queue_mutex);
			if (rc < 0) {
				MTX_UNLOCK(&lu_task->trans_mutex);
				ISTGT_ERRLOG("queue_enqueue() failed\n");
				goto error_return;
			}
			rc = write(conn->task_pipe[1], tmp, 1);
			if(rc < 0 || rc != 1) {
				MTX_UNLOCK(&lu_task->trans_mutex);
				ISTGT_ERRLOG("write() failed\n");
				goto error_return;
			}

			start = now = time(NULL);
			abstime.tv_sec = now + (lu_task->condwait / 1000);
			abstime.tv_nsec = (lu_task->condwait % 1000) * 1000000;
#if 0
			ISTGT_LOG("wait CmdSN=%u\n", lu_task->lu_cmd.CmdSN);
#endif
			MTX_LOCK(&lu_task->trans_mutex);
			MTX_LOCK(&spec->wait_lu_task_mutex);
			spec->wait_lu_task = lu_task;
			MTX_UNLOCK(&spec->wait_lu_task_mutex);
			rc = 0;
			while (lu_task->req_transfer_out == 1) {
				rc = pthread_cond_timedwait(&lu_task->trans_cond,
				    &lu_task->trans_mutex,
				    &abstime);
				if (rc == ETIMEDOUT) {
					if (lu_task->req_transfer_out == 1) {
						lu_task->error = 1;
						MTX_LOCK(&spec->wait_lu_task_mutex);
						spec->wait_lu_task = NULL;
						MTX_UNLOCK(&spec->wait_lu_task_mutex);
						MTX_UNLOCK(&lu_task->trans_mutex);
						now = time(NULL);
						ISTGT_ERRLOG("timeout trans_cond CmdSN=%u "
						    "(time=%d)\n",
						    lu_task->lu_cmd.CmdSN,
						    (int)difftime(now, start));
						/* timeout */
						return -1;
					}
					/* OK cond */
					rc = 0;
					break;
				}
				if (lu_task->error != 0) {
					rc = -1;
					break;
				}
				if (rc != 0) {
					break;
				}
			}
			MTX_LOCK(&spec->wait_lu_task_mutex);
			spec->wait_lu_task = NULL;
			MTX_UNLOCK(&spec->wait_lu_task_mutex);
			MTX_UNLOCK(&lu_task->trans_mutex);
			if (rc != 0) {
				if (rc < 0) {
					lu_task->error = 1;
					if (lu_task->abort) {
						ISTGT_WARNLOG("transfer abort CmdSN=%u\n",
						    lu_task->lu_cmd.CmdSN);
						return -2;
					} else {
						ISTGT_ERRLOG("transfer error CmdSN=%u\n",
						    lu_task->lu_cmd.CmdSN);
						return -1;
					}
				}
				if (rc == ETIMEDOUT) {
					lu_task->error = 1;
					now = time(NULL);
					ISTGT_ERRLOG("timeout trans_cond CmdSN=%u (time=%d)\n",
					    lu_task->lu_cmd.CmdSN, (int)difftime(now, start));
					return -1;
				}
				lu_task->error = 1;
				ISTGT_ERRLOG("cond_timedwait rc=%d\n", rc);
				return -1;
			}

			if (lu_task->req_execute == 0) {
				ISTGT_ERRLOG("wrong request\n");
				goto error_return;
			}
			MTX_LOCK(&lu_cmd->lu->mutex);
			rc = istgt_lu_disk_execute(conn, lu_cmd);
			MTX_UNLOCK(&lu_cmd->lu->mutex);
			if (rc < 0) {
				lu_task->error = 1;
				ISTGT_ERRLOG("lu_disk_execute() failed\n");
				goto error_return;
			}
			lu_task->execute = 1;

			/* response */
			if (conn->use_sender == 0) {
				MTX_LOCK(&conn->task_queue_mutex);
				rc = istgt_queue_enqueue(&conn->task_queue, lu_task);
				MTX_UNLOCK(&conn->task_queue_mutex);
				if (rc < 0) {
					ISTGT_ERRLOG("queue_enqueue() failed\n");
					goto error_return;
				}
				rc = write(conn->task_pipe[1], tmp, 1);
				if(rc < 0 || rc != 1) {
					ISTGT_ERRLOG("write() failed\n");
					goto error_return;
				}
			} else {
				MTX_LOCK(&conn->result_queue_mutex);
				rc = istgt_queue_enqueue(&conn->result_queue, lu_task);
				if (rc < 0) {
					MTX_UNLOCK(&conn->result_queue_mutex);
					ISTGT_ERRLOG("queue_enqueue() failed\n");
					goto error_return;
				}
				rc = pthread_cond_broadcast(&conn->result_queue_cond);
				MTX_UNLOCK(&conn->result_queue_mutex);
				if (rc != 0) {
					ISTGT_ERRLOG("cond_broadcast() failed\n");
					goto error_return;
				}
			}

#if 0
			/* write cache */
			if (spec->req_write_cache) {
				MTX_LOCK(&lu->mutex);
				rc = istgt_lu_disk_write_cache(spec, conn);
				MTX_UNLOCK(&lu->mutex);
				if (rc < 0) {
					ISTGT_ERRLOG("disk_write_cache() failed\n");
					return -1;
				}
			}
#endif
		}
	} else {
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
		    "LU%d: LUN%d Task Read Start\n",
		    lu->num, lun);
#if 0
		lu_cmd->iobufsize = lu_cmd->transfer_len + 65536;
		iobuf = xmalloc(lu_cmd->iobufsize);
		lu_task->iobuf = iobuf;
#else
		lu_cmd->iobufsize = lu_task->lu_cmd.iobufsize;
		iobuf = lu_task->iobuf;
#endif
		lu_cmd->iobuf = iobuf;
		MTX_LOCK(&lu_cmd->lu->mutex);
		rc = istgt_lu_disk_execute(conn, lu_cmd);
		MTX_UNLOCK(&lu_cmd->lu->mutex);
		if (rc < 0) {
			ISTGT_ERRLOG("lu_disk_execute() failed\n");
			goto error_return;
		}
		lu_task->execute = 1;

		/* response */
		if (conn->use_sender == 0) {
			MTX_LOCK(&conn->task_queue_mutex);
			rc = istgt_queue_enqueue(&conn->task_queue, lu_task);
			MTX_UNLOCK(&conn->task_queue_mutex);
			if (rc < 0) {
				ISTGT_ERRLOG("queue_enqueue() failed\n");
				goto error_return;
			}
			rc = write(conn->task_pipe[1], tmp, 1);
			if(rc < 0 || rc != 1) {
				ISTGT_ERRLOG("write() failed\n");
				goto error_return;
			}
		} else {
			MTX_LOCK(&conn->result_queue_mutex);
			rc = istgt_queue_enqueue(&conn->result_queue, lu_task);
			if (rc < 0) {
				MTX_UNLOCK(&conn->result_queue_mutex);
				ISTGT_ERRLOG("queue_enqueue() failed\n");
				goto error_return;
			}
			rc = pthread_cond_broadcast(&conn->result_queue_cond);
			MTX_UNLOCK(&conn->result_queue_mutex);
			if (rc != 0) {
				ISTGT_ERRLOG("cond_broadcast() failed\n");
				goto error_return;
			}
		}
	}

	ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "LU%d: LUN%d queue end\n",
	    lu->num, lun);

	if (abort_task) {
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "Abort Task\n");
		return -1;
	}
	return 0;
}

int
istgt_lu_disk_execute(CONN_Ptr conn, ISTGT_LU_CMD_Ptr lu_cmd)
{
	ISTGT_LU_Ptr lu;
	ISTGT_LU_DISK *spec;
	uint8_t *data;
	uint8_t *cdb;
	uint32_t allocation_len;
	int data_len;
	int data_alloc_len;
	uint64_t lba;
	uint32_t len;
	uint32_t transfer_len;
	uint32_t parameter_len;
	uint8_t *sense_data;
	size_t *sense_len;
	int lun_i;
	int rc;

	if (lu_cmd == NULL)
		return -1;
	lu = lu_cmd->lu;
	if (lu == NULL) {
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return -1;
	}
	spec = NULL;
	cdb = lu_cmd->cdb;
	data = lu_cmd->data;
	data_alloc_len = lu_cmd->alloc_len;
	sense_data = lu_cmd->sense_data;
	sense_len = &lu_cmd->sense_data_len;
	*sense_len = 0;

	lun_i = istgt_lu_islun2lun(lu_cmd->lun);
	if (lun_i >= lu->maxlun) {
#ifdef ISTGT_TRACE_DISK
		ISTGT_ERRLOG("LU%d: LUN%4.4"PRIx64" invalid\n",
		    lu->num, lun_i);
#endif /* ISTGT_TRACE_DISK */
		if (cdb[0] == SPC_INQUIRY) {
			allocation_len = DGET16(&cdb[3]);
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			memset(data, 0, allocation_len);
			/* PERIPHERAL QUALIFIER(7-5) PERIPHERAL DEVICE TYPE(4-0) */
			BDSET8W(&data[0], 0x03, 7, 3);
			BDADD8W(&data[0], 0x1f, 4, 5);
			data_len = 96;
			memset(&data[1], 0, data_len - 1);
			/* ADDITIONAL LENGTH */
			data[4] = data_len - 5;
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			return 0;
		} else {
			/* LOGICAL UNIT NOT SUPPORTED */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x25, 0x00);
			lu_cmd->data_len = 0;
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return 0;
		}
	}
	spec = (ISTGT_LU_DISK *) lu->lun[lun_i].spec;
	if (spec == NULL) {
		/* LOGICAL UNIT NOT SUPPORTED */
		BUILD_SENSE(ILLEGAL_REQUEST, 0x25, 0x00);
		lu_cmd->data_len = 0;
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return 0;
	}

	if (spec->sense != 0) {
		int sk, asc, ascq;
		if (cdb[0] != SPC_INQUIRY
		    && cdb[0] != SPC_REPORT_LUNS) {
			sk = (spec->sense >> 16) & 0xffU;
			asc = (spec->sense >> 8) & 0xffU;
			ascq = (spec->sense >> 0) & 0xffU;
			spec->sense = 0;
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
			    "Generate sk=0x%x, asc=0x%x, ascq=0x%x\n",
			    sk, asc, ascq);
			*sense_len
				= istgt_lu_disk_build_sense_data(spec, sense_data,
				    sk, asc, ascq);
			lu_cmd->data_len = 0;
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return 0;
		}
	}

	if (spec->err_write_cache) {
		/* WRITE ERROR - AUTO REALLOCATION FAILED */
		BUILD_SENSE2(MEDIUM_ERROR, 0x0c, 0x02);
#if 0
		/* WRITE ERROR - RECOMMEND REASSIGNMENT */
		BUILD_SENSE2(MEDIUM_ERROR, 0x0c, 0x03);
#endif
		spec->err_write_cache = 0;
		lba = spec->woffset / spec->blocklen;
		ISTGT_TRACELOG(ISTGT_TRACE_DEBUG,
		    "Deferred error (write cache) at %"PRIu64"\n", lba);
		if (lba > 0xffffffffULL) {
			ISTGT_WARNLOG("lba > 0xffffffff\n");
		}
		/* COMMAND-SPECIFIC INFORMATION */
		DSET32(&sense_data[8], (uint32_t)(lba & 0xffffffffULL));
		lu_cmd->data_len = 0;
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		return 0;
	}

	ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
	    "SCSI OP=0x%x, LUN=0x%16.16"PRIx64"\n",
	    cdb[0], lu_cmd->lun);
#ifdef ISTGT_TRACE_DISK
	if (cdb[0] != SPC_TEST_UNIT_READY) {
		istgt_scsi_dump_cdb(cdb);
	}
#endif /* ISTGT_TRACE_DISK */
	switch (cdb[0]) {
	case SPC_INQUIRY:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "INQUIRY\n");
		if (lu_cmd->R_bit == 0) {
			ISTGT_ERRLOG("R_bit == 0\n");
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		allocation_len = DGET16(&cdb[3]);
		if (allocation_len > (size_t) data_alloc_len) {
			ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
			    data_alloc_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		memset(data, 0, allocation_len);
		data_len = istgt_lu_disk_scsi_inquiry(spec, conn, cdb,
		    data, data_alloc_len);
		if (data_len < 0) {
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			break;
		}
		ISTGT_TRACEDUMP(ISTGT_TRACE_DEBUG, "INQUIRY", data, data_len);
		lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
		lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		break;

	case SPC_REPORT_LUNS:
		{
			int sel;

			ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "REPORT LUNS\n");
			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				return -1;
			}

			sel = cdb[2];
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "sel=%x\n", sel);

			allocation_len = DGET32(&cdb[6]);
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			if (allocation_len < 16) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			memset(data, 0, allocation_len);
			data_len = istgt_lu_disk_scsi_report_luns(lu, conn, cdb, sel,
			    data, data_alloc_len);
			if (data_len < 0) {
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			ISTGT_TRACEDUMP(ISTGT_TRACE_DEBUG, "REPORT LUNS", data, data_len);
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		}
		break;

	case SPC_TEST_UNIT_READY:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "TEST_UNIT_READY\n");
		lu_cmd->data_len = 0;
		lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		break;

	case SBC_START_STOP_UNIT:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "START_STOP_UNIT\n");
		{
			int pc, loej, start;

			pc = BGET8W(&cdb[4], 7, 4);
			loej = BGET8(&cdb[4], 1);
			start = BGET8(&cdb[4], 0);

			if (start != 0 || pc != 0) {
				if (spec->rsv_key) {
					rc = istgt_lu_disk_check_pr(spec, conn,
					    PR_ALLOW(0,0,1,0,0));
					if (rc != 0) {
						lu_cmd->status
							= ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
						break;
					}
				}
			}

			lu_cmd->data_len = 0;
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		}
		break;

	case SBC_READ_CAPACITY_10:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "READ_CAPACITY_10\n");
		if (lu_cmd->R_bit == 0) {
			ISTGT_ERRLOG("R_bit == 0\n");
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			return -1;
		}
		if (spec->blockcnt - 1 > 0xffffffffULL) {
			DSET32(&data[0], 0xffffffffUL);
		} else {
			DSET32(&data[0], (uint32_t) (spec->blockcnt - 1));
		}
		DSET32(&data[4], (uint32_t) spec->blocklen);
		data_len = 8;
		lu_cmd->data_len = data_len;
		lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		ISTGT_TRACEDUMP(ISTGT_TRACE_DEBUG,
		    "SBC_READ_CAPACITY_10", data, data_len);
		break;

	case SPC_SERVICE_ACTION_IN_16:
		switch (BGET8W(&cdb[1], 4, 5)) { /* SERVICE ACTION */
		case SBC_SAI_READ_CAPACITY_16:
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "READ_CAPACITY_16\n");
			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			allocation_len = DGET32(&cdb[10]);
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			memset(data, 0, allocation_len);
			DSET64(&data[0], spec->blockcnt - 1);
			DSET32(&data[8], (uint32_t) spec->blocklen);
			data[12] = 0;                   /* RTO_EN(1) PROT_EN(0) */
			memset(&data[13], 0, 32 - (8 + 4 + 1));     /* Reserved */
			data_len = 32;
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		case SBC_SAI_READ_LONG_16:
		default:
			/* INVALID COMMAND OPERATION CODE */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x20, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			break;
		}
		break;

	case SPC_MODE_SELECT_6:
#if 0
		istgt_scsi_dump_cdb(cdb);
#endif
		{
			int pf, sp, pllen;
			int mdlen, mt, dsp, bdlen;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			pf = BGET8(&cdb[1], 4);
			sp = BGET8(&cdb[1], 0);
			pllen = cdb[4];             /* Parameter List Length */

			if (pllen == 0) {
				lu_cmd->data_len = 0;
				lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
				break;
			}
			/* Data-Out */
			rc = istgt_lu_disk_transfer_data(conn, lu_cmd, lu_cmd->iobuf,
			    lu_cmd->iobufsize, pllen);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_transfer_data() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			if (pllen < 4) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
#if 0
			istgt_dump("MODE SELECT(6)", lu_cmd->iobuf, pllen);
#endif
			data = lu_cmd->iobuf;
			mdlen = data[0];            /* Mode Data Length */
			mt = data[1];               /* Medium Type */
			dsp = data[2];              /* Device-Specific Parameter */
			bdlen = data[3];            /* Block Descriptor Length */

			/* Short LBA mode parameter block descriptor */
			/* data[4]-data[7] Number of Blocks */
			/* data[8]-data[11] Block Length */

			/* page data */
			data_len = istgt_lu_disk_scsi_mode_select_page(spec, conn, cdb, pf, sp, &data[4 + bdlen], pllen - (4 + bdlen));
			if (data_len != 0) {
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->data_len = pllen;
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SPC_MODE_SELECT_10:
#if 0
		istgt_scsi_dump_cdb(cdb);
#endif
		{
			int pf, sp, pllen;
			int mdlen, mt, dsp, bdlen;
			int llba;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			pf = BGET8(&cdb[1], 4);
			sp = BGET8(&cdb[1], 0);
			pllen = DGET16(&cdb[7]);    /* Parameter List Length */

			if (pllen == 0) {
				lu_cmd->data_len = 0;
				lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
				break;
			}
			/* Data-Out */
			rc = istgt_lu_disk_transfer_data(conn, lu_cmd, lu_cmd->iobuf,
			    lu_cmd->iobufsize, pllen);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_transfer_data() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			if (pllen < 4) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
#if 0
			istgt_dump("MODE SELECT(10)", lu_cmd->iobuf, pllen);
#endif
			data = lu_cmd->iobuf;
			mdlen = DGET16(&data[0]);   /* Mode Data Length */
			mt = data[2];               /* Medium Type */
			dsp = data[3];              /* Device-Specific Parameter */
			llba = BGET8(&data[4], 0);  /* Long LBA */
			bdlen = DGET16(&data[6]);   /* Block Descriptor Length */

			if (llba) {
				/* Long LBA mode parameter block descriptor */
				/* data[8]-data[15] Number of Blocks */
				/* data[16]-data[19] Reserved */
				/* data[20]-data[23] Block Length */
			} else {
				/* Short LBA mode parameter block descriptor */
				/* data[8]-data[11] Number of Blocks */
				/* data[12]-data[15] Block Length */
			}

			/* page data */
			data_len = istgt_lu_disk_scsi_mode_select_page(spec, conn, cdb, pf, sp, &data[8 + bdlen], pllen - (8 + bdlen));
			if (data_len != 0) {
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->data_len = pllen;
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SPC_MODE_SENSE_6:
#if 0
		istgt_scsi_dump_cdb(cdb);
#endif
		{
			int dbd, pc, page, subpage;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			dbd = BGET8(&cdb[1], 3);
			pc = BGET8W(&cdb[2], 7, 2);
			page = BGET8W(&cdb[2], 5, 6);
			subpage = cdb[3];

			allocation_len = cdb[4];
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			memset(data, 0, allocation_len);

			data_len = istgt_lu_disk_scsi_mode_sense6(spec, conn, cdb, dbd, pc, page, subpage, data, data_alloc_len);
			if (data_len < 0) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
#if 0
			istgt_dump("MODE SENSE(6)", data, data_len);
#endif
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SPC_MODE_SENSE_10:
#if 0
		istgt_scsi_dump_cdb(cdb);
#endif
		{
			int dbd, pc, page, subpage;
			int llbaa;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			llbaa = BGET8(&cdb[1], 4);
			dbd = BGET8(&cdb[1], 3);
			pc = BGET8W(&cdb[2], 7, 2);
			page = BGET8W(&cdb[2], 5, 6);
			subpage = cdb[3];

			allocation_len = DGET16(&cdb[7]);
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			memset(data, 0, allocation_len);

			data_len = istgt_lu_disk_scsi_mode_sense10(spec, conn, cdb, llbaa, dbd, pc, page, subpage, data, data_alloc_len);
			if (data_len < 0) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
#if 0
			istgt_dump("MODE SENSE(10)", data, data_len);
#endif
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SPC_LOG_SELECT:
	case SPC_LOG_SENSE:
		/* INVALID COMMAND OPERATION CODE */
		BUILD_SENSE(ILLEGAL_REQUEST, 0x20, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		break;

	case SPC_REQUEST_SENSE:
		{
			int desc;
			int sk, asc, ascq;

			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			desc = BGET8(&cdb[1], 0);
			if (desc != 0) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}

			allocation_len = cdb[4];
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			memset(data, 0, allocation_len);

			if (!spec->sense) {
				/* NO ADDITIONAL SENSE INFORMATION */
				sk = ISTGT_SCSI_SENSE_NO_SENSE;
				asc = 0x00;
				ascq = 0x00;
			} else {
				sk = (spec->sense >> 16) & 0xffU;
				asc = (spec->sense >> 8) & 0xffU;
				ascq = spec->sense & 0xffU;
			}
			data_len = istgt_lu_disk_build_sense_data(spec, sense_data,
			    sk, asc, ascq);
			if (data_len < 0 || data_len < 2) {
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			/* omit SenseLength */
			data_len -= 2;
			memcpy(data, sense_data + 2, data_len);
#if 0
			istgt_dump("REQUEST SENSE", data, data_len);
#endif
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_READ_6:
		{
			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(1,0,1,1,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			lba = (uint64_t) (DGET24(&cdb[1]) & 0x001fffffU);
			transfer_len = (uint32_t) DGET8(&cdb[4]);
			if (transfer_len == 0) {
				transfer_len = 256;
			}
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "READ_6(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbread(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbread() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_READ_10:
		{
			int dpo, fua, fua_nv;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(1,0,1,1,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			dpo = BGET8(&cdb[1], 4);
			fua = BGET8(&cdb[1], 3);
			fua_nv = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET32(&cdb[2]);
			transfer_len = (uint32_t) DGET16(&cdb[7]);
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "READ_10(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbread(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbread() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_READ_12:
		{
			int dpo, fua, fua_nv;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(1,0,1,1,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			dpo = BGET8(&cdb[1], 4);
			fua = BGET8(&cdb[1], 3);
			fua_nv = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET32(&cdb[2]);
			transfer_len = (uint32_t) DGET32(&cdb[6]);
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "READ_12(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbread(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbread() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_READ_16:
		{
			int dpo, fua, fua_nv;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(1,0,1,1,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			dpo = BGET8(&cdb[1], 4);
			fua = BGET8(&cdb[1], 3);
			fua_nv = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET64(&cdb[2]);
			transfer_len = (uint32_t) DGET32(&cdb[10]);
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "READ_16(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbread(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbread() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_WRITE_6:
		{
			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->W_bit == 0) {
				ISTGT_ERRLOG("W_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			lba = (uint64_t) (DGET24(&cdb[1]) & 0x001fffffU);
			transfer_len = (uint32_t) DGET8(&cdb[4]);
			if (transfer_len == 0) {
				transfer_len = 256;
			}
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "WRITE_6(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbwrite(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbwrite() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_WRITE_10:
	case SBC_WRITE_AND_VERIFY_10:
		{
			int dpo, fua, fua_nv;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->W_bit == 0) {
				ISTGT_ERRLOG("W_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			dpo = BGET8(&cdb[1], 4);
			fua = BGET8(&cdb[1], 3);
			fua_nv = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET32(&cdb[2]);
			transfer_len = (uint32_t) DGET16(&cdb[7]);
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "WRITE_10(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbwrite(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbwrite() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_WRITE_12:
	case SBC_WRITE_AND_VERIFY_12:
		{
			int dpo, fua, fua_nv;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->W_bit == 0) {
				ISTGT_ERRLOG("W_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			dpo = BGET8(&cdb[1], 4);
			fua = BGET8(&cdb[1], 3);
			fua_nv = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET32(&cdb[2]);
			transfer_len = (uint32_t) DGET32(&cdb[6]);
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "WRITE_12(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbwrite(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbwrite() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_WRITE_16:
	case SBC_WRITE_AND_VERIFY_16:
		{
			int dpo, fua, fua_nv;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->W_bit == 0) {
				ISTGT_ERRLOG("W_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			dpo = BGET8(&cdb[1], 4);
			fua = BGET8(&cdb[1], 3);
			fua_nv = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET64(&cdb[2]);
			transfer_len = (uint32_t) DGET32(&cdb[10]);
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "WRITE_16(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbwrite(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbwrite() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_VERIFY_10:
		{
			int dpo, bytchk;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(1,0,1,1,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			dpo = BGET8(&cdb[1], 4);
			bytchk = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET32(&cdb[2]);
			len = (uint32_t) DGET16(&cdb[7]);
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "VERIFY_10(lba %"PRIu64", len %u blocks)\n",
			    lba, len);
			lu_cmd->data_len = 0;
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_VERIFY_12:
		{
			int dpo, bytchk;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(1,0,1,1,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			dpo = BGET8(&cdb[1], 4);
			bytchk = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET32(&cdb[2]);
			len = (uint32_t) DGET32(&cdb[6]);
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "VERIFY_12(lba %"PRIu64", len %u blocks)\n",
			    lba, len);
			lu_cmd->data_len = 0;
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_VERIFY_16:
		{
			int dpo, bytchk;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(1,0,1,1,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			dpo = BGET8(&cdb[1], 4);
			bytchk = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET64(&cdb[2]);
			len = (uint32_t) DGET32(&cdb[10]);
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "VERIFY_16(lba %"PRIu64", len %u blocks)\n",
			    lba, len);
			lu_cmd->data_len = 0;
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_WRITE_SAME_10:
		{
			int wprotect, pbdata, lbdata, group_no;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->W_bit == 0) {
				ISTGT_ERRLOG("W_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			wprotect = BGET8W(&cdb[1], 7, 3);
			pbdata = BGET8(&cdb[1], 2);
			lbdata = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET32(&cdb[2]);
			transfer_len = (uint32_t) DGET16(&cdb[7]);
			group_no = BGET8W(&cdb[6], 4, 5);

			/* only PBDATA=0 and LBDATA=0 support */
			if (pbdata || lbdata) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}

			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "WRITE_SAME_10(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbwrite_same(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbwrite_same() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_WRITE_SAME_16:
		{
			int wprotect, anchor, unmap, pbdata, lbdata, group_no;

#if 0
			istgt_scsi_dump_cdb(cdb);
#endif
			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->W_bit == 0) {
				ISTGT_ERRLOG("W_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			wprotect = BGET8W(&cdb[1], 7, 3);
			anchor = BGET8(&cdb[1], 4);
			unmap = BGET8(&cdb[1], 3);
			pbdata = BGET8(&cdb[1], 2);
			lbdata = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET64(&cdb[2]);
			transfer_len = (uint32_t) DGET32(&cdb[10]);
			group_no = BGET8W(&cdb[14], 4, 5);

			/* only PBDATA=0 and LBDATA=0 support */
			if (pbdata || lbdata) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			if (anchor) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}

			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "WRITE_SAME_16(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbwrite_same(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbwrite_same() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_COMPARE_AND_WRITE:
		{
			int64_t maxlen;
			int wprotect, dpo, fua, fua_nv, group_no;

#if 0
			istgt_scsi_dump_cdb(cdb);
#endif
			if (spec->lu->istgt->swmode == ISTGT_SWMODE_TRADITIONAL) {
				/* INVALID COMMAND OPERATION CODE */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x20, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			if (lu_cmd->W_bit == 0) {
				ISTGT_ERRLOG("W_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			wprotect = BGET8W(&cdb[1], 7, 3);
			dpo = BGET8(&cdb[1], 4);
			fua = BGET8(&cdb[1], 3);
			fua_nv = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET64(&cdb[2]);
			transfer_len = (uint32_t) DGET8(&cdb[13]);
			group_no = BGET8W(&cdb[14], 4, 5);

			maxlen = ISTGT_LU_WORK_ATS_BLOCK_SIZE / spec->blocklen;
			if (maxlen > 0xff) {
				maxlen = 0xff;
			}
			if (transfer_len > maxlen) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}

			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "COMPARE_AND_WRITE(lba %"PRIu64", len %u blocks)\n",
			    lba, transfer_len);
			rc = istgt_lu_disk_lbwrite_ats(spec, conn, lu_cmd, lba, transfer_len);
			if (rc < 0) {
				//ISTGT_ERRLOG("lu_disk_lbwrite_ats() failed\n");
				/* sense data build by function */
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_SYNCHRONIZE_CACHE_10:
		{
			int sync_nv, immed;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			sync_nv = BGET8(&cdb[1], 2);
			immed = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET32(&cdb[2]);
			len = (uint32_t) DGET16(&cdb[7]);
			if (len == 0) {
				len = spec->blockcnt;
			}
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "SYNCHRONIZE_CACHE_10(lba %"PRIu64
			    ", len %u blocks)\n",
			    lba, len);
			rc = istgt_lu_disk_lbsync(spec, conn, lu_cmd, lba, len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbsync() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->data_len = 0;
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_SYNCHRONIZE_CACHE_16:
		{
			int sync_nv, immed;

			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}

			sync_nv = BGET8(&cdb[1], 2);
			immed = BGET8(&cdb[1], 1);
			lba = (uint64_t) DGET64(&cdb[2]);
			len = (uint32_t) DGET32(&cdb[10]);
			if (len == 0) {
				len = spec->blockcnt;
			}
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
			    "SYNCHRONIZE_CACHE_10(lba %"PRIu64
			    ", len %u blocks)\n",
			    lba, len);
			rc = istgt_lu_disk_lbsync(spec, conn, lu_cmd, lba, len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_lbsync() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->data_len = 0;
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_READ_DEFECT_DATA_10:
		{
			int req_plist, req_glist, list_format;

			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			req_plist = BGET8(&cdb[2], 4);
			req_glist = BGET8(&cdb[2], 3);
			list_format = BGET8W(&cdb[2], 2, 3);

			allocation_len = (uint32_t) DGET16(&cdb[7]);
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			memset(data, 0, allocation_len);

			data_len = istgt_lu_disk_scsi_read_defect10(spec, conn, cdb,
			    req_plist, req_glist, list_format, data, data_alloc_len);
			if (data_len < 0) {
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SBC_READ_DEFECT_DATA_12:
		{
			int req_plist, req_glist, list_format;

			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			req_plist = BGET8(&cdb[2], 4);
			req_glist = BGET8(&cdb[2], 3);
			list_format = BGET8W(&cdb[2], 2, 3);

			allocation_len = DGET32(&cdb[6]);
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			memset(data, 0, allocation_len);

			data_len = istgt_lu_disk_scsi_read_defect12(spec, conn, cdb,
			    req_plist, req_glist, list_format, data, data_alloc_len);
			if (data_len < 0) {
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		}

	case SCC_MAINTENANCE_IN:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MAINTENANCE_IN\n");
		switch (BGET8W(&cdb[1], 4, 5)) { /* SERVICE ACTION */
		case SPC_MI_REPORT_TARGET_PORT_GROUPS:
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "REPORT_TARGET_PORT_GROUPS\n");
			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			allocation_len = DGET32(&cdb[6]);
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			memset(data, 0, allocation_len);
			data_len = istgt_lu_disk_scsi_report_target_port_groups(spec, conn, cdb, data, data_alloc_len);
			if (data_len < 0) {
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			ISTGT_TRACEDUMP(ISTGT_TRACE_DEBUG,
			    "REPORT_TARGET_PORT_GROUPS", data, data_len);
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		default:
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "SA=0x%2.2x\n",
			    BGET8W(&cdb[1], 4, 5));
			/* INVALID COMMAND OPERATION CODE */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x20, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			break;
		}
		break;

	case SCC_MAINTENANCE_OUT:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "MAINTENANCE_OUT\n");
		switch (BGET8W(&cdb[1], 4, 5)) { /* SERVICE ACTION */
		case SPC_MO_SET_TARGET_PORT_GROUPS:
			ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "SET_TARGET_PORT_GROUPS\n");
			if (spec->rsv_key) {
				rc = istgt_lu_disk_check_pr(spec, conn, PR_ALLOW(0,0,1,0,0));
				if (rc != 0) {
					lu_cmd->status = ISTGT_SCSI_STATUS_RESERVATION_CONFLICT;
					break;
				}
			}
			if (lu_cmd->W_bit == 0) {
				ISTGT_ERRLOG("W_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			parameter_len = DGET32(&cdb[6]);
			if (parameter_len == 0) {
				lu_cmd->data_len = 0;
				lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
				break;
			}
			/* Data-Out */
			rc = istgt_lu_disk_transfer_data(conn, lu_cmd, lu_cmd->iobuf,
			    lu_cmd->iobufsize, parameter_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_transfer_data() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			if (parameter_len < 4) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			ISTGT_TRACEDUMP(ISTGT_TRACE_DEBUG,
			    "SET_TARGET_PORT_GROUPS",
			    lu_cmd->iobuf, parameter_len);
			data = lu_cmd->iobuf;
			/* data[0]-data[3] Reserved */
			/* Set target port group descriptor(s) */
			data_len = istgt_lu_disk_scsi_set_target_port_groups(spec, conn, cdb, &data[4], parameter_len - 4);
			if (data_len < 0) {
				/* INVALID FIELD IN PARAMETER LIST */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x26, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			lu_cmd->data_len = parameter_len;
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
			break;
		default:
			ISTGT_TRACELOG(ISTGT_TRACE_DEBUG, "SA=0x%2.2x\n",
			    BGET8W(&cdb[1], 4, 5));
			/* INVALID COMMAND OPERATION CODE */
			BUILD_SENSE(ILLEGAL_REQUEST, 0x20, 0x00);
			lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
			break;
		}
		break;

	case SPC_PERSISTENT_RESERVE_IN:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "PERSISTENT_RESERVE_IN\n");
		{
			int sa;

			if (lu_cmd->R_bit == 0) {
				ISTGT_ERRLOG("R_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			sa = BGET8W(&cdb[1], 4, 5);
			allocation_len = DGET16(&cdb[7]);
			if (allocation_len > (size_t) data_alloc_len) {
				ISTGT_ERRLOG("data_alloc_len(%d) too small\n",
				    data_alloc_len);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}
			memset(data, 0, allocation_len);

			data_len = istgt_lu_disk_scsi_persistent_reserve_in(spec, conn, lu_cmd, sa, data, allocation_len);
			if (data_len < 0) {
				/* status build by function */
				break;
			}
			ISTGT_TRACEDUMP(ISTGT_TRACE_DEBUG,
			    "PERSISTENT_RESERVE_IN", data, data_len);
			lu_cmd->data_len = DMIN32((size_t)data_len, lu_cmd->transfer_len);
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		}
		break;

	case SPC_PERSISTENT_RESERVE_OUT:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "PERSISTENT_RESERVE_OUT\n");
		{
			int sa, scope, type;

			if (lu_cmd->W_bit == 0) {
				ISTGT_ERRLOG("W_bit == 0\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				return -1;
			}

			sa = BGET8W(&cdb[1], 4, 5);
			scope = BGET8W(&cdb[2], 7, 4);
			type = BGET8W(&cdb[2], 3, 4);
			parameter_len = DGET32(&cdb[5]);

			/* Data-Out */
			rc = istgt_lu_disk_transfer_data(conn, lu_cmd, lu_cmd->iobuf,
			    lu_cmd->iobufsize, parameter_len);
			if (rc < 0) {
				ISTGT_ERRLOG("lu_disk_transfer_data() failed\n");
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}
			if (parameter_len < 24) {
				/* INVALID FIELD IN CDB */
				BUILD_SENSE(ILLEGAL_REQUEST, 0x24, 0x00);
				lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
				break;
			}

			ISTGT_TRACEDUMP(ISTGT_TRACE_DEBUG,
			    "PERSISTENT_RESERVE_OUT",
			    lu_cmd->iobuf, parameter_len);
			data = lu_cmd->iobuf;

			data_len = istgt_lu_disk_scsi_persistent_reserve_out(spec, conn, lu_cmd, sa, scope, type, &data[0], parameter_len);
			if (data_len < 0) {
				/* status build by function */
				break;
			}
			lu_cmd->data_len = parameter_len;
			lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		}
		break;

	/* XXX TODO: fix */
	case 0x85: /* ATA PASS-THROUGH(16) */
	case 0xA1: /* ATA PASS-THROUGH(12) */
		/* INVALID COMMAND OPERATION CODE */
		BUILD_SENSE(ILLEGAL_REQUEST, 0x20, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		break;
	case SPC_EXTENDED_COPY:
		/* INVALID COMMAND OPERATION CODE */
		BUILD_SENSE(ILLEGAL_REQUEST, 0x20, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		break;
	case SPC2_RELEASE_6:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "RELEASE_6\n");
		rc = istgt_lu_disk_scsi_release(spec, conn, lu_cmd);
		if (rc < 0) {
			/* build by function */
			break;
		}
		lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		break;
	case SPC2_RELEASE_10:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "RELEASE_10\n");
		rc = istgt_lu_disk_scsi_release(spec, conn, lu_cmd);
		if (rc < 0) {
			/* build by function */
			break;
		}
		lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		break;
	case SPC2_RESERVE_6:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "RESERVE_6\n");
		rc = istgt_lu_disk_scsi_reserve(spec, conn, lu_cmd);
		if (rc < 0) {
			/* build by function */
			break;
		}
		lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		break;
	case SPC2_RESERVE_10:
		ISTGT_TRACELOG(ISTGT_TRACE_SCSI, "RESERVE_10\n");
		rc = istgt_lu_disk_scsi_reserve(spec, conn, lu_cmd);
		if (rc < 0) {
			/* build by function */
			break;
		}
		lu_cmd->status = ISTGT_SCSI_STATUS_GOOD;
		break;

	default:
		ISTGT_ERRLOG("unsupported SCSI OP=0x%x\n", cdb[0]);
		/* INVALID COMMAND OPERATION CODE */
		BUILD_SENSE(ILLEGAL_REQUEST, 0x20, 0x00);
		lu_cmd->status = ISTGT_SCSI_STATUS_CHECK_CONDITION;
		break;
	}

	ISTGT_TRACELOG(ISTGT_TRACE_SCSI,
	    "SCSI OP=0x%x, LUN=0x%16.16"PRIx64" status=0x%x,"
	    " complete\n",
	    cdb[0], lu_cmd->lun, lu_cmd->status);
	return 0;
}
