/*
 * Copyright 2016, IBM Corp.
 *
 * This file is licensed to you under your choice of the GNU Lesser
 * General Public License, version 2.1 or any later version (LGPLv2.1 or
 * later), or the Apache License 2.0.
 */

/*
 * Block driver for the File Optical Format.
 *
 * Copyright (C) 2016, IBM Corp.
 * Authors: Michael Cyr <mikecyr@linux.vnet.ibm.com>
 * Authors: Bryant G. Ly <bryantly@linux.vnet.ibm.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#define _GNU_SOURCE
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <endian.h>
#include <scsi/scsi.h>
#include <linux/cdrom.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>

#include "be_byteshift.h"
#include "tcmu-runner.h"
#include "tcmur_device.h"
#include "libtcmu.h"

struct fbo_state {
	int fd;
	uint64_t num_lbas;
	uint32_t block_size;
	uint32_t cur_lba;

#define FBO_READ_ONLY		0x01
#define FBO_PREV_EJECT		0x02
#define FBO_DEV_IO		0x04
#define FBO_BUSY_EVENT		0x08
#define FBO_FORMATTING		0x10
#define FBO_FORMAT_IMMED	0x20
	uint32_t flags;
	uint32_t format_progress;
	uint8_t event_op_ch_code;
	uint8_t async_cache_count;
	pthread_mutex_t state_mtx;
	int curr_handler;
};

static void fbo_report_op_change(struct tcmu_device *dev, uint8_t code)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);

	pthread_mutex_lock(&state->state_mtx);
	if (code > state->event_op_ch_code)
		state->event_op_ch_code = code;
	pthread_mutex_unlock(&state->state_mtx);
}

/* Note: this is called per lun, not per mapping */
static int fbo_open(struct tcmu_device *dev, bool reopen)
{
	struct fbo_state *state;
	int64_t size;
	char *options;
	char *path;
	int ret;

	state = calloc(1, sizeof(*state));
	if (!state)
		return -ENOMEM;

	tcmur_dev_set_private(dev, state);

#if 0
	/* TBD: If we can figure out how the hw_block_size attribute
	 * gets set (and change it), we could use that in the future.
	 */
	state->block_size = tcmu_cfgfs_dev_get_attr(dev, "hw_block_size");
	if (state->block_size == -1) {
		tcmu_err("Could not get device block size\n");
		goto err;
	}
	tcmu_dev_set_block_size(dev, state->block_size);
#else
	/* MM logical units use a block size of 2048 */
	state->block_size = 2048;
	tcmu_dev_set_block_size(dev, state->block_size);
#endif

	size = tcmu_cfgfs_dev_get_info_u64(dev, "Size", &ret);
	if (ret < 0) {
		tcmu_err("Could not get device size\n");
		goto err;
	}

	tcmu_dev_set_num_lbas(dev, size / state->block_size);
	state->num_lbas = size / state->block_size;

	tcmu_dbg("open: cfgstring %s\n", tcmu_dev_get_cfgstring(dev));
	options = strchr(tcmu_dev_get_cfgstring(dev), '/');
	if (!options) {
		tcmu_err("invalid cfgstring\n");
		goto err;
	}
	options += 1; /* get past '/' */
	while (options[0] != '/') {
		if (!strncasecmp(options, "ro/", 3))
			state->flags |= FBO_READ_ONLY;
		else
			tcmu_err("Ignoring unknown option %s\n", options);

		options = strchr(options, '/');
		if (!options) {
			tcmu_err("no path found in cfgstring");
			goto err;
		}
		options += 1;
	}

	path = options;
	if (!path) {
		tcmu_err("no path found in cfgstring\n");
		goto err;
	}

	if (access(path, F_OK) == -1)
		state->fd = open(path, O_CREAT | O_RDWR | O_EXCL,
				 S_IRUSR | S_IWUSR);
	else if (state->flags & FBO_READ_ONLY)
		state->fd = open(path, O_RDONLY, 0);
	else
		state->fd = open(path, O_RDWR, 0);
	if (state->fd == -1) {
		tcmu_err("could not open %s: %m\n", path);
		goto err;
	}
	tcmu_dbg("FBO Open: fd %d\n", state->fd);

	pthread_mutex_init(&state->state_mtx, NULL);

	/* Record that we've changed our Operational state */
	fbo_report_op_change(dev, 0x02);

	return 0;

err:
	free(state);
	return -EINVAL;
}

static void fbo_close(struct tcmu_device *dev)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);

	pthread_mutex_destroy(&state->state_mtx);
	close(state->fd);
	free(state);
}

static int fbo_emulate_inquiry(uint8_t *cdb, struct iovec *iovec, size_t iov_cnt,
			       uint8_t *sense)
{
	uint8_t	buf[36];

	if ((cdb[1] & 0x01) || cdb[2])
		return TCMU_STS_INVALID_CDB;

	memset(buf, 0, sizeof(buf));

	buf[0] = 0x05;	/* CD/DVD device */
	buf[1] = 0x80;	/* Removable Medium Bit */
	buf[2] = 0x05;	/* SPC-3 */
	buf[3] = 0x02;	/* response data format */
	buf[7] = 0x02;	/* CmdQue */

	memcpy(&buf[8], "LIO-ORG ", 8);
	memset(&buf[16], 0x20, 16);
	memcpy(&buf[16], "VOPTA", 5);
	memcpy(&buf[32], "0001", 4);

	buf[4] = 31;	/* additional length */

	/* TBD: Resid data? */
	tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, sizeof(buf));

	return TCMU_STS_OK;
}

static int fbo_emulate_request_sense(struct tcmu_device *dev, uint8_t *cdb,
				     struct iovec *iovec, size_t iov_cnt,
				     uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint8_t buf[18];

	if (cdb[1] & 0x01)
		return TCMU_STS_INVALID_CDB;

	/* Note that upon successful completion, Request Sense returns the
	 * sense data in the data buffer, not as sense data.
	 */
	memset(buf, 0, sizeof(buf));

	buf[0] = 0x70;
	buf[7] = 0xa;
	if (state->flags & FBO_FORMATTING) {
		buf[2] = NOT_READY;
		buf[12] = 0x04;		// Not Ready
		buf[13] = 0x04;		// Format in progress
		buf[15] = 0x80;
		put_unaligned_be16(state->format_progress, &buf[16]);
	}
	else {
		buf[2] = NO_SENSE;
	}

	tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, sizeof(buf));

	return TCMU_STS_OK;
}

static int fbo_handle_rwerp_page(uint8_t *buf, size_t buf_len,
				 uint8_t page_control)
{
	if (buf_len < 12)
		return -1;

	buf[0] = GPMODE_R_W_ERROR_PAGE;
	buf[1] = 10;
	if (page_control != 1) {
		buf[3] = 5;	/* Read Retry Count */
		buf[8] = 5;	/* Write Retry Count */
	}

	return 12;
}

static int fbo_handle_cache_page(uint8_t *buf, size_t buf_len,
				 uint8_t page_control)
{
	if (buf_len < 12)
		return -1;

	buf[0] = GPMODE_WCACHING_PAGE;
	buf[1] = 10;
	if (page_control != 1) {
		buf[2] = 4;	/* WCE=1 */
	}

	return 12;
}

static int fbo_handle_timeout_page(uint8_t *buf, size_t buf_len,
				   uint8_t page_control)
{
	if (buf_len < 10)
		return -1;

	buf[0] = GPMODE_TO_PROTECT_PAGE;
	buf[1] = 8;
	if (page_control != 1) {
		buf[6] = 0xff;
		buf[7] = 0xff;
		buf[8] = 0xff;
		buf[9] = 0xff;
	}

	return 10;
}

static struct {
	uint8_t page;
	int (*get)(uint8_t *buf, size_t buf_len, uint8_t page_control);
} fbo_modesense_handlers[] = {
	{ GPMODE_R_W_ERROR_PAGE, fbo_handle_rwerp_page },
	{ GPMODE_WCACHING_PAGE, fbo_handle_cache_page },
	{ GPMODE_TO_PROTECT_PAGE, fbo_handle_timeout_page },
};

static int fbo_emulate_mode_sense(uint8_t *cdb, struct iovec *iovec,
				  size_t iov_cnt, uint8_t *sense)
{
	bool sense_ten = (cdb[0] == MODE_SENSE_10);
	uint8_t page_control = (cdb[2] >> 6) & 0x3;
	uint8_t page_code = cdb[2] & 0x3F;
	bool return_pages = true;
	size_t alloc_len = tcmu_cdb_get_xfer_length(cdb);
	int i;
	int ret;
	int used_len;
	uint8_t buf[512];
	bool got_sense = false;

	/* We don't support saved pages */
	if (page_control == 3)
		return TCMU_STS_NOTSUPP_SAVE_PARAMS;

	memset(buf, 0, sizeof(buf));

	if ((page_code == 0) && (page_control == 0))
		return_pages = false;

	/* Mode parameter header length */
	used_len = sense_ten ? 8 : 4;

	if (return_pages) {
		if (page_code == 0x3f) {
			got_sense = true;
			for (i = 0; i < ARRAY_SIZE(fbo_modesense_handlers);
			     i++) {
				ret = fbo_modesense_handlers[i].get(&buf[used_len],
								    sizeof(buf) - used_len,
								    page_control);
				if (ret <= 0)
					break;

				if (used_len + ret >= alloc_len)
					break;

				used_len += ret;
			}
		}
		else {
			for (i = 0; i < ARRAY_SIZE(fbo_modesense_handlers);
			     i++) {
				if (page_code == fbo_modesense_handlers[i].page) {
					ret = fbo_modesense_handlers[i].get(&buf[used_len],
									    sizeof(buf) - used_len,
									    page_control);
					if (ret <= 0)
						break;

					used_len += ret;
					got_sense = true;
					break;
				}
			}
		}

		if (!got_sense)
			return TCMU_STS_INVALID_CDB;
	}

	if (sense_ten) {
		uint16_t *ptr = (uint16_t *)buf;
		*ptr = htobe16(used_len - 2);
	}
	else {
		buf[0] = used_len - 1;
	}

	tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, used_len);

	return TCMU_STS_OK;
}

static int fbo_emulate_mode_select(uint8_t *cdb, struct iovec *iovec,
				   size_t iov_cnt, uint8_t *sense)
{
	bool select_ten = (cdb[0] == MODE_SELECT_10);
	uint8_t page_code;
	size_t alloc_len = tcmu_cdb_get_xfer_length(cdb);
	int i;
	int ret;
	int used_len;
	uint8_t buf[512];
	uint8_t in_buf[512];
	bool got_sense;

	/* Abort if !PF or SP */
	if (!(cdb[1] & 0x10) || (cdb[1] & 0x01))
		return TCMU_STS_INVALID_CDB;

	if (alloc_len > sizeof(in_buf))
		return TCMU_STS_INVALID_PARAM_LIST_LEN;

	memset(buf, 0, sizeof(buf));

	if (tcmu_memcpy_from_iovec(in_buf, sizeof(in_buf), iovec, iov_cnt) !=
	    alloc_len)
		return TCMU_STS_INVALID_PARAM_LIST_LEN;

	/* Mode parameter header length */
	used_len = select_ten ? 8 : 4;

	while (alloc_len > used_len) {
		got_sense = false;
		page_code = in_buf[used_len];
		for (i = 0; i < ARRAY_SIZE(fbo_modesense_handlers); i++) {
			if (page_code == fbo_modesense_handlers[i].page) {
				ret = fbo_modesense_handlers[i].get(&buf[used_len],
								    sizeof(buf) - used_len,
								    0);
				if (ret <= 0)
					return TCMU_STS_INVALID_CDB;

				if (used_len + ret > alloc_len)
					return TCMU_STS_INVALID_PARAM_LIST_LEN;

				got_sense = true;
				break;
			}
		}

		if (!got_sense)
			return TCMU_STS_INVALID_PARAM_LIST;

		/* We don't support changing anything, so data must match */
		if (memcmp(&buf[used_len], &in_buf[used_len], ret))
			return TCMU_STS_INVALID_PARAM_LIST;

		used_len += ret;
	}

	return TCMU_STS_OK;
}

static int fbo_emulate_allow_medium_removal(struct tcmu_device *dev,
					    uint8_t *cdb, uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);

	pthread_mutex_lock(&state->state_mtx);
	/* We're ignoring the persistent prevent bit */
	if (cdb[4] & 0x01)
		state->flags |= FBO_PREV_EJECT;
	else
		state->flags &= ~FBO_PREV_EJECT;
	pthread_mutex_unlock(&state->state_mtx);

	return TCMU_STS_OK;
}

static int fbo_emulate_read_toc(struct tcmu_device *dev, uint8_t *cdb,
				struct iovec *iovec, size_t iov_cnt,
				uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint8_t time_bit = cdb[1] & 0x02;
	uint8_t format = cdb[2] & 0x0f;
	uint8_t buf[512];

	memset(buf, 0, sizeof(buf));

	// TBD: If we simulate start/stop, then fail if stopped
	switch (format) {
	case 0x00:	// Formatted TOC
		buf[1] = 0x12;			// TOC Data Length
		buf[2] = 1;			// First Track
		buf[3] = 1;			// Last Track
		buf[5] = 0x14;			// ADR + CONTROL
		buf[6] = 1;			// Track #
		if (time_bit)
			buf[10] = CDROM_MSF;	// 00:00:02:00
		if (state->flags & FBO_READ_ONLY)
			buf[13] = 0x14;		// ADR + CONTROL
		else
			buf[13] = 0x17;		// ADR + CONTROL
		buf[14] = CDROM_LEADOUT;	// Track #
		if (time_bit) {
			/* Max time address is 00:FF:3B:4A */
			if (state->num_lbas >=
			    (0xff * CD_SECS + CD_SECS - 1) * CD_FRAMES +
			    CD_FRAMES - 1 - CD_MSF_OFFSET) {
				buf[17] = 0xff;
				buf[18] = CD_SECS - 1;
				buf[19] = CD_FRAMES - 1;
			}
			else {
				buf[17] = (state->num_lbas / CD_FRAMES + 2) /
					CD_SECS;
				buf[18] = (state->num_lbas / CD_FRAMES + 2) %
					CD_SECS;
				buf[19] = state->num_lbas % CD_FRAMES;
			}
		}
		else {
			put_unaligned_be32(state->num_lbas, &buf[16]);
		}

		tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, 0x14);
		break;

	case 0x01:	// Multi-Session Information
		buf[1] = 0xa;			// TOC Data Length
		buf[2] = 1;			// First Complete Session
		buf[3] = 1;			// Last Complete Session
		buf[5] = 0x14;			// ADR + CONTROL
		buf[6] = 1;			// Track #
		if (time_bit)
			buf[10] = 2;		// 00:00:02:00

		tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, 0xc);
		break;

	default:
		return TCMU_STS_INVALID_CDB;
	}

	return TCMU_STS_OK;
}

static int fbo_emulate_get_configuration(struct tcmu_device *dev, uint8_t *cdb,
					 struct iovec *iovec, size_t iov_cnt,
					 uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint8_t rt = cdb[1] & 0x03;
	uint16_t start = be16toh(*(uint16_t *)&cdb[2]);
	int used_len;
	uint8_t *cur;
	uint8_t buf[512];

	/* Reserved value for RT */
	if (rt == 3)
		return TCMU_STS_INVALID_CDB;

	memset(buf, 0, sizeof(buf));

	/* Set current profile in the feature header */
	if (state->flags & FBO_READ_ONLY)
		put_unaligned_be16(0x10, &buf[6]); //DVD-ROM
	else
		put_unaligned_be16(0x12, &buf[6]); //DVD-ROM

	/* Feature header */
	used_len = 8;

	/* Profile List Feature */
	if ((rt == 2 && start == 0x0000) ||
	    (start <= 0x0000 && (rt == 0 || rt == 1))) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x0000);
		cur[2] = 3;		// persistent=1, current=1
		cur[3] = 16;		// # of profile descriptors * 4

		*(uint16_t *)&cur[4] = htobe16(0x12);	// DVD-RAM Profile
		if (!(state->flags & FBO_READ_ONLY))
			cur[6] = 1;			// active profile

		*(uint16_t *)&cur[8] = htobe16(0x10);	// DVD-ROM Profile
		if (state->flags & FBO_READ_ONLY)
			cur[10] = 1;			// active profile

		*(uint16_t *)&cur[12] = htobe16(0x0a);	// CD-RW Profile

		*(uint16_t *)&cur[16] = htobe16(0x08);	// CD-ROM Profile

		used_len += 20;
	}

	/* Core Feature */
	if ((rt == 2 && start == 0x0001) ||
	    (start <= 0x0001 && (rt == 0 || rt == 1))) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x0001);
		cur[2] = 7;	// version=1, persistent=1, current=1
		cur[3] = 8;	// additional length

		/* SCSI Physical Interface Standard */
		*(uint32_t *)&cur[4] = htobe32(1);
		cur[8] = 1;	// Device Busy Event

		used_len += 12;
	}

	/* Morphing Feature */
	if ((rt == 2 && start == 0x0002) ||
	    (start <= 0x0002 && (rt == 0 || rt == 1))) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x0002);
		cur[2] = 7;	// version=1, persistent=1, current=1
		cur[3] = 4;	// additional length

		cur[4] = 2;	// OCEvent = 1, ASYNC = 0

		used_len += 8;
	}

	/* Removable Medium Feature */
	if ((rt == 2 && start == 0x0003) ||
	    (start <= 0x0003 && (rt == 0 || rt == 1))) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x0003);
		cur[2] = 3;	// persistent=1, current=1
		cur[3] = 4;	// additional length

		cur[4] = 0x20;	// mechanism=1, eject=0, pvnt jmpr=0, lock=0

		used_len += 8;
	}

	/* Random Readable Feature */
	if ((rt == 2 && start == 0x0010) ||
	    (start <= 0x0010 && (rt == 0 || rt == 1))) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x0010);
		cur[2] = 1;	// version=0, persistent=0, current=1
		cur[3] = 8;	// additional length

		*(uint32_t *)&cur[4] = htobe32(state->block_size);
		*(uint16_t *)&cur[8] = htobe16(0x10);	// Blocking
		cur[10] = 0x01;	// PP=1

		used_len += 12;
	}

	/* Multi Read Feature - never current */
	if ((rt == 2 && start == 0x001d) ||
	    (start <= 0x001d && rt == 0)) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x001d);
		cur[2] = 0;	// version=0, persistent=0, current=0
		cur[3] = 0;	// additional length

		used_len += 4;
	}

	/* CD Read Feature - never current */
	if ((rt == 2 && start == 0x001e) ||
	    (start <= 0x001e && rt == 0)) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x001e);
		cur[2] = 8;	// version=2, persistent=0, current=0
		cur[3] = 4;	// additional length

		used_len += 8;
	}

	/* DVD Read Feature */
	if ((rt == 2 && start == 0x001f) ||
	    (start <= 0x001f && (rt == 0 || rt == 1))) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x001f);
		cur[2] = 1;	// version=0, persistent=0, current=1
		cur[3] = 0;	// additional length

		used_len += 4;
	}

	/* Random Writable Feature */
	if ((rt == 2 && start == CDF_RWRT) ||
	    (start <= CDF_RWRT &&
	     (rt == 0 || (rt == 1 && !(state->flags & FBO_READ_ONLY))))) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(CDF_RWRT);
		if (state->flags & FBO_READ_ONLY)
			cur[2] = 4;	// version=1, persistent=0, current=0
		else
			cur[2] = 5;	// version=1, persistent=0, current=1
		cur[3] = 0xc;		// additional length

		*(uint32_t *)&cur[4] = htobe32(state->num_lbas - 1);
		*(uint32_t *)&cur[8] = htobe32(state->block_size);
		*(uint16_t *)&cur[12] = htobe16(0x10);	// Blocking
		cur[14] = 0x01;		// PP=1

		used_len += 16;
	}

	/* Incremental Streaming Writable Feature - never current */
	if ((rt == 2 && start == 0x0021) ||
	    (start <= 0x0021 && rt == 0)) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x0021);
		cur[2] = 4;	// version=1, persistent=0, current=0
		cur[3] = 4;	// additional length

		used_len += 8;
	}

	/* Formattable Feature */
	if ((rt == 2 && start == 0x0023) ||
	    (start <= 0x0023 &&
	     (rt == 0 || (rt == 1 && !(state->flags & FBO_READ_ONLY))))) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x0023);
		if (state->flags & FBO_READ_ONLY)
			cur[2] = 0;	// version=0, persistent=0, current=0
		else
			cur[2] = 1;	// version=0, persistent=0, current=1
		cur[3] = 0;	// additional length

		used_len += 4;
	}

	/* Hardware Defect Management Feature */
	if ((rt == 2 && start == CDF_HWDM) ||
	    (start <= CDF_HWDM &&
	     (rt == 0 || (rt == 1 && !(state->flags & FBO_READ_ONLY))))) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(CDF_HWDM);
		if (state->flags & FBO_READ_ONLY)
			cur[2] = 4;	// version=1, persistent=0, current=0
		else
			cur[2] = 5;	// version=1, persistent=0, current=1
		cur[3] = 4;	// additional length

		used_len += 8;
	}

	/* Restricted Overwrite Feature - never current */
	if ((rt == 2 && start == 0x0026) ||
	    (start <= 0x0026 && rt == 0)) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x0026);
		cur[2] = 0;	// version=0, persistent=0, current=0
		cur[3] = 0;	// additional length

		used_len += 4;
	}

	/* CD Track at Once Feature - never current */
	if ((rt == 2 && start == 0x002d) ||
	    (start <= 0x002d && rt == 0)) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x002d);
		cur[2] = 8;	// version=2, persistent=0, current=0
		cur[3] = 4;	// additional length

		used_len += 8;
	}

	/* Timeout Feature */
	if ((rt == 2 && start == 0x0105) ||
	    (start <= 0x0105 && (rt == 0 || rt == 1))) {
		cur = &buf[used_len];
		*(uint16_t *)&cur[0] = htobe16(0x0105);
		cur[2] = 5;	// version=1, persistent=0, current=1
		cur[3] = 4;	// additional length

		used_len += 8;
	}

	put_unaligned_be32(used_len - 4, &buf[0]);

	tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, used_len);

	return TCMU_STS_OK;
}

static int fbo_emulate_get_event_status_notification(struct tcmu_device *dev,
						     uint8_t *cdb,
						     struct iovec *iovec,
						     size_t iov_cnt,
						     uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint8_t class_req = cdb[4];
	uint16_t alloc_len = tcmu_cdb_get_xfer_length(cdb);
	int used_len;
	uint8_t buf[8];

	if (!(cdb[1] & 0x01))
		/* We don't support asynchronous operation */
		return TCMU_STS_INVALID_CDB;

	memset(buf, 0, sizeof(buf));

	buf[3] = 0x42;	// Operational Change and Device Busy

	if (!(class_req & 0x42)) {
		/* We don't support any requested notification classes */
		buf[2] = 0x80;
		used_len = 4;
		goto done;
	}

	pthread_mutex_lock(&state->state_mtx);
	if (class_req & 0x02 &&
	    (state->event_op_ch_code || !(class_req & 0x40) ||
	     !(state->flags & FBO_BUSY_EVENT)))
	{
		/* We're reporting an Operational Change event */
		buf[2] = 1;
		if (alloc_len > 4)
		{
			buf[4] = state->event_op_ch_code;
			if (state->event_op_ch_code)
				put_unaligned_be16(0x0001, &buf[6]);
			state->event_op_ch_code = 0;
			used_len = 8;
		}
		else {
			/* Only return the header */
			used_len = 4;
		}
	}
	else {
		/* We're reporting a Device Busy event */
		buf[2] = 6;
		if (alloc_len > 4)
		{
			if (state->flags & FBO_BUSY_EVENT) {
				/* A Busy Event has occurred */
				buf[4] = 1;
				state->flags &= ~FBO_BUSY_EVENT;
			}
			if ((state->flags & FBO_FORMAT_IMMED) ||
			    state->async_cache_count)
				/* Our current state is "busy" */
				buf[5] = 1;
			used_len = 8;
		}
		else {
			/* Only return the header */
			used_len = 4;
		}
	}
	pthread_mutex_unlock(&state->state_mtx);

done:
	put_unaligned_be16(used_len - 4, &buf[0]);

	tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, used_len);

	return TCMU_STS_OK;
}

static int fbo_emulate_read_disc_information(struct tcmu_device *dev,
                                             uint8_t *cdb, struct iovec *iovec,
                                             size_t iov_cnt, uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint8_t buf[34];

	memset(buf, 0, sizeof(buf));

	buf[1] = 32;            // Disc Information Length
	if (state->flags & FBO_READ_ONLY)
		buf[2] = 0x0E;	// Era=0, Last=3, Status=2
	else
		buf[2] = 0x1F;	// Era=1, Last=3, Status=3
	buf[3] = 1;             // First Track
	buf[4] = 1;		// One session
	buf[5] = 1;		// First Track (LSB)
	buf[6] = 1;		// Last Track (LSB)
	buf[7] = 0x20;		// URU=1

	tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, 34);

	return TCMU_STS_OK;
}

static int fbo_emulate_read_dvd_structure(struct tcmu_device *dev, uint8_t *cdb,
					  struct iovec *iovec, size_t iov_cnt,
					  uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint8_t format = cdb[7];
	uint32_t start_phys;
	uint32_t end_phys;
	char manuf_info[] = "VIRTUAL FB OPT";
	uint8_t buf[512];

	// TBD: If we simulate start/stop, then fail if stopped and format != 0xff
	/* Fail anything other than layer 0 */
	if (cdb[6])
		return TCMU_STS_INVALID_CDB;

	memset(buf, 0, sizeof(buf));

	switch (format) {
	case DVD_STRUCT_PHYSICAL:
		if (state->flags & FBO_READ_ONLY)
			start_phys = 0x30000;
		else
			start_phys = 0x31000;
		buf[1] = 19;			//DVD Structure Data Length
		if (state->flags & FBO_READ_ONLY)
			buf[4] = 0x02;		// Book Type=0, Part Version=2
		else
			buf[4] = 0x12;		// Book Type=1, Part Version=2
		buf[5] = 0x0f;			// Maximum Rate=Not specified
		if (!(state->flags & FBO_READ_ONLY))
			buf[6] = 0x02;		// Layer Type=2
		buf[9] = start_phys >> 16;	// Starting physical sector
		put_unaligned_be16(start_phys & 0xffff, &buf[10]);
		end_phys = start_phys + state->num_lbas - 1;
		buf[13] = end_phys >> 16;
		put_unaligned_be16(end_phys & 0xffff, &buf[14]);

		tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, 21);
		break;

	case DVD_STRUCT_COPYRIGHT:
		buf[1] = 6;	// DVD Structure Data Length

		tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, 8);
		break;

	case DVD_STRUCT_MANUFACT:
		buf[2] = strlen(manuf_info) + 2;	// DVD Struct Data Len
		memcpy(&buf[4], manuf_info, strlen(manuf_info));

		tcmu_memcpy_into_iovec(iovec, iov_cnt, buf,
				       strlen(manuf_info) + 4);
		break;

	case 0x09:	// DVD-RAM Medium Status (Cartridge Info)
		if (state->flags & FBO_READ_ONLY)
			return TCMU_STS_INVALID_CDB;

		buf[1] = 6;	// DVD Structure Data Length
		buf[5] = 0x10;	// Disc Type Identification

		tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, 8);
		break;

	case 0xff:	// Read DVD Structure Capabilities List
		buf[1] = 18;	// DVD Structure Data Length

		buf[4] = 0x00;	// Format Field
		buf[5] = 0x40;	// SDS=0, RDS=1
		buf[7] = 21;	// Structure Length

		buf[8] = 0x01;	// Format Field
		buf[9] = 0x40;	// SDS=0, RDS=1
		buf[11] = 8;	// Structure Length

		buf[12] = 0x04;	// Format Field
		buf[13] = 0x40;	// SDS=0, RDS=1
		buf[15] = strlen(manuf_info) + 4;	// Structure Length

		buf[16] = 0x09;	// Format Field
		buf[17] = 0x40;	// SDS=0, RDS=1
		buf[19] = 8;	// Structure Length

		tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, 20);
		break;

	default:
		return TCMU_STS_INVALID_CDB;
	}

	return TCMU_STS_OK;
}

static int fbo_emulate_mechanism_status(struct tcmu_device *dev, uint8_t *cdb,
					struct iovec *iovec, size_t iov_cnt,
					uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint8_t buf[8];

	memset(buf, 0, sizeof(buf));

	if (state->flags & FBO_DEV_IO) {
		buf[1] = 0x20;	// mechanism state=1 (playing)
		buf[2] = (state->cur_lba >> 16) & 0xff;
		put_unaligned_be16(state->cur_lba & 0xffff, &buf[3]);
	}

	tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, 8);

	return TCMU_STS_OK;
}

static int fbo_do_sync(struct fbo_state *state, uint8_t *sense)
{
	int rc;

	rc = fsync(state->fd);
	if (rc) {
		tcmu_err("sync failed: %m\n");
		return TCMU_STS_WR_ERR;
	}

	return TCMU_STS_OK;
}

static void *fbo_async_sync_cache(void *arg)
{
	struct tcmu_device *dev = (struct tcmu_device *)arg;
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint8_t sense[SENSE_BUFFERSIZE];

	tcmu_set_thread_name("fbo-cache", dev);

	pthread_mutex_lock(&state->state_mtx);
	state->async_cache_count++;
	state->flags |= FBO_BUSY_EVENT;
	pthread_mutex_unlock(&state->state_mtx);

	/* We don't do deferred sense data, so ignore errors */
	(void)fbo_do_sync(state, sense);

	pthread_mutex_lock(&state->state_mtx);
	state->async_cache_count--;
	/* A Busy Event also applies when we go from "busy" to "not busy" */
	state->flags |= FBO_BUSY_EVENT;
	pthread_mutex_unlock(&state->state_mtx);

	return NULL;
}

static int fbo_synchronize_cache(struct tcmu_device *dev, uint8_t *cdb,
				 uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	pthread_t thr;

	// TBD: If we simulate start/stop, then fail if stopped
	/* Reserved bit */
	if (cdb[1] & 0x01)
		return TCMU_STS_INVALID_CDB;

	if (cdb[1] & 0x02) {
		/* Immediate Bit set */
		pthread_create(&thr, NULL, fbo_async_sync_cache, dev);

		return TCMU_STS_OK;
	}

	return fbo_do_sync(state, sense);
}

static int fbo_check_lba_and_length(struct fbo_state *state, uint8_t *cdb,
				    uint8_t *sense, uint64_t *plba, int *plen)
{
	uint64_t lba;
	uint32_t num_blocks;

	lba = tcmu_cdb_get_lba(cdb);
	num_blocks = tcmu_cdb_get_xfer_length(cdb);

	if (lba >= state->num_lbas || lba + num_blocks > state->num_lbas)
		return TCMU_STS_RANGE;

	*plba = lba;
	*plen = num_blocks * state->block_size;

	return TCMU_STS_OK;
}

static int fbo_read(struct tcmu_device *dev, uint8_t *cdb, struct iovec *iovec,
		    size_t iov_cnt, uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint8_t fua = cdb[1] & 0x08;
	uint64_t cur_lba = 0;
	uint64_t offset;
	int length = 0;
	int remaining;
	ssize_t ret;
	int rc;

	// TBD: If we simulate start/stop, then fail if stopped
	/* DPO and RelAdr bits should be 0 */
	if (cdb[0] != READ_6 && cdb[1] & 0x11)
		return TCMU_STS_INVALID_CDB;

	rc = fbo_check_lba_and_length(state, cdb, sense, &cur_lba, &length);
	if (rc)
		return rc;

	offset = state->block_size * cur_lba;

	if (fua) {
		rc = fsync(state->fd);
		if (rc) {
			tcmu_err("sync failed: %m\n");
			return TCMU_STS_RD_ERR;
		}
	}

	pthread_mutex_lock(&state->state_mtx);
	state->cur_lba = cur_lba;
	state->flags |= FBO_DEV_IO;
	pthread_mutex_unlock(&state->state_mtx);

	remaining = length;

	while (remaining) {
		ret = preadv(state->fd, iovec, iov_cnt, offset);
		if (ret < 0) {
			tcmu_err("read failed: %m\n");
			rc = TCMU_STS_RD_ERR;
			break;
		}
		tcmu_iovec_seek(iovec, ret);
		offset += ret;
		remaining -= ret;
	}

	pthread_mutex_lock(&state->state_mtx);
	state->flags &= ~FBO_DEV_IO;
	pthread_mutex_unlock(&state->state_mtx);

	return TCMU_STS_OK;
}

static void fbo_cleanup_buffer(void *buf)
{
	free(buf);
}

static int fbo_do_verify(struct fbo_state *state, struct iovec *iovec,
			 size_t iov_cnt, uint64_t offset, int length,
			 uint8_t *sense)
{
	ssize_t ret;
	uint32_t cmp_offset;
	void *buf;
	int rc = TCMU_STS_OK;
	int remaining;

	buf = malloc(length);
	if (!buf)
		return TCMU_STS_NO_RESOURCE;

	pthread_cleanup_push(fbo_cleanup_buffer, buf);
	memset(buf, 0, length);

	pthread_mutex_lock(&state->state_mtx);
	state->cur_lba = offset / state->block_size;
	state->flags |= FBO_DEV_IO;
	pthread_mutex_unlock(&state->state_mtx);

	remaining = length;

	while (remaining) {
		ret = pread(state->fd, buf, remaining, offset);
		if (ret < 0) {
			tcmu_err("read failed: %m\n");
			rc = TCMU_STS_RD_ERR;
			break;
		}

		cmp_offset = tcmu_iovec_compare(buf, iovec, ret);
		if (cmp_offset != -1) {
			rc = TCMU_STS_MISCOMPARE;
			tcmu_sense_set_info(sense, cmp_offset);
			break;
		}
		tcmu_iovec_seek(iovec, ret);

		offset += ret;
		remaining -= ret;
	}

	pthread_mutex_lock(&state->state_mtx);
	state->flags &= ~FBO_DEV_IO;
	pthread_mutex_unlock(&state->state_mtx);

	free(buf);
	pthread_cleanup_pop(0);

	return rc;
}

static int fbo_write(struct tcmu_device *dev, uint8_t *cdb, struct iovec *iovec,
		     size_t iov_cnt, uint8_t *sense, bool do_verify)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	struct iovec write_iovec[iov_cnt];
	uint8_t fua = cdb[1] & 0x08;
	uint64_t cur_lba = 0;
	uint64_t offset;
	int length = 0;
	int remaining;
	ssize_t ret;
	int rc = TCMU_STS_OK;
	int rc1;

	// TBD: If we simulate start/stop, then fail if stopped
	if (state->flags & FBO_READ_ONLY)
		return TCMU_STS_WR_ERR_INCOMPAT_FRMT;

	rc = fbo_check_lba_and_length(state, cdb, sense, &cur_lba, &length);
	if (rc != TCMU_STS_OK)
		return rc;

	offset = state->block_size * cur_lba;

	pthread_mutex_lock(&state->state_mtx);
	state->cur_lba = cur_lba;
	state->flags |= FBO_DEV_IO;
	pthread_mutex_unlock(&state->state_mtx);

	remaining = length;

	memcpy(write_iovec, iovec, sizeof(write_iovec));

	while (remaining) {
		ret = pwritev(state->fd, write_iovec, iov_cnt, offset);
		if (ret < 0) {
			tcmu_err("write failed: %m\n");
			rc = TCMU_STS_WR_ERR;
			break;
		}
		tcmu_iovec_seek(write_iovec, ret);
		offset += ret;
		remaining -= ret;
	}

	if (rc == TCMU_STS_OK && (do_verify || fua)) {
		rc1 = fsync(state->fd);
		if (rc1) {
			tcmu_err("sync failed: %m\n");
			rc = TCMU_STS_WR_ERR;
		}
	}

	pthread_mutex_lock(&state->state_mtx);
	state->flags &= ~FBO_DEV_IO;
	pthread_mutex_unlock(&state->state_mtx);

	if (!do_verify || rc != TCMU_STS_OK)
		return rc;

	offset = state->block_size * cur_lba;
	return fbo_do_verify(state, iovec, iov_cnt, offset, length, sense);
}

static int fbo_verify(struct tcmu_device *dev, uint8_t *cdb,
		      struct iovec *iovec, size_t iov_cnt, uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint64_t cur_lba = 0;
	uint64_t offset;
	int length = 0;
	int rc;

	// TBD: If we simulate start/stop, then fail if stopped
	if (state->flags & FBO_READ_ONLY)
		return TCMU_STS_WR_ERR_INCOMPAT_FRMT;

	/* All of these bits are reserved for MM logical units */
	if (cdb[1] & 0x13)
		return TCMU_STS_INVALID_CDB;

	rc = fbo_check_lba_and_length(state, cdb, sense, &cur_lba, &length);
	if (rc)
		return rc;

	offset = state->block_size * cur_lba;

	return fbo_do_verify(state, iovec, iov_cnt, offset, length, sense);
}

static int fbo_do_format(struct tcmu_device *dev, uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint32_t done_blocks = 0;
	uint64_t offset = 0;
	uint8_t *buf;
	unsigned int length = 1024 * 1024;
	ssize_t ret;
	int rc = TCMU_STS_OK;

	buf = malloc(length);
	if (!buf) {
		tcmu_dbg("  malloc failed\n");
		return TCMU_STS_NO_RESOURCE;
	}

	pthread_cleanup_push(fbo_cleanup_buffer, buf);
	memset(buf, 0, length);

	while (done_blocks < state->num_lbas) {
		if ((state->num_lbas - done_blocks) * state->block_size <
		    length)
			length = (state->num_lbas - done_blocks) *
				state->block_size;
		ret = pwrite(state->fd, buf, length, offset);
		if (ret == -1) {
			tcmu_err("Could not write: %m\n");
			rc = TCMU_STS_WR_ERR;
			break;
		}
		done_blocks += length / state->block_size;
		offset += length;
		if (done_blocks < state->num_lbas)
			state->format_progress = (0x10000 * done_blocks) /
				state->num_lbas;
	}

	pthread_mutex_lock(&state->state_mtx);
	state->flags &= ~FBO_FORMATTING;
	pthread_mutex_unlock(&state->state_mtx);

	free(buf);
	pthread_cleanup_pop(0);

	return rc;
}

static void *fbo_async_format(void *arg)
{
	struct tcmu_device *dev = (struct tcmu_device *)arg;
	struct fbo_state *state = tcmur_dev_get_private(dev);
	uint8_t sense[SENSE_BUFFERSIZE];

	tcmu_set_thread_name("fbo-format", dev);

	pthread_mutex_lock(&state->state_mtx);
	state->flags |= FBO_BUSY_EVENT | FBO_FORMAT_IMMED;
	pthread_mutex_unlock(&state->state_mtx);

	/* We don't do deferred sense data, so ignore errors */
	(void)fbo_do_format(dev, sense);

	pthread_mutex_lock(&state->state_mtx);
	state->flags &= ~FBO_FORMAT_IMMED;
	/* A Busy Event also applies when we go from "busy" to "not busy" */
	state->flags |= FBO_BUSY_EVENT;
	pthread_mutex_unlock(&state->state_mtx);

	return NULL;
}

static int fbo_emulate_format_unit(struct tcmu_device *dev, uint8_t *cdb,
				   struct iovec *iovec, size_t iov_cnt,
				   uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	pthread_t thr;
	uint8_t param_list[12];

	// TBD: If we simulate start/stop, then fail if stopped
	if (state->flags & FBO_READ_ONLY)
		return TCMU_STS_WR_ERR_INCOMPAT_FRMT;

	if (!(cdb[1] & 0x10) || ((cdb[1] & 0x07) != 1) || cdb[3] || cdb[4])
		return TCMU_STS_INVALID_CDB;

	if (tcmu_memcpy_from_iovec(param_list, 12, iovec, iov_cnt) < 12)
		return TCMU_STS_INVALID_PARAM_LIST_LEN;

	if (!(param_list[1] & 0x80) && (param_list[1] & 0x7c))
		/* Options Valid not set but option bits set */
		return TCMU_STS_INVALID_PARAM_LIST;

	if (param_list[1] & 0x1c)
		/* We don't support these options */
		return TCMU_STS_INVALID_PARAM_LIST;

	if (get_unaligned_be16(&param_list[2]) != 8)
		return TCMU_STS_INVALID_PARAM_LIST;

	if (param_list[8])
		/* We only support Format Type 0 */
		return TCMU_STS_INVALID_PARAM_LIST;

	if ((cdb[1] & 0x08 || !(param_list[1] & 0x20)) &&
	    get_unaligned_be16(&param_list[4])  != state->num_lbas)
		/* Number of Blocks doesn't match */
		return TCMU_STS_INVALID_PARAM_LIST;

	if ((((uint32_t)param_list[9] << 16) +
	     get_unaligned_be16(&param_list[10])) != state->block_size)
		/* Block Size is wrong */
		return TCMU_STS_INVALID_PARAM_LIST;

	pthread_mutex_lock(&state->state_mtx);
	/* Note that while our caller already checked this flag, the
	 * check was made outside the lock.  We need to check it again
	 * now that we have the lock.
	 */
	if (state->flags & FBO_FORMATTING) {
		pthread_mutex_unlock(&state->state_mtx);
		tcmu_sense_set_key_specific_info(sense, state->format_progress);
		return TCMU_STS_FRMT_IN_PROGRESS;
	}
	state->format_progress = 0;
	state->flags |= FBO_FORMATTING;
	pthread_mutex_unlock(&state->state_mtx);

	if (param_list[1] & 0x02) {
		/* Immediate Bit set */
		pthread_create(&thr, NULL, fbo_async_format, dev);

		return TCMU_STS_OK;
	}

	return fbo_do_format(dev, sense);
}

static int fbo_emulate_read_format_capacities(struct tcmu_device *dev,
					      uint8_t *cdb, struct iovec *iovec,
					      size_t iov_cnt, uint8_t *sense)
{
	struct fbo_state *state = tcmur_dev_get_private(dev);
	int used_len;
	uint8_t buf[20];

	memset(buf, 0, sizeof(buf));

	put_unaligned_be32(state->num_lbas, &buf[4]);
	buf[8] = 0x02;
	buf[9] = (state->block_size >> 16) & 0xff;
	put_unaligned_be16(state->block_size & 0xffff, &buf[10]);
	if (state->flags & FBO_READ_ONLY) {
		used_len = 12;
	}
	else {
		memcpy(&buf[12], &buf[4], 8);
		buf[16] = 0;
		used_len = 20;
	}
	buf[3] = used_len - 4;

	tcmu_memcpy_into_iovec(iovec, iov_cnt, buf, used_len);

	return TCMU_STS_OK;
}

/*
 * Return scsi status or TCMU_STS_NOT_HANDLED
 */
static int fbo_handle_cmd(struct tcmu_device *dev, struct tcmur_cmd *tcmur_cmd)
{
	struct tcmulib_cmd *cmd = tcmur_cmd->lib_cmd;
	uint8_t *cdb = cmd->cdb;
	struct iovec *iovec = cmd->iovec;
	size_t iov_cnt = cmd->iov_cnt;
	uint8_t *sense = cmd->sense_buf;
	struct fbo_state *state = tcmur_dev_get_private(dev);
	bool do_verify = false;
	int ret;

	/* Check for format in progress */
	/* Certain commands can be executed even if a format is in progress */
	if (state->flags & FBO_FORMATTING &&
	    cdb[0] != INQUIRY &&
	    cdb[0] != REQUEST_SENSE &&
	    cdb[0] != GET_CONFIGURATION &&
	    cdb[0] != GPCMD_GET_EVENT_STATUS_NOTIFICATION) {
		tcmu_sense_set_key_specific_info(sense, state->format_progress);
		ret = TCMU_STS_FRMT_IN_PROGRESS;
		return ret;
	}

	switch(cdb[0]) {
	case TEST_UNIT_READY:
		ret = tcmu_emulate_test_unit_ready(cdb, iovec, iov_cnt);
		break;
	case REQUEST_SENSE:
		ret = fbo_emulate_request_sense(dev, cdb, iovec, iov_cnt, sense);
		break;
	case FORMAT_UNIT:
		ret = fbo_emulate_format_unit(dev, cdb, iovec, iov_cnt, sense);
		break;
	case READ_6:
	case READ_10:
	case READ_12:
		ret = fbo_read(dev, cdb, iovec, iov_cnt, sense);
		break;
	case WRITE_VERIFY:
		do_verify = true;
	case WRITE_6:
	case WRITE_10:
	case WRITE_12:
		ret = fbo_write(dev, cdb, iovec, iov_cnt, sense, do_verify);
		break;
	case INQUIRY:
		ret = fbo_emulate_inquiry(cdb, iovec, iov_cnt, sense);
		break;
	case MODE_SELECT:
	case MODE_SELECT_10:
		ret = fbo_emulate_mode_select(cdb, iovec, iov_cnt, sense);
		break;
	case MODE_SENSE:
	case MODE_SENSE_10:
		ret = fbo_emulate_mode_sense(cdb, iovec, iov_cnt, sense);
		break;
	case START_STOP:
		ret = tcmu_emulate_start_stop(dev, cdb);
		break;
	case ALLOW_MEDIUM_REMOVAL:
		ret = fbo_emulate_allow_medium_removal(dev, cdb, sense);
		break;
	case READ_FORMAT_CAPACITIES:
		ret = fbo_emulate_read_format_capacities(dev, cdb, iovec,
							 iov_cnt, sense);
		break;
	case READ_CAPACITY:
		if ((cdb[1] & 0x01) || (cdb[8] & 0x01))
			/* Reserved bits for MM logical units */
			return TCMU_STS_INVALID_CDB;
		else
			ret = tcmu_emulate_read_capacity_10(state->num_lbas,
							    state->block_size,
							    cdb, iovec,
							    iov_cnt);
		break;
	case VERIFY:
		ret = fbo_verify(dev, cdb, iovec, iov_cnt, sense);
		break;
	case SYNCHRONIZE_CACHE:
		ret = fbo_synchronize_cache(dev, cdb, sense);
		break;
	case READ_TOC:
		ret = fbo_emulate_read_toc(dev, cdb, iovec, iov_cnt, sense);
		break;
	case GET_CONFIGURATION:
		ret = fbo_emulate_get_configuration(dev, cdb, iovec, iov_cnt,
						    sense);
		break;
	case GPCMD_GET_EVENT_STATUS_NOTIFICATION:
		ret = fbo_emulate_get_event_status_notification(dev, cdb,
								iovec, iov_cnt,
								sense);
		break;
	case READ_DISC_INFORMATION:
		ret = fbo_emulate_read_disc_information(dev, cdb, iovec,
							iov_cnt, sense);
		break;
	case READ_DVD_STRUCTURE:
		ret = fbo_emulate_read_dvd_structure(dev, cdb, iovec, iov_cnt,
						     sense);
		break;
	case MECHANISM_STATUS:
		ret = fbo_emulate_mechanism_status(dev, cdb, iovec, iov_cnt,
						   sense);
		break;
	default:
		ret = TCMU_STS_NOT_HANDLED;
	}

	return ret;
}

static const char fbo_cfg_desc[] =
	"The path to the file to use as a backstore.";

static struct tcmur_handler fbo_handler = {
	.cfg_desc = fbo_cfg_desc,

	.open = fbo_open,
	.close = fbo_close,
	.name = "File-backed optical Handler",
	.subtype = "fbo",
	.handle_cmd = fbo_handle_cmd,
	.nr_threads = 1,
};

/* Entry point must be named "handler_init". */
int handler_init(void)
{
	return tcmur_register_handler(&fbo_handler);
}

