[PATCH 13/17]: SCST vdisk dev handler

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch contains SCST vdisk dev handler. This dev handler allows
to create virtual disks and CDROMs from files on file system.

Signed-off-by: Vladislav Bolkhovitin <vst@xxxxxxxx>
---
 scst_vdisk.c | 3966 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 3966 insertions(+)

diff -uprN orig/linux-2.6.35/drivers/scst/dev_handlers/scst_vdisk.c linux-2.6.35/drivers/scst/dev_handlers/scst_vdisk.c
--- orig/linux-2.6.35/drivers/scst/dev_handlers/scst_vdisk.c
+++ linux-2.6.35/drivers/scst/dev_handlers/scst_vdisk.c
@@ -0,0 +1,3966 @@
+/*
+ *  scst_vdisk.c
+ *
+ *  Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <vst@xxxxxxxx>
+ *  Copyright (C) 2004 - 2005 Leonid Stoljar
+ *  Copyright (C) 2007 Ming Zhang <blackmagic02881 at gmail dot com>
+ *  Copyright (C) 2007 Ross Walker <rswwalker at hotmail dot com>
+ *  Copyright (C) 2007 - 2010 ID7 Ltd.
+ *
+ *  SCSI disk (type 0) and CDROM (type 5) dev handler using files
+ *  on file systems or block devices (VDISK)
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU General Public License
+ *  as published by the Free Software Foundation, version 2
+ *  of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ */
+
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/unistd.h>
+#include <linux/smp_lock.h>
+#include <linux/spinlock.h>
+#include <linux/init.h>
+#include <linux/uio.h>
+#include <linux/list.h>
+#include <linux/ctype.h>
+#include <linux/writeback.h>
+#include <linux/vmalloc.h>
+#include <asm/atomic.h>
+#include <linux/kthread.h>
+#include <linux/sched.h>
+#include <linux/version.h>
+#include <asm/div64.h>
+#include <asm/unaligned.h>
+#include <linux/slab.h>
+#include <linux/bio.h>
+
+#define LOG_PREFIX			"dev_vdisk"
+
+#include <scst/scst.h>
+
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+
+#define TRACE_ORDER	0x80000000
+
+static struct scst_trace_log vdisk_local_trace_tbl[] = {
+    { TRACE_ORDER,		"order" },
+    { 0,			NULL }
+};
+#define trace_log_tbl			vdisk_local_trace_tbl
+
+#define VDISK_TRACE_TLB_HELP	", order"
+
+#endif
+
+#include "scst_dev_handler.h"
+
+/* 8 byte ASCII Vendor */
+#define SCST_FIO_VENDOR			"SCST_FIO"
+#define SCST_BIO_VENDOR			"SCST_BIO"
+/* 4 byte ASCII Product Revision Level - left aligned */
+#define SCST_FIO_REV			" 210"
+
+#define MAX_USN_LEN			(20+1) /* For '\0' */
+
+#define INQ_BUF_SZ			128
+#define EVPD				0x01
+#define CMDDT				0x02
+
+#define MSENSE_BUF_SZ			256
+#define DBD				0x08	/* disable block descriptor */
+#define WP				0x80	/* write protect */
+#define DPOFUA				0x10	/* DPOFUA bit */
+#define WCE				0x04	/* write cache enable */
+
+#define PF				0x10	/* page format */
+#define SP				0x01	/* save pages */
+#define PS				0x80	/* parameter saveable */
+
+#define	BYTE				8
+#define	DEF_DISK_BLOCKSIZE_SHIFT	9
+#define	DEF_DISK_BLOCKSIZE		(1 << DEF_DISK_BLOCKSIZE_SHIFT)
+#define	DEF_CDROM_BLOCKSIZE_SHIFT	11
+#define	DEF_CDROM_BLOCKSIZE		(1 << DEF_CDROM_BLOCKSIZE_SHIFT)
+#define	DEF_SECTORS			56
+#define	DEF_HEADS			255
+#define LEN_MEM				(32 * 1024)
+#define DEF_RD_ONLY			0
+#define DEF_WRITE_THROUGH		0
+#define DEF_NV_CACHE			0
+#define DEF_O_DIRECT			0
+#define DEF_REMOVABLE			0
+
+#define VDISK_NULLIO_SIZE		(3LL*1024*1024*1024*1024/2)
+
+#define DEF_TST				SCST_CONTR_MODE_SEP_TASK_SETS
+/*
+ * Since we can't control backstorage device's reordering, we have to always
+ * report unrestricted reordering.
+ */
+#define DEF_QUEUE_ALG_WT	SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER
+#define DEF_QUEUE_ALG		SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER
+#define DEF_SWP			0
+#define DEF_TAS			0
+
+#define DEF_DSENSE		SCST_CONTR_MODE_FIXED_SENSE
+
+static unsigned int random_values[256] = {
+	    9862592UL,  3744545211UL,  2348289082UL,  4036111983UL,
+	  435574201UL,  3110343764UL,  2383055570UL,  1826499182UL,
+	 4076766377UL,  1549935812UL,  3696752161UL,  1200276050UL,
+	 3878162706UL,  1783530428UL,  2291072214UL,   125807985UL,
+	 3407668966UL,   547437109UL,  3961389597UL,   969093968UL,
+	   56006179UL,  2591023451UL,     1849465UL,  1614540336UL,
+	 3699757935UL,   479961779UL,  3768703953UL,  2529621525UL,
+	 4157893312UL,  3673555386UL,  4091110867UL,  2193909423UL,
+	 2800464448UL,  3052113233UL,   450394455UL,  3424338713UL,
+	 2113709130UL,  4082064373UL,  3708640918UL,  3841182218UL,
+	 3141803315UL,  1032476030UL,  1166423150UL,  1169646901UL,
+	 2686611738UL,   575517645UL,  2829331065UL,  1351103339UL,
+	 2856560215UL,  2402488288UL,   867847666UL,     8524618UL,
+	  704790297UL,  2228765657UL,   231508411UL,  1425523814UL,
+	 2146764591UL,  1287631730UL,  4142687914UL,  3879884598UL,
+	  729945311UL,   310596427UL,  2263511876UL,  1983091134UL,
+	 3500916580UL,  1642490324UL,  3858376049UL,   695342182UL,
+	  780528366UL,  1372613640UL,  1100993200UL,  1314818946UL,
+	  572029783UL,  3775573540UL,   776262915UL,  2684520905UL,
+	 1007252738UL,  3505856396UL,  1974886670UL,  3115856627UL,
+	 4194842288UL,  2135793908UL,  3566210707UL,     7929775UL,
+	 1321130213UL,  2627281746UL,  3587067247UL,  2025159890UL,
+	 2587032000UL,  3098513342UL,  3289360258UL,   130594898UL,
+	 2258149812UL,  2275857755UL,  3966929942UL,  1521739999UL,
+	 4191192765UL,   958953550UL,  4153558347UL,  1011030335UL,
+	  524382185UL,  4099757640UL,   498828115UL,  2396978754UL,
+	  328688935UL,   826399828UL,  3174103611UL,  3921966365UL,
+	 2187456284UL,  2631406787UL,  3930669674UL,  4282803915UL,
+	 1776755417UL,   374959755UL,  2483763076UL,   844956392UL,
+	 2209187588UL,  3647277868UL,   291047860UL,  3485867047UL,
+	 2223103546UL,  2526736133UL,  3153407604UL,  3828961796UL,
+	 3355731910UL,  2322269798UL,  2752144379UL,   519897942UL,
+	 3430536488UL,  1801511593UL,  1953975728UL,  3286944283UL,
+	 1511612621UL,  1050133852UL,   409321604UL,  1037601109UL,
+	 3352316843UL,  4198371381UL,   617863284UL,   994672213UL,
+	 1540735436UL,  2337363549UL,  1242368492UL,   665473059UL,
+	 2330728163UL,  3443103219UL,  2291025133UL,  3420108120UL,
+	 2663305280UL,  1608969839UL,  2278959931UL,  1389747794UL,
+	 2226946970UL,  2131266900UL,  3856979144UL,  1894169043UL,
+	 2692697628UL,  3797290626UL,  3248126844UL,  3922786277UL,
+	  343705271UL,  3739749888UL,  2191310783UL,  2962488787UL,
+	 4119364141UL,  1403351302UL,  2984008923UL,  3822407178UL,
+	 1932139782UL,  2323869332UL,  2793574182UL,  1852626483UL,
+	 2722460269UL,  1136097522UL,  1005121083UL,  1805201184UL,
+	 2212824936UL,  2979547931UL,  4133075915UL,  2585731003UL,
+	 2431626071UL,   134370235UL,  3763236829UL,  1171434827UL,
+	 2251806994UL,  1289341038UL,  3616320525UL,   392218563UL,
+	 1544502546UL,  2993937212UL,  1957503701UL,  3579140080UL,
+	 4270846116UL,  2030149142UL,  1792286022UL,   366604999UL,
+	 2625579499UL,   790898158UL,   770833822UL,   815540197UL,
+	 2747711781UL,  3570468835UL,  3976195842UL,  1257621341UL,
+	 1198342980UL,  1860626190UL,  3247856686UL,   351473955UL,
+	  993440563UL,   340807146UL,  1041994520UL,  3573925241UL,
+	  480246395UL,  2104806831UL,  1020782793UL,  3362132583UL,
+	 2272911358UL,  3440096248UL,  2356596804UL,   259492703UL,
+	 3899500740UL,   252071876UL,  2177024041UL,  4284810959UL,
+	 2775999888UL,  2653420445UL,  2876046047UL,  1025771859UL,
+	 1994475651UL,  3564987377UL,  4112956647UL,  1821511719UL,
+	 3113447247UL,   455315102UL,  1585273189UL,  2311494568UL,
+	  774051541UL,  1898115372UL,  2637499516UL,   247231365UL,
+	 1475014417UL,   803585727UL,  3911097303UL,  1714292230UL,
+	  476579326UL,  2496900974UL,  3397613314UL,   341202244UL,
+	  807790202UL,  4221326173UL,   499979741UL,  1301488547UL,
+	 1056807896UL,  3525009458UL,  1174811641UL,  3049738746UL,
+};
+
+struct scst_vdisk_dev {
+	uint32_t block_size;
+	uint64_t nblocks;
+	int block_shift;
+	loff_t file_size;	/* in bytes */
+
+	/*
+	 * This lock can be taken on both SIRQ and thread context, but in
+	 * all cases for each particular instance it's taken consistenly either
+	 * on SIRQ or thread context. Mix of them is forbidden.
+	 */
+	spinlock_t flags_lock;
+
+	/*
+	 * Below flags are protected by flags_lock or suspended activity
+	 * with scst_vdisk_mutex.
+	 */
+	unsigned int rd_only:1;
+	unsigned int wt_flag:1;
+	unsigned int nv_cache:1;
+	unsigned int o_direct_flag:1;
+	unsigned int media_changed:1;
+	unsigned int prevent_allow_medium_removal:1;
+	unsigned int nullio:1;
+	unsigned int blockio:1;
+	unsigned int cdrom_empty:1;
+	unsigned int removable:1;
+
+	int virt_id;
+	char name[16+1];	/* Name of the virtual device,
+				   must be <= SCSI Model + 1 */
+	char *filename;		/* File name, protected by
+				   scst_mutex and suspended activities */
+	uint16_t command_set_version;
+	unsigned int t10_dev_id_set:1; /* true if t10_dev_id manually set */
+	char t10_dev_id[16+8+2]; /* T10 device ID */
+	char usn[MAX_USN_LEN];
+	struct scst_device *dev;
+	struct list_head vdev_list_entry;
+
+	struct scst_dev_type *vdev_devt;
+};
+
+struct scst_vdisk_thr {
+	struct scst_thr_data_hdr hdr;
+	struct file *fd;
+	struct block_device *bdev;
+	struct iovec *iv;
+	int iv_count;
+};
+
+/* Context RA patch supposed to be applied on the kernel */
+#define DEF_NUM_THREADS		8
+static int num_threads = DEF_NUM_THREADS;
+
+module_param_named(num_threads, num_threads, int, S_IRUGO);
+MODULE_PARM_DESC(num_threads, "vdisk threads count");
+
+static int vdisk_attach(struct scst_device *dev);
+static void vdisk_detach(struct scst_device *dev);
+static int vdisk_attach_tgt(struct scst_tgt_dev *tgt_dev);
+static void vdisk_detach_tgt(struct scst_tgt_dev *tgt_dev);
+static int vdisk_parse(struct scst_cmd *);
+static int vdisk_do_job(struct scst_cmd *cmd);
+static int vcdrom_parse(struct scst_cmd *);
+static int vcdrom_exec(struct scst_cmd *cmd);
+static void vdisk_exec_read(struct scst_cmd *cmd,
+	struct scst_vdisk_thr *thr, loff_t loff);
+static void vdisk_exec_write(struct scst_cmd *cmd,
+	struct scst_vdisk_thr *thr, loff_t loff);
+static void blockio_exec_rw(struct scst_cmd *cmd, struct scst_vdisk_thr *thr,
+	u64 lba_start, int write);
+static int blockio_flush(struct block_device *bdev);
+static void vdisk_exec_verify(struct scst_cmd *cmd,
+	struct scst_vdisk_thr *thr, loff_t loff);
+static void vdisk_exec_read_capacity(struct scst_cmd *cmd);
+static void vdisk_exec_read_capacity16(struct scst_cmd *cmd);
+static void vdisk_exec_inquiry(struct scst_cmd *cmd);
+static void vdisk_exec_request_sense(struct scst_cmd *cmd);
+static void vdisk_exec_mode_sense(struct scst_cmd *cmd);
+static void vdisk_exec_mode_select(struct scst_cmd *cmd);
+static void vdisk_exec_log(struct scst_cmd *cmd);
+static void vdisk_exec_read_toc(struct scst_cmd *cmd);
+static void vdisk_exec_prevent_allow_medium_removal(struct scst_cmd *cmd);
+static int vdisk_fsync(struct scst_vdisk_thr *thr, loff_t loff,
+	loff_t len, struct scst_cmd *cmd, struct scst_device *dev);
+static ssize_t vdisk_add_fileio_device(const char *device_name, char *params);
+static ssize_t vdisk_add_blockio_device(const char *device_name, char *params);
+static ssize_t vdisk_add_nullio_device(const char *device_name, char *params);
+static ssize_t vdisk_del_device(const char *device_name);
+static ssize_t vcdrom_add_device(const char *device_name, char *params);
+static ssize_t vcdrom_del_device(const char *device_name);
+static int vdisk_task_mgmt_fn(struct scst_mgmt_cmd *mcmd,
+	struct scst_tgt_dev *tgt_dev);
+static uint64_t vdisk_gen_dev_id_num(const char *virt_dev_name);
+
+/** SYSFS **/
+
+static ssize_t vdev_sysfs_size_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+static ssize_t vdisk_sysfs_blocksize_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+static ssize_t vdisk_sysfs_rd_only_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+static ssize_t vdisk_sysfs_wt_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+static ssize_t vdisk_sysfs_o_direct_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+static ssize_t vdisk_sysfs_removable_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+static ssize_t vdev_sysfs_filename_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+static ssize_t vdisk_sysfs_resync_size_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count);
+static ssize_t vdev_sysfs_t10_dev_id_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count);
+static ssize_t vdev_sysfs_t10_dev_id_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+static ssize_t vdev_sysfs_usn_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf);
+
+static ssize_t vcdrom_sysfs_filename_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count);
+
+static struct kobj_attribute vdev_size_attr =
+	__ATTR(size_mb, S_IRUGO, vdev_sysfs_size_show, NULL);
+static struct kobj_attribute vdisk_blocksize_attr =
+	__ATTR(blocksize, S_IRUGO, vdisk_sysfs_blocksize_show, NULL);
+static struct kobj_attribute vdisk_rd_only_attr =
+	__ATTR(read_only, S_IRUGO, vdisk_sysfs_rd_only_show, NULL);
+static struct kobj_attribute vdisk_wt_attr =
+	__ATTR(write_through, S_IRUGO, vdisk_sysfs_wt_show, NULL);
+static struct kobj_attribute vdisk_nv_cache_attr =
+	__ATTR(nv_cache, S_IRUGO, vdisk_sysfs_nv_cache_show, NULL);
+static struct kobj_attribute vdisk_o_direct_attr =
+	__ATTR(o_direct, S_IRUGO, vdisk_sysfs_o_direct_show, NULL);
+static struct kobj_attribute vdisk_removable_attr =
+	__ATTR(removable, S_IRUGO, vdisk_sysfs_removable_show, NULL);
+static struct kobj_attribute vdisk_filename_attr =
+	__ATTR(filename, S_IRUGO, vdev_sysfs_filename_show, NULL);
+static struct kobj_attribute vdisk_resync_size_attr =
+	__ATTR(resync_size, S_IWUSR, NULL, vdisk_sysfs_resync_size_store);
+static struct kobj_attribute vdev_t10_dev_id_attr =
+	__ATTR(t10_dev_id, S_IWUSR|S_IRUGO, vdev_sysfs_t10_dev_id_show,
+		vdev_sysfs_t10_dev_id_store);
+static struct kobj_attribute vdev_usn_attr =
+	__ATTR(usn, S_IRUGO, vdev_sysfs_usn_show, NULL);
+
+static struct kobj_attribute vcdrom_filename_attr =
+	__ATTR(filename, S_IRUGO|S_IWUSR, vdev_sysfs_filename_show,
+		vcdrom_sysfs_filename_store);
+
+static const struct attribute *vdisk_fileio_attrs[] = {
+	&vdev_size_attr.attr,
+	&vdisk_blocksize_attr.attr,
+	&vdisk_rd_only_attr.attr,
+	&vdisk_wt_attr.attr,
+	&vdisk_nv_cache_attr.attr,
+	&vdisk_o_direct_attr.attr,
+	&vdisk_removable_attr.attr,
+	&vdisk_filename_attr.attr,
+	&vdisk_resync_size_attr.attr,
+	&vdev_t10_dev_id_attr.attr,
+	&vdev_usn_attr.attr,
+	NULL,
+};
+
+static const struct attribute *vdisk_blockio_attrs[] = {
+	&vdev_size_attr.attr,
+	&vdisk_blocksize_attr.attr,
+	&vdisk_rd_only_attr.attr,
+	&vdisk_nv_cache_attr.attr,
+	&vdisk_removable_attr.attr,
+	&vdisk_filename_attr.attr,
+	&vdisk_resync_size_attr.attr,
+	&vdev_t10_dev_id_attr.attr,
+	&vdev_usn_attr.attr,
+	NULL,
+};
+
+static const struct attribute *vdisk_nullio_attrs[] = {
+	&vdev_size_attr.attr,
+	&vdisk_blocksize_attr.attr,
+	&vdisk_rd_only_attr.attr,
+	&vdisk_removable_attr.attr,
+	&vdev_t10_dev_id_attr.attr,
+	&vdev_usn_attr.attr,
+	NULL,
+};
+
+static const struct attribute *vcdrom_attrs[] = {
+	&vdev_size_attr.attr,
+	&vcdrom_filename_attr.attr,
+	&vdev_t10_dev_id_attr.attr,
+	&vdev_usn_attr.attr,
+	NULL,
+};
+
+/* Protects vdisks addition/deletion and related activities, like search */
+static DEFINE_MUTEX(scst_vdisk_mutex);
+static DEFINE_RWLOCK(vdisk_t10_dev_id_rwlock);
+
+/* Protected by scst_vdisk_mutex */
+static LIST_HEAD(vdev_list);
+
+static struct kmem_cache *vdisk_thr_cachep;
+
+/*
+ * Be careful changing "name" field, since it is the name of the corresponding
+ * /sys/kernel/scst_tgt entry, hence a part of user space ABI.
+ */
+
+static struct scst_dev_type vdisk_file_devtype = {
+	.name =			"vdisk_fileio",
+	.type =			TYPE_DISK,
+	.exec_sync =		1,
+	.threads_num =		-1,
+	.parse_atomic =		1,
+	.dev_done_atomic =	1,
+	.attach =		vdisk_attach,
+	.detach =		vdisk_detach,
+	.attach_tgt =		vdisk_attach_tgt,
+	.detach_tgt =		vdisk_detach_tgt,
+	.parse =		vdisk_parse,
+	.exec =			vdisk_do_job,
+	.task_mgmt_fn =		vdisk_task_mgmt_fn,
+	.add_device =		vdisk_add_fileio_device,
+	.del_device =		vdisk_del_device,
+	.dev_attrs =		vdisk_fileio_attrs,
+	.add_device_parameters = "filename, blocksize, write_through, "
+		"nv_cache, o_direct, read_only, removable",
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+	.default_trace_flags =	SCST_DEFAULT_DEV_LOG_FLAGS,
+	.trace_flags =		&trace_flag,
+	.trace_tbl =		vdisk_local_trace_tbl,
+	.trace_tbl_help =	VDISK_TRACE_TLB_HELP,
+#endif
+};
+
+static struct kmem_cache *blockio_work_cachep;
+
+static struct scst_dev_type vdisk_blk_devtype = {
+	.name =			"vdisk_blockio",
+	.type =			TYPE_DISK,
+	.threads_num =		1,
+	.parse_atomic =		1,
+	.dev_done_atomic =	1,
+	.attach =		vdisk_attach,
+	.detach =		vdisk_detach,
+	.attach_tgt =		vdisk_attach_tgt,
+	.detach_tgt =		vdisk_detach_tgt,
+	.parse =		vdisk_parse,
+	.exec =			vdisk_do_job,
+	.task_mgmt_fn =		vdisk_task_mgmt_fn,
+	.add_device =		vdisk_add_blockio_device,
+	.del_device =		vdisk_del_device,
+	.dev_attrs =		vdisk_blockio_attrs,
+	.add_device_parameters = "filename, blocksize, nv_cache, read_only, "
+		"removable",
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+	.default_trace_flags =	SCST_DEFAULT_DEV_LOG_FLAGS,
+	.trace_flags =		&trace_flag,
+	.trace_tbl =		vdisk_local_trace_tbl,
+	.trace_tbl_help =	VDISK_TRACE_TLB_HELP,
+#endif
+};
+
+static struct scst_dev_type vdisk_null_devtype = {
+	.name =			"vdisk_nullio",
+	.type =			TYPE_DISK,
+	.threads_num =		0,
+	.parse_atomic =		1,
+	.dev_done_atomic =	1,
+	.attach =		vdisk_attach,
+	.detach =		vdisk_detach,
+	.attach_tgt =		vdisk_attach_tgt,
+	.detach_tgt =		vdisk_detach_tgt,
+	.parse =		vdisk_parse,
+	.exec =			vdisk_do_job,
+	.task_mgmt_fn =		vdisk_task_mgmt_fn,
+	.add_device =		vdisk_add_nullio_device,
+	.del_device =		vdisk_del_device,
+	.dev_attrs =		vdisk_nullio_attrs,
+	.add_device_parameters = "blocksize, read_only, removable",
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+	.default_trace_flags =	SCST_DEFAULT_DEV_LOG_FLAGS,
+	.trace_flags =		&trace_flag,
+	.trace_tbl =		vdisk_local_trace_tbl,
+	.trace_tbl_help =	VDISK_TRACE_TLB_HELP,
+#endif
+};
+
+static struct scst_dev_type vcdrom_devtype = {
+	.name =			"vcdrom",
+	.type =			TYPE_ROM,
+	.exec_sync =		1,
+	.threads_num =		-1,
+	.parse_atomic =		1,
+	.dev_done_atomic =	1,
+	.attach =		vdisk_attach,
+	.detach =		vdisk_detach,
+	.attach_tgt =		vdisk_attach_tgt,
+	.detach_tgt =		vdisk_detach_tgt,
+	.parse =		vcdrom_parse,
+	.exec =			vcdrom_exec,
+	.task_mgmt_fn =		vdisk_task_mgmt_fn,
+	.add_device =		vcdrom_add_device,
+	.del_device =		vcdrom_del_device,
+	.dev_attrs =		vcdrom_attrs,
+	.add_device_parameters = NULL,
+#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING)
+	.default_trace_flags =	SCST_DEFAULT_DEV_LOG_FLAGS,
+	.trace_flags =		&trace_flag,
+	.trace_tbl =		vdisk_local_trace_tbl,
+	.trace_tbl_help =	VDISK_TRACE_TLB_HELP,
+#endif
+};
+
+static struct scst_vdisk_thr nullio_thr_data;
+
+static const char *vdev_get_filename(const struct scst_vdisk_dev *virt_dev)
+{
+	if (virt_dev->filename != NULL)
+		return virt_dev->filename;
+	else
+		return "none";
+}
+
+/* Returns fd, use IS_ERR(fd) to get error status */
+static struct file *vdev_open_fd(const struct scst_vdisk_dev *virt_dev)
+{
+	int open_flags = 0;
+	struct file *fd;
+
+	if (virt_dev->dev->rd_only)
+		open_flags |= O_RDONLY;
+	else
+		open_flags |= O_RDWR;
+	if (virt_dev->o_direct_flag)
+		open_flags |= O_DIRECT;
+	if (virt_dev->wt_flag && !virt_dev->nv_cache)
+		open_flags |= O_SYNC;
+	TRACE_DBG("Opening file %s, flags 0x%x",
+		  virt_dev->filename, open_flags);
+	fd = filp_open(virt_dev->filename, O_LARGEFILE | open_flags, 0600);
+	return fd;
+}
+
+static void vdisk_blockio_check_flush_support(struct scst_vdisk_dev *virt_dev)
+{
+	struct inode *inode;
+	struct file *fd;
+
+	if (!virt_dev->blockio || virt_dev->rd_only || virt_dev->nv_cache)
+		goto out;
+
+	fd = filp_open(virt_dev->filename, O_LARGEFILE, 0600);
+	if (IS_ERR(fd)) {
+		PRINT_ERROR("filp_open(%s) returned error %ld",
+			virt_dev->filename, PTR_ERR(fd));
+		goto out;
+	}
+
+	inode = fd->f_dentry->d_inode;
+
+	if (!S_ISBLK(inode->i_mode)) {
+		PRINT_ERROR("%s is NOT a block device", virt_dev->filename);
+		goto out_close;
+	}
+
+	if (blockio_flush(inode->i_bdev) != 0) {
+		PRINT_WARNING("Device %s doesn't support barriers, switching "
+			"to NV_CACHE mode. Read README for more details.",
+			virt_dev->filename);
+		virt_dev->nv_cache = 1;
+	}
+
+out_close:
+	filp_close(fd, NULL);
+
+out:
+	return;
+}
+
+/* Returns 0 on success and file size in *file_size, error code otherwise */
+static int vdisk_get_file_size(const char *filename, bool blockio,
+	loff_t *file_size)
+{
+	struct inode *inode;
+	int res = 0;
+	struct file *fd;
+
+	*file_size = 0;
+
+	fd = filp_open(filename, O_LARGEFILE | O_RDONLY, 0600);
+	if (IS_ERR(fd)) {
+		res = PTR_ERR(fd);
+		PRINT_ERROR("filp_open(%s) returned error %d", filename, res);
+		goto out;
+	}
+
+	inode = fd->f_dentry->d_inode;
+
+	if (blockio && !S_ISBLK(inode->i_mode)) {
+		PRINT_ERROR("File %s is NOT a block device", filename);
+		res = -EINVAL;
+		goto out_close;
+	}
+
+	if (S_ISREG(inode->i_mode))
+		/* Nothing to do */;
+	else if (S_ISBLK(inode->i_mode))
+		inode = inode->i_bdev->bd_inode;
+	else {
+		res = -EINVAL;
+		goto out_close;
+	}
+
+	*file_size = inode->i_size;
+
+out_close:
+	filp_close(fd, NULL);
+
+out:
+	return res;
+}
+
+static int vdisk_attach(struct scst_device *dev)
+{
+	int res = 0;
+	loff_t err;
+	struct scst_vdisk_dev *virt_dev = NULL, *vv;
+
+	TRACE_DBG("virt_id %d (%s)", dev->virt_id, dev->virt_name);
+
+	if (dev->virt_id == 0) {
+		PRINT_ERROR("%s", "Not a virtual device");
+		res = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * scst_vdisk_mutex must be already taken before
+	 * scst_register_virtual_device()
+	 */
+	list_for_each_entry(vv, &vdev_list, vdev_list_entry) {
+		if (strcmp(vv->name, dev->virt_name) == 0) {
+			virt_dev = vv;
+			break;
+		}
+	}
+
+	if (virt_dev == NULL) {
+		PRINT_ERROR("Device %s not found", dev->virt_name);
+		res = -EINVAL;
+		goto out;
+	}
+
+	virt_dev->dev = dev;
+
+	dev->rd_only = virt_dev->rd_only;
+
+	if (!virt_dev->cdrom_empty) {
+		if (virt_dev->nullio)
+			err = VDISK_NULLIO_SIZE;
+		else {
+			res = vdisk_get_file_size(virt_dev->filename,
+				virt_dev->blockio, &err);
+			if (res != 0)
+				goto out;
+		}
+		virt_dev->file_size = err;
+
+		TRACE_DBG("size of file: %lld", (long long unsigned int)err);
+
+		vdisk_blockio_check_flush_support(virt_dev);
+	} else
+		virt_dev->file_size = 0;
+
+	virt_dev->nblocks = virt_dev->file_size >> virt_dev->block_shift;
+
+	if (!virt_dev->cdrom_empty) {
+		PRINT_INFO("Attached SCSI target virtual %s %s "
+		      "(file=\"%s\", fs=%lldMB, bs=%d, nblocks=%lld,"
+		      " cyln=%lld%s)",
+		      (dev->type == TYPE_DISK) ? "disk" : "cdrom",
+		      virt_dev->name, vdev_get_filename(virt_dev),
+		      virt_dev->file_size >> 20, virt_dev->block_size,
+		      (long long unsigned int)virt_dev->nblocks,
+		      (long long unsigned int)virt_dev->nblocks/64/32,
+		      virt_dev->nblocks < 64*32
+		      ? " !WARNING! cyln less than 1" : "");
+	} else {
+		PRINT_INFO("Attached empty SCSI target virtual cdrom %s",
+			virt_dev->name);
+	}
+
+	dev->dh_priv = virt_dev;
+
+	dev->tst = DEF_TST;
+	dev->d_sense = DEF_DSENSE;
+	if (virt_dev->wt_flag && !virt_dev->nv_cache)
+		dev->queue_alg = DEF_QUEUE_ALG_WT;
+	else
+		dev->queue_alg = DEF_QUEUE_ALG;
+	dev->swp = DEF_SWP;
+	dev->tas = DEF_TAS;
+
+out:
+	return res;
+}
+
+/* scst_mutex supposed to be held */
+static void vdisk_detach(struct scst_device *dev)
+{
+	struct scst_vdisk_dev *virt_dev =
+	    (struct scst_vdisk_dev *)dev->dh_priv;
+
+	TRACE_DBG("virt_id %d", dev->virt_id);
+
+	PRINT_INFO("Detached virtual device %s (\"%s\")",
+		      virt_dev->name, vdev_get_filename(virt_dev));
+
+	/* virt_dev will be freed by the caller */
+	dev->dh_priv = NULL;
+	return;
+}
+
+static void vdisk_free_thr_data(struct scst_thr_data_hdr *d)
+{
+	struct scst_vdisk_thr *thr =
+		container_of(d, struct scst_vdisk_thr, hdr);
+
+	if (thr->fd)
+		filp_close(thr->fd, NULL);
+
+	kfree(thr->iv);
+
+	kmem_cache_free(vdisk_thr_cachep, thr);
+	return;
+}
+
+static struct scst_vdisk_thr *vdisk_init_thr_data(
+	struct scst_tgt_dev *tgt_dev)
+{
+	struct scst_vdisk_thr *res;
+	struct scst_vdisk_dev *virt_dev =
+	    (struct scst_vdisk_dev *)tgt_dev->dev->dh_priv;
+
+	EXTRACHECKS_BUG_ON(virt_dev->nullio);
+
+	res = kmem_cache_zalloc(vdisk_thr_cachep, GFP_KERNEL);
+	if (res == NULL) {
+		TRACE(TRACE_OUT_OF_MEM, "%s", "Unable to allocate struct "
+			"scst_vdisk_thr");
+		goto out;
+	}
+
+	if (!virt_dev->cdrom_empty) {
+		res->fd = vdev_open_fd(virt_dev);
+		if (IS_ERR(res->fd)) {
+			PRINT_ERROR("filp_open(%s) returned an error %ld",
+				virt_dev->filename, PTR_ERR(res->fd));
+			goto out_free;
+		}
+		if (virt_dev->blockio)
+			res->bdev = res->fd->f_dentry->d_inode->i_bdev;
+		else
+			res->bdev = NULL;
+	} else
+		res->fd = NULL;
+
+	scst_add_thr_data(tgt_dev, &res->hdr, vdisk_free_thr_data);
+
+out:
+	return res;
+
+out_free:
+	kmem_cache_free(vdisk_thr_cachep, res);
+	res = NULL;
+	goto out;
+}
+
+static int vdisk_attach_tgt(struct scst_tgt_dev *tgt_dev)
+{
+	int res = 0;
+
+	/* Nothing to do */
+	return res;
+}
+
+static void vdisk_detach_tgt(struct scst_tgt_dev *tgt_dev)
+{
+
+	scst_del_all_thr_data(tgt_dev);
+	return;
+}
+
+static int vdisk_do_job(struct scst_cmd *cmd)
+{
+	int rc, res;
+	uint64_t lba_start = 0;
+	loff_t data_len = 0;
+	uint8_t *cdb = cmd->cdb;
+	int opcode = cdb[0];
+	loff_t loff;
+	struct scst_device *dev = cmd->dev;
+	struct scst_tgt_dev *tgt_dev = cmd->tgt_dev;
+	struct scst_vdisk_dev *virt_dev =
+		(struct scst_vdisk_dev *)dev->dh_priv;
+	struct scst_thr_data_hdr *d;
+	struct scst_vdisk_thr *thr = NULL;
+	int fua = 0;
+
+	switch (cmd->queue_type) {
+	case SCST_CMD_QUEUE_ORDERED:
+		TRACE(TRACE_ORDER, "ORDERED cmd %p (op %x)", cmd, cmd->cdb[0]);
+		break;
+	case SCST_CMD_QUEUE_HEAD_OF_QUEUE:
+		TRACE(TRACE_ORDER, "HQ cmd %p (op %x)", cmd, cmd->cdb[0]);
+		break;
+	default:
+		break;
+	}
+
+	rc = scst_check_local_events(cmd);
+	if (unlikely(rc != 0))
+		goto out_done;
+
+	cmd->status = 0;
+	cmd->msg_status = 0;
+	cmd->host_status = DID_OK;
+	cmd->driver_status = 0;
+
+	if (!virt_dev->nullio) {
+		d = scst_find_thr_data(tgt_dev);
+		if (unlikely(d == NULL)) {
+			thr = vdisk_init_thr_data(tgt_dev);
+			if (thr == NULL) {
+				scst_set_busy(cmd);
+				goto out_compl;
+			}
+			scst_thr_data_get(&thr->hdr);
+		} else
+			thr = container_of(d, struct scst_vdisk_thr, hdr);
+	} else {
+		thr = &nullio_thr_data;
+		scst_thr_data_get(&thr->hdr);
+	}
+
+	switch (opcode) {
+	case READ_6:
+	case WRITE_6:
+	case VERIFY_6:
+		lba_start = (((cdb[1] & 0x1f) << (BYTE * 2)) +
+			     (cdb[2] << (BYTE * 1)) +
+			     (cdb[3] << (BYTE * 0)));
+		data_len = cmd->bufflen;
+		break;
+	case READ_10:
+	case READ_12:
+	case WRITE_10:
+	case WRITE_12:
+	case VERIFY:
+	case WRITE_VERIFY:
+	case WRITE_VERIFY_12:
+	case VERIFY_12:
+		lba_start |= ((u64)cdb[2]) << 24;
+		lba_start |= ((u64)cdb[3]) << 16;
+		lba_start |= ((u64)cdb[4]) << 8;
+		lba_start |= ((u64)cdb[5]);
+		data_len = cmd->bufflen;
+		break;
+	case READ_16:
+	case WRITE_16:
+	case WRITE_VERIFY_16:
+	case VERIFY_16:
+		lba_start |= ((u64)cdb[2]) << 56;
+		lba_start |= ((u64)cdb[3]) << 48;
+		lba_start |= ((u64)cdb[4]) << 40;
+		lba_start |= ((u64)cdb[5]) << 32;
+		lba_start |= ((u64)cdb[6]) << 24;
+		lba_start |= ((u64)cdb[7]) << 16;
+		lba_start |= ((u64)cdb[8]) << 8;
+		lba_start |= ((u64)cdb[9]);
+		data_len = cmd->bufflen;
+		break;
+	case SYNCHRONIZE_CACHE:
+		lba_start |= ((u64)cdb[2]) << 24;
+		lba_start |= ((u64)cdb[3]) << 16;
+		lba_start |= ((u64)cdb[4]) << 8;
+		lba_start |= ((u64)cdb[5]);
+		data_len = ((cdb[7] << (BYTE * 1)) + (cdb[8] << (BYTE * 0)))
+				<< virt_dev->block_shift;
+		if (data_len == 0)
+			data_len = virt_dev->file_size -
+				((loff_t)lba_start << virt_dev->block_shift);
+		break;
+	}
+
+	loff = (loff_t)lba_start << virt_dev->block_shift;
+	TRACE_DBG("cmd %p, lba_start %lld, loff %lld, data_len %lld", cmd,
+		  (long long unsigned int)lba_start,
+		  (long long unsigned int)loff,
+		  (long long unsigned int)data_len);
+	if (unlikely(loff < 0) || unlikely(data_len < 0) ||
+	    unlikely((loff + data_len) > virt_dev->file_size)) {
+		PRINT_INFO("Access beyond the end of the device "
+			"(%lld of %lld, len %lld)",
+			   (long long unsigned int)loff,
+			   (long long unsigned int)virt_dev->file_size,
+			   (long long unsigned int)data_len);
+		scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
+					scst_sense_block_out_range_error));
+		goto out_compl;
+	}
+
+	switch (opcode) {
+	case WRITE_10:
+	case WRITE_12:
+	case WRITE_16:
+		fua = (cdb[1] & 0x8);
+		if (fua) {
+			TRACE(TRACE_ORDER, "FUA: loff=%lld, "
+				"data_len=%lld", (long long unsigned int)loff,
+				(long long unsigned int)data_len);
+		}
+		break;
+	}
+
+	switch (opcode) {
+	case READ_6:
+	case READ_10:
+	case READ_12:
+	case READ_16:
+		if (virt_dev->blockio) {
+			blockio_exec_rw(cmd, thr, lba_start, 0);
+			goto out_thr;
+		} else
+			vdisk_exec_read(cmd, thr, loff);
+		break;
+	case WRITE_6:
+	case WRITE_10:
+	case WRITE_12:
+	case WRITE_16:
+	{
+		if (virt_dev->blockio) {
+			blockio_exec_rw(cmd, thr, lba_start, 1);
+			goto out_thr;
+		} else
+			vdisk_exec_write(cmd, thr, loff);
+		/* O_SYNC flag is used for WT devices */
+		if (fua)
+			vdisk_fsync(thr, loff, data_len, cmd, dev);
+		break;
+	}
+	case WRITE_VERIFY:
+	case WRITE_VERIFY_12:
+	case WRITE_VERIFY_16:
+	{
+		/* ToDo: BLOCKIO VERIFY */
+		vdisk_exec_write(cmd, thr, loff);
+		/* O_SYNC flag is used for WT devices */
+		if (scsi_status_is_good(cmd->status))
+			vdisk_exec_verify(cmd, thr, loff);
+		break;
+	}
+	case SYNCHRONIZE_CACHE:
+	{
+		int immed = cdb[1] & 0x2;
+		TRACE(TRACE_ORDER, "SYNCHRONIZE_CACHE: "
+			"loff=%lld, data_len=%lld, immed=%d",
+			(long long unsigned int)loff,
+			(long long unsigned int)data_len, immed);
+		if (immed) {
+			scst_cmd_get(cmd); /* to protect dev */
+			cmd->completed = 1;
+			cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT,
+				SCST_CONTEXT_SAME);
+			vdisk_fsync(thr, loff, data_len, NULL, dev);
+			/* ToDo: vdisk_fsync() error processing */
+			scst_cmd_put(cmd);
+			goto out_thr;
+		} else {
+			vdisk_fsync(thr, loff, data_len, cmd, dev);
+			break;
+		}
+	}
+	case VERIFY_6:
+	case VERIFY:
+	case VERIFY_12:
+	case VERIFY_16:
+		vdisk_exec_verify(cmd, thr, loff);
+		break;
+	case MODE_SENSE:
+	case MODE_SENSE_10:
+		vdisk_exec_mode_sense(cmd);
+		break;
+	case MODE_SELECT:
+	case MODE_SELECT_10:
+		vdisk_exec_mode_select(cmd);
+		break;
+	case LOG_SELECT:
+	case LOG_SENSE:
+		vdisk_exec_log(cmd);
+		break;
+	case ALLOW_MEDIUM_REMOVAL:
+		vdisk_exec_prevent_allow_medium_removal(cmd);
+		break;
+	case READ_TOC:
+		vdisk_exec_read_toc(cmd);
+		break;
+	case START_STOP:
+		vdisk_fsync(thr, 0, virt_dev->file_size, cmd, dev);
+		break;
+	case RESERVE:
+	case RESERVE_10:
+	case RELEASE:
+	case RELEASE_10:
+	case TEST_UNIT_READY:
+		break;
+	case INQUIRY:
+		vdisk_exec_inquiry(cmd);
+		break;
+	case REQUEST_SENSE:
+		vdisk_exec_request_sense(cmd);
+		break;
+	case READ_CAPACITY:
+		vdisk_exec_read_capacity(cmd);
+		break;
+	case SERVICE_ACTION_IN:
+		if ((cmd->cdb[1] & 0x1f) == SAI_READ_CAPACITY_16) {
+			vdisk_exec_read_capacity16(cmd);
+			break;
+		}
+		/* else go through */
+	case REPORT_LUNS:
+	default:
+		TRACE_DBG("Invalid opcode %d", opcode);
+		scst_set_cmd_error(cmd,
+		    SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+	}
+
+out_compl:
+	cmd->completed = 1;
+
+out_done:
+	cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
+
+out_thr:
+	if (likely(thr != NULL))
+		scst_thr_data_put(&thr->hdr);
+
+	res = SCST_EXEC_COMPLETED;
+	return res;
+}
+
+static int vdisk_get_block_shift(struct scst_cmd *cmd)
+{
+	struct scst_vdisk_dev *virt_dev =
+	    (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+	return virt_dev->block_shift;
+}
+
+static int vdisk_parse(struct scst_cmd *cmd)
+{
+	scst_sbc_generic_parse(cmd, vdisk_get_block_shift);
+	return SCST_CMD_STATE_DEFAULT;
+}
+
+static int vcdrom_parse(struct scst_cmd *cmd)
+{
+	scst_cdrom_generic_parse(cmd, vdisk_get_block_shift);
+	return SCST_CMD_STATE_DEFAULT;
+}
+
+static int vcdrom_exec(struct scst_cmd *cmd)
+{
+	int res = SCST_EXEC_COMPLETED;
+	int opcode = cmd->cdb[0];
+	struct scst_vdisk_dev *virt_dev =
+	    (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+
+	cmd->status = 0;
+	cmd->msg_status = 0;
+	cmd->host_status = DID_OK;
+	cmd->driver_status = 0;
+
+	if (virt_dev->cdrom_empty && (opcode != INQUIRY)) {
+		TRACE_DBG("%s", "CDROM empty");
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_not_ready));
+		goto out_done;
+	}
+
+	if (virt_dev->media_changed && scst_is_ua_command(cmd)) {
+		spin_lock(&virt_dev->flags_lock);
+		if (virt_dev->media_changed) {
+			virt_dev->media_changed = 0;
+			TRACE_DBG("%s", "Reporting media changed");
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_medium_changed_UA));
+			spin_unlock(&virt_dev->flags_lock);
+			goto out_done;
+		}
+		spin_unlock(&virt_dev->flags_lock);
+	}
+
+	res = vdisk_do_job(cmd);
+
+out:
+	return res;
+
+out_done:
+	cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME);
+	goto out;
+}
+
+static uint64_t vdisk_gen_dev_id_num(const char *virt_dev_name)
+{
+	unsigned int dev_id_num, i;
+
+	for (dev_id_num = 0, i = 0; i < strlen(virt_dev_name); i++) {
+		unsigned int rv = random_values[(int)(virt_dev_name[i])];
+		/* Do some rotating of the bits */
+		dev_id_num ^= ((rv << i) | (rv >> (32 - i)));
+	}
+
+	return ((uint64_t)scst_get_setup_id() << 32) | dev_id_num;
+}
+
+static void vdisk_exec_inquiry(struct scst_cmd *cmd)
+{
+	int32_t length, i, resp_len = 0;
+	uint8_t *address;
+	uint8_t *buf;
+	struct scst_vdisk_dev *virt_dev =
+	    (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+
+	/* ToDo: Performance Boost:
+	 * 1. remove kzalloc, buf
+	 * 2. do all checks before touching *address
+	 * 3. zero *address
+	 * 4. write directly to *address
+	 */
+
+	buf = kzalloc(INQ_BUF_SZ, GFP_KERNEL);
+	if (buf == NULL) {
+		scst_set_busy(cmd);
+		goto out;
+	}
+
+	length = scst_get_buf_first(cmd, &address);
+	TRACE_DBG("length %d", length);
+	if (unlikely(length <= 0)) {
+		if (length < 0) {
+			PRINT_ERROR("scst_get_buf_first() failed: %d", length);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+		}
+		goto out_free;
+	}
+
+	if (cmd->cdb[1] & CMDDT) {
+		TRACE_DBG("%s", "INQUIRY: CMDDT is unsupported");
+		scst_set_cmd_error(cmd,
+		    SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out_put;
+	}
+
+	buf[0] = cmd->dev->type;      /* type dev */
+	if (virt_dev->removable)
+		buf[1] = 0x80;      /* removable */
+	/* Vital Product */
+	if (cmd->cdb[1] & EVPD) {
+		if (0 == cmd->cdb[2]) {
+			/* supported vital product data pages */
+			buf[3] = 3;
+			buf[4] = 0x0; /* this page */
+			buf[5] = 0x80; /* unit serial number */
+			buf[6] = 0x83; /* device identification */
+			if (virt_dev->dev->type == TYPE_DISK) {
+				buf[3] += 1;
+				buf[7] = 0xB0; /* block limits */
+			}
+			resp_len = buf[3] + 4;
+		} else if (0x80 == cmd->cdb[2]) {
+			/* unit serial number */
+			int usn_len = strlen(virt_dev->usn);
+			buf[1] = 0x80;
+			buf[3] = usn_len;
+			strncpy(&buf[4], virt_dev->usn, usn_len);
+			resp_len = buf[3] + 4;
+		} else if (0x83 == cmd->cdb[2]) {
+			/* device identification */
+			int num = 4;
+
+			buf[1] = 0x83;
+			/* T10 vendor identifier field format (faked) */
+			buf[num + 0] = 0x2;	/* ASCII */
+			buf[num + 1] = 0x1;	/* Vendor ID */
+			if (virt_dev->blockio)
+				memcpy(&buf[num + 4], SCST_BIO_VENDOR, 8);
+			else
+				memcpy(&buf[num + 4], SCST_FIO_VENDOR, 8);
+
+			read_lock_bh(&vdisk_t10_dev_id_rwlock);
+			i = strlen(virt_dev->t10_dev_id);
+			memcpy(&buf[num + 12], virt_dev->t10_dev_id, i);
+			read_unlock_bh(&vdisk_t10_dev_id_rwlock);
+
+			buf[num + 3] = 8 + i;
+			num += buf[num + 3];
+
+			num += 4;
+
+			/*
+			 * Relative target port identifier
+			 */
+			buf[num + 0] = 0x01; /* binary */
+			/* Relative target port id */
+			buf[num + 1] = 0x10 | 0x04;
+
+			put_unaligned(cpu_to_be16(cmd->tgt->rel_tgt_id),
+				(__be16 *)&buf[num + 4 + 2]);
+
+			buf[num + 3] = 4;
+			num += buf[num + 3];
+
+			num += 4;
+
+			/*
+			 * IEEE id
+			 */
+			buf[num + 0] = 0x01; /* binary */
+
+			/* EUI-64 */
+			buf[num + 1] = 0x02;
+			buf[num + 2] = 0x00;
+			buf[num + 3] = 0x08;
+
+			/* IEEE id */
+			buf[num + 4] = virt_dev->t10_dev_id[0];
+			buf[num + 5] = virt_dev->t10_dev_id[1];
+			buf[num + 6] = virt_dev->t10_dev_id[2];
+
+			/* IEEE ext id */
+			buf[num + 7] = virt_dev->t10_dev_id[3];
+			buf[num + 8] = virt_dev->t10_dev_id[4];
+			buf[num + 9] = virt_dev->t10_dev_id[5];
+			buf[num + 10] = virt_dev->t10_dev_id[6];
+			buf[num + 11] = virt_dev->t10_dev_id[7];
+			num += buf[num + 3];
+
+			resp_len = num;
+			buf[2] = (resp_len >> 8) & 0xFF;
+			buf[3] = resp_len & 0xFF;
+			resp_len += 4;
+		} else if ((0xB0 == cmd->cdb[2]) &&
+			   (virt_dev->dev->type == TYPE_DISK)) {
+			/* block limits */
+			int max_transfer;
+			buf[1] = 0xB0;
+			buf[3] = 0x1C;
+			/* Optimal transfer granuality is PAGE_SIZE */
+			put_unaligned(cpu_to_be16(max_t(int,
+					PAGE_SIZE/virt_dev->block_size, 1)),
+				      (uint16_t *)&buf[6]);
+			/* Max transfer len is min of sg limit and 8M */
+			max_transfer = min_t(int,
+					cmd->tgt_dev->max_sg_cnt << PAGE_SHIFT,
+					8*1024*1024) / virt_dev->block_size;
+			put_unaligned(cpu_to_be32(max_transfer),
+					(uint32_t *)&buf[8]);
+			/*
+			 * Let's have optimal transfer len 1MB. Better to not
+			 * set it at all, because we don't have such limit,
+			 * but some initiators may not understand that (?).
+			 * From other side, too big transfers  are not optimal,
+			 * because SGV cache supports only <4M buffers.
+			 */
+			put_unaligned(cpu_to_be32(min_t(int,
+					max_transfer,
+					1*1024*1024 / virt_dev->block_size)),
+				      (uint32_t *)&buf[12]);
+			resp_len = buf[3] + 4;
+		} else {
+			TRACE_DBG("INQUIRY: Unsupported EVPD page %x",
+				cmd->cdb[2]);
+			scst_set_cmd_error(cmd,
+			    SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+			goto out_put;
+		}
+	} else {
+		int len, num;
+
+		if (cmd->cdb[2] != 0) {
+			TRACE_DBG("INQUIRY: Unsupported page %x", cmd->cdb[2]);
+			scst_set_cmd_error(cmd,
+			    SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+			goto out_put;
+		}
+
+		buf[2] = 5; /* Device complies to SPC-3 */
+		buf[3] = 0x12;	/* HiSup + data in format specified in SPC */
+		buf[4] = 31;/* n - 4 = 35 - 4 = 31 for full 36 byte data */
+		buf[6] = 1; /* MultiP 1 */
+		buf[7] = 2; /* CMDQUE 1, BQue 0 => commands queuing supported */
+
+		/*
+		 * 8 byte ASCII Vendor Identification of the target
+		 * - left aligned.
+		 */
+		if (virt_dev->blockio)
+			memcpy(&buf[8], SCST_BIO_VENDOR, 8);
+		else
+			memcpy(&buf[8], SCST_FIO_VENDOR, 8);
+
+		/*
+		 * 16 byte ASCII Product Identification of the target - left
+		 * aligned.
+		 */
+		memset(&buf[16], ' ', 16);
+		len = min(strlen(virt_dev->name), (size_t)16);
+		memcpy(&buf[16], virt_dev->name, len);
+
+		/*
+		 * 4 byte ASCII Product Revision Level of the target - left
+		 * aligned.
+		 */
+		memcpy(&buf[32], SCST_FIO_REV, 4);
+
+		/** Version descriptors **/
+
+		buf[4] += 58 - 36;
+		num = 0;
+
+		/* SAM-3 T10/1561-D revision 14 */
+		buf[58 + num] = 0x0;
+		buf[58 + num + 1] = 0x76;
+		num += 2;
+
+		/* Physical transport */
+		if (cmd->tgtt->get_phys_transport_version != NULL) {
+			uint16_t v = cmd->tgtt->get_phys_transport_version(cmd->tgt);
+			if (v != 0) {
+				*((__be16 *)&buf[58 + num]) = cpu_to_be16(v);
+				num += 2;
+			}
+		}
+
+		/* SCSI transport */
+		if (cmd->tgtt->get_scsi_transport_version != NULL) {
+			*((__be16 *)&buf[58 + num]) =
+				cpu_to_be16(cmd->tgtt->get_scsi_transport_version(cmd->tgt));
+			num += 2;
+		}
+
+		/* SPC-3 T10/1416-D revision 23 */
+		buf[58 + num] = 0x3;
+		buf[58 + num + 1] = 0x12;
+		num += 2;
+
+		/* Device command set */
+		if (virt_dev->command_set_version != 0) {
+			*((__be16 *)&buf[58 + num]) =
+				cpu_to_be16(virt_dev->command_set_version);
+			num += 2;
+		}
+
+		buf[4] += num;
+		resp_len = buf[4] + 5;
+	}
+
+	BUG_ON(resp_len >= INQ_BUF_SZ);
+
+	if (length > resp_len)
+		length = resp_len;
+	memcpy(address, buf, length);
+
+out_put:
+	scst_put_buf(cmd, address);
+	if (length < cmd->resp_data_len)
+		scst_set_resp_data_len(cmd, length);
+
+out_free:
+	kfree(buf);
+
+out:
+	return;
+}
+
+static void vdisk_exec_request_sense(struct scst_cmd *cmd)
+{
+	int32_t length, sl;
+	uint8_t *address;
+	uint8_t b[SCST_STANDARD_SENSE_LEN];
+
+	sl = scst_set_sense(b, sizeof(b), cmd->dev->d_sense,
+		SCST_LOAD_SENSE(scst_sense_no_sense));
+
+	length = scst_get_buf_first(cmd, &address);
+	TRACE_DBG("length %d", length);
+	if (length < 0) {
+		PRINT_ERROR("scst_get_buf_first() failed: %d)", length);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_hardw_error));
+		goto out;
+	}
+
+	length = min(sl, length);
+	memcpy(address, b, length);
+	scst_set_resp_data_len(cmd, length);
+
+	scst_put_buf(cmd, address);
+
+out:
+	return;
+}
+
+/*
+ * <<Following mode pages info copied from ST318451LW with some corrections>>
+ *
+ * ToDo: revise them
+ */
+static int vdisk_err_recov_pg(unsigned char *p, int pcontrol,
+			       struct scst_vdisk_dev *virt_dev)
+{	/* Read-Write Error Recovery page for mode_sense */
+	const unsigned char err_recov_pg[] = {0x1, 0xa, 0xc0, 11, 240, 0, 0, 0,
+					      5, 0, 0xff, 0xff};
+
+	memcpy(p, err_recov_pg, sizeof(err_recov_pg));
+	if (1 == pcontrol)
+		memset(p + 2, 0, sizeof(err_recov_pg) - 2);
+	return sizeof(err_recov_pg);
+}
+
+static int vdisk_disconnect_pg(unsigned char *p, int pcontrol,
+				struct scst_vdisk_dev *virt_dev)
+{	/* Disconnect-Reconnect page for mode_sense */
+	const unsigned char disconnect_pg[] = {0x2, 0xe, 128, 128, 0, 10, 0, 0,
+					       0, 0, 0, 0, 0, 0, 0, 0};
+
+	memcpy(p, disconnect_pg, sizeof(disconnect_pg));
+	if (1 == pcontrol)
+		memset(p + 2, 0, sizeof(disconnect_pg) - 2);
+	return sizeof(disconnect_pg);
+}
+
+static int vdisk_rigid_geo_pg(unsigned char *p, int pcontrol,
+	struct scst_vdisk_dev *virt_dev)
+{
+	unsigned char geo_m_pg[] = {0x04, 0x16, 0, 0, 0, DEF_HEADS, 0, 0,
+				    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+				    0x3a, 0x98/* 15K RPM */, 0, 0};
+	int32_t ncyl, n, rem;
+	uint64_t dividend;
+
+	memcpy(p, geo_m_pg, sizeof(geo_m_pg));
+	/*
+	 * Divide virt_dev->nblocks by (DEF_HEADS * DEF_SECTORS) and store
+	 * the quotient in ncyl and the remainder in rem.
+	 */
+	dividend = virt_dev->nblocks;
+	rem = do_div(dividend, DEF_HEADS * DEF_SECTORS);
+	ncyl = dividend;
+	if (rem != 0)
+		ncyl++;
+	memcpy(&n, p + 2, sizeof(u32));
+	n = n | ((__force u32)cpu_to_be32(ncyl) >> 8);
+	memcpy(p + 2, &n, sizeof(u32));
+	if (1 == pcontrol)
+		memset(p + 2, 0, sizeof(geo_m_pg) - 2);
+	return sizeof(geo_m_pg);
+}
+
+static int vdisk_format_pg(unsigned char *p, int pcontrol,
+			    struct scst_vdisk_dev *virt_dev)
+{       /* Format device page for mode_sense */
+	const unsigned char format_pg[] = {0x3, 0x16, 0, 0, 0, 0, 0, 0,
+					   0, 0, 0, 0, 0, 0, 0, 0,
+					   0, 0, 0, 0, 0x40, 0, 0, 0};
+
+	memcpy(p, format_pg, sizeof(format_pg));
+	p[10] = (DEF_SECTORS >> 8) & 0xff;
+	p[11] = DEF_SECTORS & 0xff;
+	p[12] = (virt_dev->block_size >> 8) & 0xff;
+	p[13] = virt_dev->block_size & 0xff;
+	if (1 == pcontrol)
+		memset(p + 2, 0, sizeof(format_pg) - 2);
+	return sizeof(format_pg);
+}
+
+static int vdisk_caching_pg(unsigned char *p, int pcontrol,
+			     struct scst_vdisk_dev *virt_dev)
+{	/* Caching page for mode_sense */
+	const unsigned char caching_pg[] = {0x8, 18, 0x10, 0, 0xff, 0xff, 0, 0,
+		0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0, 0, 0, 0, 0, 0};
+
+	memcpy(p, caching_pg, sizeof(caching_pg));
+	p[2] |= !(virt_dev->wt_flag || virt_dev->nv_cache) ? WCE : 0;
+	if (1 == pcontrol)
+		memset(p + 2, 0, sizeof(caching_pg) - 2);
+	return sizeof(caching_pg);
+}
+
+static int vdisk_ctrl_m_pg(unsigned char *p, int pcontrol,
+			    struct scst_vdisk_dev *virt_dev)
+{	/* Control mode page for mode_sense */
+	const unsigned char ctrl_m_pg[] = {0xa, 0xa, 0, 0, 0, 0, 0, 0,
+					   0, 0, 0x2, 0x4b};
+
+	memcpy(p, ctrl_m_pg, sizeof(ctrl_m_pg));
+	switch (pcontrol) {
+	case 0:
+		p[2] |= virt_dev->dev->tst << 5;
+		p[2] |= virt_dev->dev->d_sense << 2;
+		p[3] |= virt_dev->dev->queue_alg << 4;
+		p[4] |= virt_dev->dev->swp << 3;
+		p[5] |= virt_dev->dev->tas << 6;
+		break;
+	case 1:
+		memset(p + 2, 0, sizeof(ctrl_m_pg) - 2);
+#if 0	/*
+	 * It's too early to implement it, since we can't control the
+	 * backstorage device parameters. ToDo
+	 */
+		p[2] |= 7 << 5;		/* TST */
+		p[3] |= 0xF << 4;	/* QUEUE ALGORITHM MODIFIER */
+#endif
+		p[2] |= 1 << 2;		/* D_SENSE */
+		p[4] |= 1 << 3;		/* SWP */
+		p[5] |= 1 << 6;		/* TAS */
+		break;
+	case 2:
+		p[2] |= DEF_TST << 5;
+		p[2] |= DEF_DSENSE << 2;
+		if (virt_dev->wt_flag || virt_dev->nv_cache)
+			p[3] |= DEF_QUEUE_ALG_WT << 4;
+		else
+			p[3] |= DEF_QUEUE_ALG << 4;
+		p[4] |= DEF_SWP << 3;
+		p[5] |= DEF_TAS << 6;
+		break;
+	default:
+		BUG();
+	}
+	return sizeof(ctrl_m_pg);
+}
+
+static int vdisk_iec_m_pg(unsigned char *p, int pcontrol,
+			   struct scst_vdisk_dev *virt_dev)
+{	/* Informational Exceptions control mode page for mode_sense */
+	const unsigned char iec_m_pg[] = {0x1c, 0xa, 0x08, 0, 0, 0, 0, 0,
+					  0, 0, 0x0, 0x0};
+	memcpy(p, iec_m_pg, sizeof(iec_m_pg));
+	if (1 == pcontrol)
+		memset(p + 2, 0, sizeof(iec_m_pg) - 2);
+	return sizeof(iec_m_pg);
+}
+
+static void vdisk_exec_mode_sense(struct scst_cmd *cmd)
+{
+	int32_t length;
+	uint8_t *address;
+	uint8_t *buf;
+	struct scst_vdisk_dev *virt_dev;
+	uint32_t blocksize;
+	uint64_t nblocks;
+	unsigned char dbd, type;
+	int pcontrol, pcode, subpcode;
+	unsigned char dev_spec;
+	int msense_6, offset = 0, len;
+	unsigned char *bp;
+
+	buf = kzalloc(MSENSE_BUF_SZ, GFP_KERNEL);
+	if (buf == NULL) {
+		scst_set_busy(cmd);
+		goto out;
+	}
+
+	virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+	blocksize = virt_dev->block_size;
+	nblocks = virt_dev->nblocks;
+
+	type = cmd->dev->type;    /* type dev */
+	dbd = cmd->cdb[1] & DBD;
+	pcontrol = (cmd->cdb[2] & 0xc0) >> 6;
+	pcode = cmd->cdb[2] & 0x3f;
+	subpcode = cmd->cdb[3];
+	msense_6 = (MODE_SENSE == cmd->cdb[0]);
+	dev_spec = (virt_dev->dev->rd_only ||
+		     cmd->tgt_dev->acg_dev->rd_only) ? WP : 0;
+
+	if (!virt_dev->blockio)
+		dev_spec |= DPOFUA;
+
+	length = scst_get_buf_first(cmd, &address);
+	if (unlikely(length <= 0)) {
+		if (length < 0) {
+			PRINT_ERROR("scst_get_buf_first() failed: %d", length);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+		}
+		goto out_free;
+	}
+
+	if (0x3 == pcontrol) {
+		TRACE_DBG("%s", "MODE SENSE: Saving values not supported");
+		scst_set_cmd_error(cmd,
+		    SCST_LOAD_SENSE(scst_sense_saving_params_unsup));
+		goto out_put;
+	}
+
+	if (msense_6) {
+		buf[1] = type;
+		buf[2] = dev_spec;
+		offset = 4;
+	} else {
+		buf[2] = type;
+		buf[3] = dev_spec;
+		offset = 8;
+	}
+
+	if (0 != subpcode) {
+		/* TODO: Control Extension page */
+		TRACE_DBG("%s", "MODE SENSE: Only subpage 0 is supported");
+		scst_set_cmd_error(cmd,
+		    SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out_put;
+	}
+
+	if (!dbd) {
+		/* Create block descriptor */
+		buf[offset - 1] = 0x08;		/* block descriptor length */
+		if (nblocks >> 32) {
+			buf[offset + 0] = 0xFF;
+			buf[offset + 1] = 0xFF;
+			buf[offset + 2] = 0xFF;
+			buf[offset + 3] = 0xFF;
+		} else {
+			/* num blks */
+			buf[offset + 0] = (nblocks >> (BYTE * 3)) & 0xFF;
+			buf[offset + 1] = (nblocks >> (BYTE * 2)) & 0xFF;
+			buf[offset + 2] = (nblocks >> (BYTE * 1)) & 0xFF;
+			buf[offset + 3] = (nblocks >> (BYTE * 0)) & 0xFF;
+		}
+		buf[offset + 4] = 0;			/* density code */
+		buf[offset + 5] = (blocksize >> (BYTE * 2)) & 0xFF;/* blklen */
+		buf[offset + 6] = (blocksize >> (BYTE * 1)) & 0xFF;
+		buf[offset + 7] = (blocksize >> (BYTE * 0)) & 0xFF;
+
+		offset += 8;			/* increment offset */
+	}
+
+	bp = buf + offset;
+
+	switch (pcode) {
+	case 0x1:	/* Read-Write error recovery page, direct access */
+		len = vdisk_err_recov_pg(bp, pcontrol, virt_dev);
+		break;
+	case 0x2:	/* Disconnect-Reconnect page, all devices */
+		len = vdisk_disconnect_pg(bp, pcontrol, virt_dev);
+		break;
+	case 0x3:       /* Format device page, direct access */
+		len = vdisk_format_pg(bp, pcontrol, virt_dev);
+		break;
+	case 0x4:	/* Rigid disk geometry */
+		len = vdisk_rigid_geo_pg(bp, pcontrol, virt_dev);
+		break;
+	case 0x8:	/* Caching page, direct access */
+		len = vdisk_caching_pg(bp, pcontrol, virt_dev);
+		break;
+	case 0xa:	/* Control Mode page, all devices */
+		len = vdisk_ctrl_m_pg(bp, pcontrol, virt_dev);
+		break;
+	case 0x1c:	/* Informational Exceptions Mode page, all devices */
+		len = vdisk_iec_m_pg(bp, pcontrol, virt_dev);
+		break;
+	case 0x3f:	/* Read all Mode pages */
+		len = vdisk_err_recov_pg(bp, pcontrol, virt_dev);
+		len += vdisk_disconnect_pg(bp + len, pcontrol, virt_dev);
+		len += vdisk_format_pg(bp + len, pcontrol, virt_dev);
+		len += vdisk_caching_pg(bp + len, pcontrol, virt_dev);
+		len += vdisk_ctrl_m_pg(bp + len, pcontrol, virt_dev);
+		len += vdisk_iec_m_pg(bp + len, pcontrol, virt_dev);
+		len += vdisk_rigid_geo_pg(bp + len, pcontrol, virt_dev);
+		break;
+	default:
+		TRACE_DBG("MODE SENSE: Unsupported page %x", pcode);
+		scst_set_cmd_error(cmd,
+		    SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out_put;
+	}
+
+	offset += len;
+
+	if (msense_6)
+		buf[0] = offset - 1;
+	else {
+		buf[0] = ((offset - 2) >> 8) & 0xff;
+		buf[1] = (offset - 2) & 0xff;
+	}
+
+	if (offset > length)
+		offset = length;
+	memcpy(address, buf, offset);
+
+out_put:
+	scst_put_buf(cmd, address);
+	if (offset < cmd->resp_data_len)
+		scst_set_resp_data_len(cmd, offset);
+
+out_free:
+	kfree(buf);
+
+out:
+	return;
+}
+
+static int vdisk_set_wt(struct scst_vdisk_dev *virt_dev, int wt)
+{
+	int res = 0;
+
+	if ((virt_dev->wt_flag == wt) || virt_dev->nullio || virt_dev->nv_cache)
+		goto out;
+
+	spin_lock(&virt_dev->flags_lock);
+	virt_dev->wt_flag = wt;
+	spin_unlock(&virt_dev->flags_lock);
+
+	scst_dev_del_all_thr_data(virt_dev->dev);
+
+out:
+	return res;
+}
+
+static void vdisk_ctrl_m_pg_select(unsigned char *p,
+	struct scst_vdisk_dev *virt_dev)
+{
+	struct scst_device *dev = virt_dev->dev;
+	int old_swp = dev->swp, old_tas = dev->tas, old_dsense = dev->d_sense;
+
+#if 0
+	/* Not implemented yet, see comment in vdisk_ctrl_m_pg() */
+	dev->tst = p[2] >> 5;
+	dev->queue_alg = p[3] >> 4;
+#endif
+	dev->swp = (p[4] & 0x8) >> 3;
+	dev->tas = (p[5] & 0x40) >> 6;
+	dev->d_sense = (p[2] & 0x4) >> 2;
+
+	PRINT_INFO("Device %s: new control mode page parameters: SWP %x "
+		"(was %x), TAS %x (was %x), D_SENSE %d (was %d)",
+		virt_dev->name, dev->swp, old_swp, dev->tas, old_tas,
+		dev->d_sense, old_dsense);
+	return;
+}
+
+static void vdisk_exec_mode_select(struct scst_cmd *cmd)
+{
+	int32_t length;
+	uint8_t *address;
+	struct scst_vdisk_dev *virt_dev;
+	int mselect_6, offset;
+
+	virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+	mselect_6 = (MODE_SELECT == cmd->cdb[0]);
+
+	length = scst_get_buf_first(cmd, &address);
+	if (unlikely(length <= 0)) {
+		if (length < 0) {
+			PRINT_ERROR("scst_get_buf_first() failed: %d", length);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+		}
+		goto out;
+	}
+
+	if (!(cmd->cdb[1] & PF) || (cmd->cdb[1] & SP)) {
+		TRACE(TRACE_MINOR|TRACE_SCSI, "MODE SELECT: Unsupported "
+			"value(s) of PF and/or SP bits (cdb[1]=%x)",
+			cmd->cdb[1]);
+		scst_set_cmd_error(cmd,
+		    SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out_put;
+	}
+
+	if (mselect_6)
+		offset = 4;
+	else
+		offset = 8;
+
+	if (address[offset - 1] == 8) {
+		offset += 8;
+	} else if (address[offset - 1] != 0) {
+		PRINT_ERROR("%s", "MODE SELECT: Wrong parameters list "
+			"lenght");
+		scst_set_cmd_error(cmd,
+		    SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
+		goto out_put;
+	}
+
+	while (length > offset + 2) {
+		if (address[offset] & PS) {
+			PRINT_ERROR("%s", "MODE SELECT: Illegal PS bit");
+			scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
+				scst_sense_invalid_field_in_parm_list));
+			goto out_put;
+		}
+		if ((address[offset] & 0x3f) == 0x8) {
+			/* Caching page */
+			if (address[offset + 1] != 18) {
+				PRINT_ERROR("%s", "MODE SELECT: Invalid "
+					"caching page request");
+				scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
+				    scst_sense_invalid_field_in_parm_list));
+				goto out_put;
+			}
+			if (vdisk_set_wt(virt_dev,
+			      (address[offset + 2] & WCE) ? 0 : 1) != 0) {
+				scst_set_cmd_error(cmd,
+				    SCST_LOAD_SENSE(scst_sense_hardw_error));
+				goto out_put;
+			}
+			break;
+		} else if ((address[offset] & 0x3f) == 0xA) {
+			/* Control page */
+			if (address[offset + 1] != 0xA) {
+				PRINT_ERROR("%s", "MODE SELECT: Invalid "
+					"control page request");
+				scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
+				    scst_sense_invalid_field_in_parm_list));
+				goto out_put;
+			}
+			vdisk_ctrl_m_pg_select(&address[offset], virt_dev);
+		} else {
+			PRINT_ERROR("MODE SELECT: Invalid request %x",
+				address[offset] & 0x3f);
+			scst_set_cmd_error(cmd, SCST_LOAD_SENSE(
+			    scst_sense_invalid_field_in_parm_list));
+			goto out_put;
+		}
+		offset += address[offset + 1];
+	}
+
+out_put:
+	scst_put_buf(cmd, address);
+
+out:
+	return;
+}
+
+static void vdisk_exec_log(struct scst_cmd *cmd)
+{
+
+	/* No log pages are supported */
+	scst_set_cmd_error(cmd,
+		SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+	return;
+}
+
+static void vdisk_exec_read_capacity(struct scst_cmd *cmd)
+{
+	int32_t length;
+	uint8_t *address;
+	struct scst_vdisk_dev *virt_dev;
+	uint32_t blocksize;
+	uint64_t nblocks;
+	uint8_t buffer[8];
+
+	virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+	blocksize = virt_dev->block_size;
+	nblocks = virt_dev->nblocks;
+
+	if ((cmd->cdb[8] & 1) == 0) {
+		uint64_t lba = be64_to_cpu(get_unaligned((__be64 *)&cmd->cdb[2]));
+		if (lba != 0) {
+			TRACE_DBG("PMI zero and LBA not zero (cmd %p)", cmd);
+			scst_set_cmd_error(cmd,
+			    SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+			goto out;
+		}
+	}
+
+	/* Last block on the virt_dev is (nblocks-1) */
+	memset(buffer, 0, sizeof(buffer));
+	if (nblocks >> 32) {
+		buffer[0] = 0xFF;
+		buffer[1] = 0xFF;
+		buffer[2] = 0xFF;
+		buffer[3] = 0xFF;
+	} else {
+		buffer[0] = ((nblocks - 1) >> (BYTE * 3)) & 0xFF;
+		buffer[1] = ((nblocks - 1) >> (BYTE * 2)) & 0xFF;
+		buffer[2] = ((nblocks - 1) >> (BYTE * 1)) & 0xFF;
+		buffer[3] = ((nblocks - 1) >> (BYTE * 0)) & 0xFF;
+	}
+	buffer[4] = (blocksize >> (BYTE * 3)) & 0xFF;
+	buffer[5] = (blocksize >> (BYTE * 2)) & 0xFF;
+	buffer[6] = (blocksize >> (BYTE * 1)) & 0xFF;
+	buffer[7] = (blocksize >> (BYTE * 0)) & 0xFF;
+
+	length = scst_get_buf_first(cmd, &address);
+	if (unlikely(length <= 0)) {
+		if (length < 0) {
+			PRINT_ERROR("scst_get_buf_first() failed: %d", length);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+		}
+		goto out;
+	}
+
+	length = min_t(int, length, sizeof(buffer));
+
+	memcpy(address, buffer, length);
+
+	scst_put_buf(cmd, address);
+
+	if (length < cmd->resp_data_len)
+		scst_set_resp_data_len(cmd, length);
+
+out:
+	return;
+}
+
+static void vdisk_exec_read_capacity16(struct scst_cmd *cmd)
+{
+	int32_t length;
+	uint8_t *address;
+	struct scst_vdisk_dev *virt_dev;
+	uint32_t blocksize;
+	uint64_t nblocks;
+	uint8_t buffer[32];
+
+	virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+	blocksize = virt_dev->block_size;
+	nblocks = virt_dev->nblocks - 1;
+
+	if ((cmd->cdb[14] & 1) == 0) {
+		uint64_t lba = be64_to_cpu(get_unaligned((__be64 *)&cmd->cdb[2]));
+		if (lba != 0) {
+			TRACE_DBG("PMI zero and LBA not zero (cmd %p)", cmd);
+			scst_set_cmd_error(cmd,
+			    SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+			goto out;
+		}
+	}
+
+	memset(buffer, 0, sizeof(buffer));
+
+	buffer[0] = nblocks >> 56;
+	buffer[1] = (nblocks >> 48) & 0xFF;
+	buffer[2] = (nblocks >> 40) & 0xFF;
+	buffer[3] = (nblocks >> 32) & 0xFF;
+	buffer[4] = (nblocks >> 24) & 0xFF;
+	buffer[5] = (nblocks >> 16) & 0xFF;
+	buffer[6] = (nblocks >> 8) & 0xFF;
+	buffer[7] = nblocks & 0xFF;
+
+	buffer[8] = (blocksize >> (BYTE * 3)) & 0xFF;
+	buffer[9] = (blocksize >> (BYTE * 2)) & 0xFF;
+	buffer[10] = (blocksize >> (BYTE * 1)) & 0xFF;
+	buffer[11] = (blocksize >> (BYTE * 0)) & 0xFF;
+
+	switch (blocksize) {
+	case 512:
+		buffer[13] = 3;
+		break;
+	case 1024:
+		buffer[13] = 2;
+		break;
+	case 2048:
+		buffer[13] = 1;
+		break;
+	case 4096:
+	default:
+		buffer[13] = 0;
+		break;
+	}
+
+	length = scst_get_buf_first(cmd, &address);
+	if (unlikely(length <= 0)) {
+		if (length < 0) {
+			PRINT_ERROR("scst_get_buf_first() failed: %d", length);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+			}
+		goto out;
+	}
+
+	length = min_t(int, length, sizeof(buffer));
+
+	memcpy(address, buffer, length);
+
+	scst_put_buf(cmd, address);
+
+	if (length < cmd->resp_data_len)
+		scst_set_resp_data_len(cmd, length);
+
+out:
+	return;
+}
+
+static void vdisk_exec_read_toc(struct scst_cmd *cmd)
+{
+	int32_t length, off = 0;
+	uint8_t *address;
+	struct scst_vdisk_dev *virt_dev;
+	uint32_t nblocks;
+	uint8_t buffer[4+8+8] = { 0x00, 0x0a, 0x01, 0x01, 0x00, 0x14,
+				  0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+	if (cmd->dev->type != TYPE_ROM) {
+		PRINT_ERROR("%s", "READ TOC for non-CDROM device");
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_opcode));
+		goto out;
+	}
+
+	if (cmd->cdb[2] & 0x0e/*Format*/) {
+		PRINT_ERROR("%s", "READ TOC: invalid requested data format");
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out;
+	}
+
+	if ((cmd->cdb[6] != 0 && (cmd->cdb[2] & 0x01)) ||
+	    (cmd->cdb[6] > 1 && cmd->cdb[6] != 0xAA)) {
+		PRINT_ERROR("READ TOC: invalid requested track number %x",
+			cmd->cdb[6]);
+		scst_set_cmd_error(cmd,
+			SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+		goto out;
+	}
+
+	length = scst_get_buf_first(cmd, &address);
+	if (unlikely(length <= 0)) {
+		if (length < 0) {
+			PRINT_ERROR("scst_get_buf_first() failed: %d", length);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+		}
+		goto out;
+	}
+
+	virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+	/* ToDo when you have > 8TB ROM device. */
+	nblocks = (uint32_t)virt_dev->nblocks;
+
+	/* Header */
+	memset(buffer, 0, sizeof(buffer));
+	buffer[2] = 0x01;    /* First Track/Session */
+	buffer[3] = 0x01;    /* Last Track/Session */
+	off = 4;
+	if (cmd->cdb[6] <= 1) {
+		/* Fistr TOC Track Descriptor */
+		/* ADDR    0x10 - Q Sub-channel encodes current position data
+		   CONTROL 0x04 - Data track, recoreded uninterrupted */
+		buffer[off+1] = 0x14;
+		/* Track Number */
+		buffer[off+2] = 0x01;
+		off += 8;
+	}
+	if (!(cmd->cdb[2] & 0x01)) {
+		/* Lead-out area TOC Track Descriptor */
+		buffer[off+1] = 0x14;
+		/* Track Number */
+		buffer[off+2] = 0xAA;
+		/* Track Start Address */
+		buffer[off+4] = (nblocks >> (BYTE * 3)) & 0xFF;
+		buffer[off+5] = (nblocks >> (BYTE * 2)) & 0xFF;
+		buffer[off+6] = (nblocks >> (BYTE * 1)) & 0xFF;
+		buffer[off+7] = (nblocks >> (BYTE * 0)) & 0xFF;
+		off += 8;
+	}
+
+	buffer[1] = off - 2;    /* Data  Length */
+
+	if (off > length)
+		off = length;
+	memcpy(address, buffer, off);
+
+	scst_put_buf(cmd, address);
+
+	if (off < cmd->resp_data_len)
+		scst_set_resp_data_len(cmd, off);
+
+out:
+	return;
+}
+
+static void vdisk_exec_prevent_allow_medium_removal(struct scst_cmd *cmd)
+{
+	struct scst_vdisk_dev *virt_dev =
+		(struct scst_vdisk_dev *)cmd->dev->dh_priv;
+
+	TRACE_DBG("PERSIST/PREVENT 0x%02x", cmd->cdb[4]);
+
+	spin_lock(&virt_dev->flags_lock);
+	virt_dev->prevent_allow_medium_removal = cmd->cdb[4] & 0x01 ? 1 : 0;
+	spin_unlock(&virt_dev->flags_lock);
+
+	return;
+}
+
+static int vdisk_fsync(struct scst_vdisk_thr *thr, loff_t loff,
+	loff_t len, struct scst_cmd *cmd, struct scst_device *dev)
+{
+	int res = 0;
+	struct scst_vdisk_dev *virt_dev =
+		(struct scst_vdisk_dev *)dev->dh_priv;
+	struct file *file;
+
+	/* Hopefully, the compiler will generate the single comparison */
+	if (virt_dev->nv_cache || virt_dev->wt_flag ||
+	    virt_dev->o_direct_flag || virt_dev->nullio)
+		goto out;
+
+	if (virt_dev->blockio) {
+		res = blockio_flush(thr->bdev);
+		goto out;
+	}
+
+	file = thr->fd;
+
+#if 0	/* For sparse files we might need to sync metadata as well */
+	res = generic_write_sync(file, loff, len);
+#else
+	res = filemap_write_and_wait_range(file->f_mapping, loff, len);
+#endif
+	if (unlikely(res != 0)) {
+		PRINT_ERROR("sync range failed (%d)", res);
+		if (cmd != NULL) {
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_write_error));
+		}
+	}
+
+out:
+	return res;
+}
+
+static struct iovec *vdisk_alloc_iv(struct scst_cmd *cmd,
+	struct scst_vdisk_thr *thr)
+{
+	int iv_count;
+
+	iv_count = min_t(int, scst_get_buf_count(cmd), UIO_MAXIOV);
+	if (iv_count > thr->iv_count) {
+		kfree(thr->iv);
+		/* It can't be called in atomic context */
+		thr->iv = kmalloc(sizeof(*thr->iv) * iv_count, GFP_KERNEL);
+		if (thr->iv == NULL) {
+			PRINT_ERROR("Unable to allocate iv (%d)", iv_count);
+			scst_set_busy(cmd);
+			goto out;
+		}
+		thr->iv_count = iv_count;
+	}
+
+out:
+	return thr->iv;
+}
+
+static void vdisk_exec_read(struct scst_cmd *cmd,
+	struct scst_vdisk_thr *thr, loff_t loff)
+{
+	mm_segment_t old_fs;
+	loff_t err;
+	ssize_t length, full_len;
+	uint8_t __user *address;
+	struct scst_vdisk_dev *virt_dev =
+	    (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+	struct file *fd = thr->fd;
+	struct iovec *iv;
+	int iv_count, i;
+	bool finished = false;
+
+	if (virt_dev->nullio)
+		goto out;
+
+	iv = vdisk_alloc_iv(cmd, thr);
+	if (iv == NULL)
+		goto out;
+
+	length = scst_get_buf_first(cmd, (uint8_t __force **)&address);
+	if (unlikely(length < 0)) {
+		PRINT_ERROR("scst_get_buf_first() failed: %zd", length);
+		scst_set_cmd_error(cmd,
+		    SCST_LOAD_SENSE(scst_sense_hardw_error));
+		goto out;
+	}
+
+	old_fs = get_fs();
+	set_fs(get_ds());
+
+	while (1) {
+		iv_count = 0;
+		full_len = 0;
+		i = -1;
+		while (length > 0) {
+			full_len += length;
+			i++;
+			iv_count++;
+			iv[i].iov_base = address;
+			iv[i].iov_len = length;
+			if (iv_count == UIO_MAXIOV)
+				break;
+			length = scst_get_buf_next(cmd,
+				(uint8_t __force **)&address);
+		}
+		if (length == 0) {
+			finished = true;
+			if (unlikely(iv_count == 0))
+				break;
+		} else if (unlikely(length < 0)) {
+			PRINT_ERROR("scst_get_buf_next() failed: %zd", length);
+			scst_set_cmd_error(cmd,
+			    SCST_LOAD_SENSE(scst_sense_hardw_error));
+			goto out_set_fs;
+		}
+
+		TRACE_DBG("(iv_count %d, full_len %zd)", iv_count, full_len);
+		/* SEEK */
+		if (fd->f_op->llseek)
+			err = fd->f_op->llseek(fd, loff, 0/*SEEK_SET*/);
+		else
+			err = default_llseek(fd, loff, 0/*SEEK_SET*/);
+		if (err != loff) {
+			PRINT_ERROR("lseek trouble %lld != %lld",
+				    (long long unsigned int)err,
+				    (long long unsigned int)loff);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+			goto out_set_fs;
+		}
+
+		/* READ */
+		err = vfs_readv(fd, (struct iovec __force __user *)iv, iv_count,
+				&fd->f_pos);
+
+		if ((err < 0) || (err < full_len)) {
+			PRINT_ERROR("readv() returned %lld from %zd",
+				    (long long unsigned int)err,
+				    full_len);
+			if (err == -EAGAIN)
+				scst_set_busy(cmd);
+			else {
+				scst_set_cmd_error(cmd,
+				    SCST_LOAD_SENSE(scst_sense_read_error));
+			}
+			goto out_set_fs;
+		}
+
+		for (i = 0; i < iv_count; i++)
+			scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+
+		if (finished)
+			break;
+
+		loff += full_len;
+		length = scst_get_buf_next(cmd, (uint8_t __force **)&address);
+	};
+
+	set_fs(old_fs);
+
+out:
+	return;
+
+out_set_fs:
+	set_fs(old_fs);
+	for (i = 0; i < iv_count; i++)
+		scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+	goto out;
+}
+
+static void vdisk_exec_write(struct scst_cmd *cmd,
+	struct scst_vdisk_thr *thr, loff_t loff)
+{
+	mm_segment_t old_fs;
+	loff_t err;
+	ssize_t length, full_len, saved_full_len;
+	uint8_t __user *address;
+	struct scst_vdisk_dev *virt_dev =
+	    (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+	struct file *fd = thr->fd;
+	struct iovec *iv, *eiv;
+	int i, iv_count, eiv_count;
+	bool finished = false;
+
+	if (virt_dev->nullio)
+		goto out;
+
+	iv = vdisk_alloc_iv(cmd, thr);
+	if (iv == NULL)
+		goto out;
+
+	length = scst_get_buf_first(cmd, (uint8_t __force **)&address);
+	if (unlikely(length < 0)) {
+		PRINT_ERROR("scst_get_buf_first() failed: %zd", length);
+		scst_set_cmd_error(cmd,
+		    SCST_LOAD_SENSE(scst_sense_hardw_error));
+		goto out;
+	}
+
+	old_fs = get_fs();
+	set_fs(get_ds());
+
+	while (1) {
+		iv_count = 0;
+		full_len = 0;
+		i = -1;
+		while (length > 0) {
+			full_len += length;
+			i++;
+			iv_count++;
+			iv[i].iov_base = address;
+			iv[i].iov_len = length;
+			if (iv_count == UIO_MAXIOV)
+				break;
+			length = scst_get_buf_next(cmd,
+				(uint8_t __force **)&address);
+		}
+		if (length == 0) {
+			finished = true;
+			if (unlikely(iv_count == 0))
+				break;
+		} else if (unlikely(length < 0)) {
+			PRINT_ERROR("scst_get_buf_next() failed: %zd", length);
+			scst_set_cmd_error(cmd,
+			    SCST_LOAD_SENSE(scst_sense_hardw_error));
+			goto out_set_fs;
+		}
+
+		saved_full_len = full_len;
+		eiv = iv;
+		eiv_count = iv_count;
+restart:
+		TRACE_DBG("writing(eiv_count %d, full_len %zd)", eiv_count, full_len);
+
+		/* SEEK */
+		if (fd->f_op->llseek)
+			err = fd->f_op->llseek(fd, loff, 0 /*SEEK_SET */);
+		else
+			err = default_llseek(fd, loff, 0 /*SEEK_SET */);
+		if (err != loff) {
+			PRINT_ERROR("lseek trouble %lld != %lld",
+				    (long long unsigned int)err,
+				    (long long unsigned int)loff);
+			scst_set_cmd_error(cmd,
+				   SCST_LOAD_SENSE(scst_sense_hardw_error));
+			goto out_set_fs;
+		}
+
+		/* WRITE */
+		err = vfs_writev(fd, (struct iovec __force __user *)eiv, eiv_count,
+				 &fd->f_pos);
+
+		if (err < 0) {
+			PRINT_ERROR("write() returned %lld from %zd",
+				    (long long unsigned int)err,
+				    full_len);
+			if (err == -EAGAIN)
+				scst_set_busy(cmd);
+			else {
+				scst_set_cmd_error(cmd,
+				    SCST_LOAD_SENSE(scst_sense_write_error));
+			}
+			goto out_set_fs;
+		} else if (err < full_len) {
+			/*
+			 * Probably that's wrong, but sometimes write() returns
+			 * value less, than requested. Let's restart.
+			 */
+			int e = eiv_count;
+			TRACE_MGMT_DBG("write() returned %d from %zd "
+				"(iv_count=%d)", (int)err, full_len,
+				eiv_count);
+			if (err == 0) {
+				PRINT_INFO("Suspicious: write() returned 0 from "
+					"%zd (iv_count=%d)", full_len, eiv_count);
+			}
+			full_len -= err;
+			for (i = 0; i < e; i++) {
+				if ((long long)eiv->iov_len < err) {
+					err -= eiv->iov_len;
+					eiv++;
+					eiv_count--;
+				} else {
+					eiv->iov_base =
+					    (uint8_t __force __user *)eiv->iov_base + err;
+					eiv->iov_len -= err;
+					break;
+				}
+			}
+			goto restart;
+		}
+
+		for (i = 0; i < iv_count; i++)
+			scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+
+		if (finished)
+			break;
+
+		loff += saved_full_len;
+		length = scst_get_buf_next(cmd, (uint8_t __force **)&address);
+	}
+
+	set_fs(old_fs);
+
+out:
+	return;
+
+out_set_fs:
+	set_fs(old_fs);
+	for (i = 0; i < iv_count; i++)
+		scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+	goto out;
+}
+
+struct scst_blockio_work {
+	atomic_t bios_inflight;
+	struct scst_cmd *cmd;
+};
+
+static inline void blockio_check_finish(struct scst_blockio_work *blockio_work)
+{
+	/* Decrement the bios in processing, and if zero signal completion */
+	if (atomic_dec_and_test(&blockio_work->bios_inflight)) {
+		blockio_work->cmd->completed = 1;
+		blockio_work->cmd->scst_cmd_done(blockio_work->cmd,
+			SCST_CMD_STATE_DEFAULT, scst_estimate_context());
+		kmem_cache_free(blockio_work_cachep, blockio_work);
+	}
+	return;
+}
+
+static void blockio_endio(struct bio *bio, int error)
+{
+	struct scst_blockio_work *blockio_work = bio->bi_private;
+
+	if (unlikely(!bio_flagged(bio, BIO_UPTODATE))) {
+		if (error == 0) {
+			PRINT_ERROR("Not up to date bio with error 0 for "
+				"cmd %p, returning -EIO", blockio_work->cmd);
+			error = -EIO;
+		}
+	}
+
+	if (unlikely(error != 0)) {
+		static DEFINE_SPINLOCK(blockio_endio_lock);
+
+		PRINT_ERROR("cmd %p returned error %d", blockio_work->cmd,
+			error);
+
+		/* To protect from several bios finishing simultaneously */
+		spin_lock_bh(&blockio_endio_lock);
+
+		if (bio->bi_rw & (1 << BIO_RW))
+			scst_set_cmd_error(blockio_work->cmd,
+				SCST_LOAD_SENSE(scst_sense_write_error));
+		else
+			scst_set_cmd_error(blockio_work->cmd,
+				SCST_LOAD_SENSE(scst_sense_read_error));
+
+		spin_unlock_bh(&blockio_endio_lock);
+	}
+
+	blockio_check_finish(blockio_work);
+
+	bio_put(bio);
+	return;
+}
+
+static void blockio_exec_rw(struct scst_cmd *cmd, struct scst_vdisk_thr *thr,
+	u64 lba_start, int write)
+{
+	struct scst_vdisk_dev *virt_dev =
+		(struct scst_vdisk_dev *)cmd->dev->dh_priv;
+	struct block_device *bdev = thr->bdev;
+	struct request_queue *q = bdev_get_queue(bdev);
+	int length, max_nr_vecs = 0;
+	uint8_t *address;
+	struct bio *bio = NULL, *hbio = NULL, *tbio = NULL;
+	int need_new_bio;
+	struct scst_blockio_work *blockio_work;
+	int bios = 0;
+
+	if (virt_dev->nullio)
+		goto out;
+
+	/* Allocate and initialize blockio_work struct */
+	blockio_work = kmem_cache_alloc(blockio_work_cachep, GFP_KERNEL);
+	if (blockio_work == NULL)
+		goto out_no_mem;
+
+	blockio_work->cmd = cmd;
+
+	if (q)
+		max_nr_vecs = min(bio_get_nr_vecs(bdev), BIO_MAX_PAGES);
+	else
+		max_nr_vecs = 1;
+
+	need_new_bio = 1;
+
+	length = scst_get_buf_first(cmd, &address);
+	while (length > 0) {
+		int len, bytes, off, thislen;
+		uint8_t *addr;
+		u64 lba_start0;
+
+		addr = address;
+		off = offset_in_page(addr);
+		len = length;
+		thislen = 0;
+		lba_start0 = lba_start;
+
+		while (len > 0) {
+			int rc;
+			struct page *page = virt_to_page(addr);
+
+			if (need_new_bio) {
+				bio = bio_kmalloc(GFP_KERNEL, max_nr_vecs);
+				if (!bio) {
+					PRINT_ERROR("Failed to create bio "
+						"for data segment %d (cmd %p)",
+						cmd->get_sg_buf_entry_num, cmd);
+					goto out_no_bio;
+				}
+
+				bios++;
+				need_new_bio = 0;
+				bio->bi_end_io = blockio_endio;
+				bio->bi_sector = lba_start0 <<
+					(virt_dev->block_shift - 9);
+				bio->bi_bdev = bdev;
+				bio->bi_private = blockio_work;
+				/*
+				 * Better to fail fast w/o any local recovery
+				 * and retries.
+				 */
+#ifdef BIO_RW_FAILFAST
+				bio->bi_rw |= (1 << BIO_RW_FAILFAST);
+#else
+				bio->bi_rw |= (1 << BIO_RW_FAILFAST_DEV) |
+					      (1 << BIO_RW_FAILFAST_TRANSPORT) |
+					      (1 << BIO_RW_FAILFAST_DRIVER);
+#endif
+#if 0 /* It could be win, but could be not, so a performance study is needed */
+				bio->bi_rw |= 1 << BIO_RW_SYNC;
+#endif
+				if (!hbio)
+					hbio = tbio = bio;
+				else
+					tbio = tbio->bi_next = bio;
+			}
+
+			bytes = min_t(unsigned int, len, PAGE_SIZE - off);
+
+			rc = bio_add_page(bio, page, bytes, off);
+			if (rc < bytes) {
+				BUG_ON(rc != 0);
+				need_new_bio = 1;
+				lba_start0 += thislen >> virt_dev->block_shift;
+				thislen = 0;
+				continue;
+			}
+
+			addr += PAGE_SIZE;
+			thislen += bytes;
+			len -= bytes;
+			off = 0;
+		}
+
+		lba_start += length >> virt_dev->block_shift;
+
+		scst_put_buf(cmd, address);
+		length = scst_get_buf_next(cmd, &address);
+	}
+
+	/* +1 to prevent erroneous too early command completion */
+	atomic_set(&blockio_work->bios_inflight, bios+1);
+
+	while (hbio) {
+		bio = hbio;
+		hbio = hbio->bi_next;
+		bio->bi_next = NULL;
+		submit_bio((write != 0), bio);
+	}
+
+	if (q && q->unplug_fn)
+		q->unplug_fn(q);
+
+	blockio_check_finish(blockio_work);
+
+out:
+	return;
+
+out_no_bio:
+	while (hbio) {
+		bio = hbio;
+		hbio = hbio->bi_next;
+		bio_put(bio);
+	}
+	kmem_cache_free(blockio_work_cachep, blockio_work);
+
+out_no_mem:
+	scst_set_busy(cmd);
+	goto out;
+}
+
+static int blockio_flush(struct block_device *bdev)
+{
+	int res = 0;
+
+	res = blkdev_issue_flush(bdev, GFP_KERNEL, NULL, BLKDEV_IFL_WAIT);
+	if (res != 0)
+		PRINT_ERROR("blkdev_issue_flush() failed: %d", res);
+	return res;
+}
+
+static void vdisk_exec_verify(struct scst_cmd *cmd,
+	struct scst_vdisk_thr *thr, loff_t loff)
+{
+	mm_segment_t old_fs;
+	loff_t err;
+	ssize_t length, len_mem = 0;
+	uint8_t *address_sav, *address;
+	int compare;
+	struct scst_vdisk_dev *virt_dev =
+	    (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+	struct file *fd = thr->fd;
+	uint8_t *mem_verify = NULL;
+
+	if (vdisk_fsync(thr, loff, cmd->bufflen, cmd, cmd->dev) != 0)
+		goto out;
+
+	/*
+	 * Until the cache is cleared prior the verifying, there is not
+	 * much point in this code. ToDo.
+	 *
+	 * Nevertherless, this code is valuable if the data have not read
+	 * from the file/disk yet.
+	 */
+
+	/* SEEK */
+	old_fs = get_fs();
+	set_fs(get_ds());
+
+	if (!virt_dev->nullio) {
+		if (fd->f_op->llseek)
+			err = fd->f_op->llseek(fd, loff, 0/*SEEK_SET*/);
+		else
+			err = default_llseek(fd, loff, 0/*SEEK_SET*/);
+		if (err != loff) {
+			PRINT_ERROR("lseek trouble %lld != %lld",
+				    (long long unsigned int)err,
+				    (long long unsigned int)loff);
+			scst_set_cmd_error(cmd,
+				SCST_LOAD_SENSE(scst_sense_hardw_error));
+			goto out_set_fs;
+		}
+	}
+
+	mem_verify = vmalloc(LEN_MEM);
+	if (mem_verify == NULL) {
+		PRINT_ERROR("Unable to allocate memory %d for verify",
+			       LEN_MEM);
+		scst_set_cmd_error(cmd,
+				   SCST_LOAD_SENSE(scst_sense_hardw_error));
+		goto out_set_fs;
+	}
+
+	length = scst_get_buf_first(cmd, &address);
+	address_sav = address;
+	if (!length && cmd->data_len) {
+		length = cmd->data_len;
+		compare = 0;
+	} else
+		compare = 1;
+
+	while (length > 0) {
+		len_mem = (length > LEN_MEM) ? LEN_MEM : length;
+		TRACE_DBG("Verify: length %zd - len_mem %zd", length, len_mem);
+
+		if (!virt_dev->nullio)
+			err = vfs_read(fd, (char __force __user *)mem_verify,
+				len_mem, &fd->f_pos);
+		else
+			err = len_mem;
+		if ((err < 0) || (err < len_mem)) {
+			PRINT_ERROR("verify() returned %lld from %zd",
+				    (long long unsigned int)err, len_mem);
+			if (err == -EAGAIN)
+				scst_set_busy(cmd);
+			else {
+				scst_set_cmd_error(cmd,
+				    SCST_LOAD_SENSE(scst_sense_read_error));
+			}
+			if (compare)
+				scst_put_buf(cmd, address_sav);
+			goto out_set_fs;
+		}
+		if (compare && memcmp(address, mem_verify, len_mem) != 0) {
+			TRACE_DBG("Verify: error memcmp length %zd", length);
+			scst_set_cmd_error(cmd,
+			    SCST_LOAD_SENSE(scst_sense_miscompare_error));
+			scst_put_buf(cmd, address_sav);
+			goto out_set_fs;
+		}
+		length -= len_mem;
+		address += len_mem;
+		if (compare && length <= 0) {
+			scst_put_buf(cmd, address_sav);
+			length = scst_get_buf_next(cmd, &address);
+			address_sav = address;
+		}
+	}
+
+	if (length < 0) {
+		PRINT_ERROR("scst_get_buf_() failed: %zd", length);
+		scst_set_cmd_error(cmd,
+		    SCST_LOAD_SENSE(scst_sense_hardw_error));
+	}
+
+out_set_fs:
+	set_fs(old_fs);
+	if (mem_verify)
+		vfree(mem_verify);
+
+out:
+	return;
+}
+
+static int vdisk_task_mgmt_fn(struct scst_mgmt_cmd *mcmd,
+	struct scst_tgt_dev *tgt_dev)
+{
+
+	if ((mcmd->fn == SCST_LUN_RESET) || (mcmd->fn == SCST_TARGET_RESET)) {
+		/* Restore default values */
+		struct scst_device *dev = tgt_dev->dev;
+		struct scst_vdisk_dev *virt_dev =
+			(struct scst_vdisk_dev *)dev->dh_priv;
+
+		dev->tst = DEF_TST;
+		dev->d_sense = DEF_DSENSE;
+		if (virt_dev->wt_flag && !virt_dev->nv_cache)
+			dev->queue_alg = DEF_QUEUE_ALG_WT;
+		else
+			dev->queue_alg = DEF_QUEUE_ALG;
+		dev->swp = DEF_SWP;
+		dev->tas = DEF_TAS;
+
+		spin_lock(&virt_dev->flags_lock);
+		virt_dev->prevent_allow_medium_removal = 0;
+		spin_unlock(&virt_dev->flags_lock);
+	} else if (mcmd->fn == SCST_PR_ABORT_ALL) {
+		struct scst_device *dev = tgt_dev->dev;
+		struct scst_vdisk_dev *virt_dev =
+			(struct scst_vdisk_dev *)dev->dh_priv;
+		spin_lock(&virt_dev->flags_lock);
+		virt_dev->prevent_allow_medium_removal = 0;
+		spin_unlock(&virt_dev->flags_lock);
+	}
+	return SCST_DEV_TM_NOT_COMPLETED;
+}
+
+static void vdisk_report_registering(const struct scst_vdisk_dev *virt_dev)
+{
+	char buf[128];
+	int i, j;
+
+	i = snprintf(buf, sizeof(buf), "Registering virtual %s device %s ",
+		virt_dev->vdev_devt->name, virt_dev->name);
+	j = i;
+
+	if (virt_dev->wt_flag)
+		i += snprintf(&buf[i], sizeof(buf) - i, "(WRITE_THROUGH");
+
+	if (virt_dev->nv_cache)
+		i += snprintf(&buf[i], sizeof(buf) - i, "%sNV_CACHE",
+			(j == i) ? "(" : ", ");
+
+	if (virt_dev->rd_only)
+		i += snprintf(&buf[i], sizeof(buf) - i, "%sREAD_ONLY",
+			(j == i) ? "(" : ", ");
+
+	if (virt_dev->o_direct_flag)
+		i += snprintf(&buf[i], sizeof(buf) - i, "%sO_DIRECT",
+			(j == i) ? "(" : ", ");
+
+	if (virt_dev->nullio)
+		i += snprintf(&buf[i], sizeof(buf) - i, "%sNULLIO",
+			(j == i) ? "(" : ", ");
+
+	if (virt_dev->blockio)
+		i += snprintf(&buf[i], sizeof(buf) - i, "%sBLOCKIO",
+			(j == i) ? "(" : ", ");
+
+	if (virt_dev->removable)
+		i += snprintf(&buf[i], sizeof(buf) - i, "%sREMOVABLE",
+			(j == i) ? "(" : ", ");
+
+	if (j == i)
+		PRINT_INFO("%s", buf);
+	else
+		PRINT_INFO("%s)", buf);
+
+	return;
+}
+
+static int vdisk_resync_size(struct scst_vdisk_dev *virt_dev)
+{
+	loff_t file_size;
+	int res = 0;
+
+	BUG_ON(virt_dev->nullio);
+
+	res = vdisk_get_file_size(virt_dev->filename,
+			virt_dev->blockio, &file_size);
+	if (res != 0)
+		goto out;
+
+	if (file_size == virt_dev->file_size) {
+		PRINT_INFO("Size of virtual disk %s remained the same",
+			virt_dev->name);
+		goto out;
+	}
+
+	res = scst_suspend_activity(true);
+	if (res != 0)
+		goto out;
+
+	virt_dev->file_size = file_size;
+	virt_dev->nblocks = virt_dev->file_size >> virt_dev->block_shift;
+
+	scst_dev_del_all_thr_data(virt_dev->dev);
+
+	PRINT_INFO("New size of SCSI target virtual disk %s "
+		"(fs=%lldMB, bs=%d, nblocks=%lld, cyln=%lld%s)",
+		virt_dev->name, virt_dev->file_size >> 20,
+		virt_dev->block_size,
+		(long long unsigned int)virt_dev->nblocks,
+		(long long unsigned int)virt_dev->nblocks/64/32,
+		virt_dev->nblocks < 64*32 ? " !WARNING! cyln less "
+						"than 1" : "");
+
+	scst_capacity_data_changed(virt_dev->dev);
+
+	scst_resume_activity();
+
+out:
+	return res;
+}
+
+static int vdev_create(struct scst_dev_type *devt,
+	const char *name, struct scst_vdisk_dev **res_virt_dev)
+{
+	int res = 0;
+	struct scst_vdisk_dev *virt_dev;
+	uint64_t dev_id_num;
+	int dev_id_len;
+	char dev_id_str[17];
+	int32_t i;
+
+	virt_dev = kzalloc(sizeof(*virt_dev), GFP_KERNEL);
+	if (virt_dev == NULL) {
+		PRINT_ERROR("Allocation of virtual device %s failed",
+			devt->name);
+		res = -ENOMEM;
+		goto out;
+	}
+
+	spin_lock_init(&virt_dev->flags_lock);
+	virt_dev->vdev_devt = devt;
+
+	virt_dev->rd_only = DEF_RD_ONLY;
+	virt_dev->removable = DEF_REMOVABLE;
+
+	virt_dev->block_size = DEF_DISK_BLOCKSIZE;
+	virt_dev->block_shift = DEF_DISK_BLOCKSIZE_SHIFT;
+
+	if (strlen(name) >= sizeof(virt_dev->name)) {
+		PRINT_ERROR("Name %s is too long (max allowed %zd)", name,
+			sizeof(virt_dev->name)-1);
+		res = -EINVAL;
+		goto out_free;
+	}
+	strcpy(virt_dev->name, name);
+
+	dev_id_num = vdisk_gen_dev_id_num(virt_dev->name);
+	dev_id_len = scnprintf(dev_id_str, sizeof(dev_id_str), "%llx",
+				dev_id_num);
+
+	i = strlen(virt_dev->name) + 1; /* for ' ' */
+	memset(virt_dev->t10_dev_id, ' ', i + dev_id_len);
+	memcpy(virt_dev->t10_dev_id, virt_dev->name, i-1);
+	memcpy(virt_dev->t10_dev_id + i, dev_id_str, dev_id_len);
+	TRACE_DBG("t10_dev_id %s", virt_dev->t10_dev_id);
+
+	scnprintf(virt_dev->usn, sizeof(virt_dev->usn), "%llx", dev_id_num);
+	TRACE_DBG("usn %s", virt_dev->usn);
+
+	*res_virt_dev = virt_dev;
+
+out:
+	return res;
+
+out_free:
+	kfree(virt_dev);
+	goto out;
+}
+
+static void vdev_destroy(struct scst_vdisk_dev *virt_dev)
+{
+	kfree(virt_dev->filename);
+	kfree(virt_dev);
+	return;
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static struct scst_vdisk_dev *vdev_find(const char *name)
+{
+	struct scst_vdisk_dev *res, *vv;
+
+	res = NULL;
+	list_for_each_entry(vv, &vdev_list, vdev_list_entry) {
+		if (strcmp(vv->name, name) == 0) {
+			res = vv;
+			break;
+		}
+	}
+	return res;
+}
+
+static int vdev_parse_add_dev_params(struct scst_vdisk_dev *virt_dev,
+	char *params, const char *allowed_params[])
+{
+	int res = 0;
+	unsigned long val;
+	char *param, *p, *pp;
+
+	while (1) {
+		param = scst_get_next_token_str(&params);
+		if (param == NULL)
+			break;
+
+		p = scst_get_next_lexem(&param);
+		if (*p == '\0') {
+			PRINT_ERROR("Syntax error at %s (device %s)",
+				param, virt_dev->name);
+			res = -EINVAL;
+			goto out;
+		}
+
+		if (allowed_params != NULL) {
+			const char **a = allowed_params;
+			bool allowed = false;
+
+			while (*a != NULL) {
+				if (!strcasecmp(*a, p)) {
+					allowed = true;
+					break;
+				}
+				a++;
+			}
+
+			if (!allowed) {
+				PRINT_ERROR("Unknown parameter %s (device %s)", p,
+					virt_dev->name);
+				res = -EINVAL;
+				goto out;
+			}
+		}
+
+		pp = scst_get_next_lexem(&param);
+		if (*pp == '\0') {
+			PRINT_ERROR("Parameter %s value missed for device %s",
+				p, virt_dev->name);
+			res = -EINVAL;
+			goto out;
+		}
+
+		if (scst_get_next_lexem(&param)[0] != '\0') {
+			PRINT_ERROR("Too many parameter's %s values (device %s)",
+				p, virt_dev->name);
+			res = -EINVAL;
+			goto out;
+		}
+
+		if (!strcasecmp("filename", p)) {
+			if (*pp != '/') {
+				PRINT_ERROR("Filename %s must be global "
+					"(device %s)", pp, virt_dev->name);
+				res = -EINVAL;
+				goto out;
+			}
+
+			virt_dev->filename = kstrdup(pp, GFP_KERNEL);
+			if (virt_dev->filename == NULL) {
+				PRINT_ERROR("Unable to duplicate file name %s "
+					"(device %s)", pp, virt_dev->name);
+				res = -ENOMEM;
+				goto out;
+			}
+			continue;
+		}
+
+		res = strict_strtoul(pp, 0, &val);
+		if (res != 0) {
+			PRINT_ERROR("strict_strtoul() for %s failed: %d "
+				"(device %s)", pp, res, virt_dev->name);
+			goto out;
+		}
+
+		if (!strcasecmp("write_through", p)) {
+			virt_dev->wt_flag = val;
+			TRACE_DBG("WRITE THROUGH %d", virt_dev->wt_flag);
+		} else if (!strcasecmp("nv_cache", p)) {
+			virt_dev->nv_cache = val;
+			TRACE_DBG("NON-VOLATILE CACHE %d", virt_dev->nv_cache);
+		} else if (!strcasecmp("o_direct", p)) {
+#if 0
+			virt_dev->o_direct_flag = val;
+			TRACE_DBG("O_DIRECT %d", virt_dev->o_direct_flag);
+#else
+			PRINT_INFO("O_DIRECT flag doesn't currently"
+				" work, ignoring it, use fileio_tgt "
+				"in O_DIRECT mode instead (device %s)", virt_dev->name);
+#endif
+		} else if (!strcasecmp("read_only", p)) {
+			virt_dev->rd_only = val;
+			TRACE_DBG("READ ONLY %d", virt_dev->rd_only);
+		} else if (!strcasecmp("removable", p)) {
+			virt_dev->removable = val;
+			TRACE_DBG("REMOVABLE %d", virt_dev->removable);
+		} else if (!strcasecmp("blocksize", p)) {
+			virt_dev->block_size = val;
+			virt_dev->block_shift = scst_calc_block_shift(
+							virt_dev->block_size);
+			if (virt_dev->block_shift < 9) {
+				res = -EINVAL;
+				goto out;
+			}
+			TRACE_DBG("block_size %d, block_shift %d",
+				virt_dev->block_size,
+				virt_dev->block_shift);
+		} else {
+			PRINT_ERROR("Unknown parameter %s (device %s)", p,
+				virt_dev->name);
+			res = -EINVAL;
+			goto out;
+		}
+	}
+
+out:
+	return res;
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static int vdev_fileio_add_device(const char *device_name, char *params)
+{
+	int res = 0;
+	struct scst_vdisk_dev *virt_dev;
+
+	res = vdev_create(&vdisk_file_devtype, device_name, &virt_dev);
+	if (res != 0)
+		goto out;
+
+	virt_dev->command_set_version = 0x04C0; /* SBC-3 */
+
+	virt_dev->wt_flag = DEF_WRITE_THROUGH;
+	virt_dev->nv_cache = DEF_NV_CACHE;
+	virt_dev->o_direct_flag = DEF_O_DIRECT;
+
+	res = vdev_parse_add_dev_params(virt_dev, params, NULL);
+	if (res != 0)
+		goto out_destroy;
+
+	if (virt_dev->rd_only && (virt_dev->wt_flag || virt_dev->nv_cache)) {
+		PRINT_ERROR("Write options on read only device %s",
+			virt_dev->name);
+		res = -EINVAL;
+		goto out_destroy;
+	}
+
+	if (virt_dev->filename == NULL) {
+		PRINT_ERROR("File name required (device %s)", virt_dev->name);
+		res = -EINVAL;
+		goto out_destroy;
+	}
+
+	list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
+
+	vdisk_report_registering(virt_dev);
+
+	virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
+					virt_dev->name);
+	if (virt_dev->virt_id < 0) {
+		res = virt_dev->virt_id;
+		goto out_del;
+	}
+
+	TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
+		virt_dev->virt_id);
+
+out:
+	return res;
+
+out_del:
+	list_del(&virt_dev->vdev_list_entry);
+
+out_destroy:
+	vdev_destroy(virt_dev);
+	goto out;
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static int vdev_blockio_add_device(const char *device_name, char *params)
+{
+	int res = 0;
+	const char *allowed_params[] = { "filename", "read_only", "removable",
+					 "blocksize", "nv_cache", NULL };
+	struct scst_vdisk_dev *virt_dev;
+
+	res = vdev_create(&vdisk_blk_devtype, device_name, &virt_dev);
+	if (res != 0)
+		goto out;
+
+	virt_dev->command_set_version = 0x04C0; /* SBC-3 */
+
+	virt_dev->blockio = 1;
+
+	res = vdev_parse_add_dev_params(virt_dev, params, allowed_params);
+	if (res != 0)
+		goto out_destroy;
+
+	if (virt_dev->filename == NULL) {
+		PRINT_ERROR("File name required (device %s)", virt_dev->name);
+		res = -EINVAL;
+		goto out_destroy;
+	}
+
+	list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
+
+	vdisk_report_registering(virt_dev);
+
+	virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
+					virt_dev->name);
+	if (virt_dev->virt_id < 0) {
+		res = virt_dev->virt_id;
+		goto out_del;
+	}
+
+	TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
+		virt_dev->virt_id);
+
+out:
+	return res;
+
+out_del:
+	list_del(&virt_dev->vdev_list_entry);
+
+out_destroy:
+	vdev_destroy(virt_dev);
+	goto out;
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static int vdev_nullio_add_device(const char *device_name, char *params)
+{
+	int res = 0;
+	const char *allowed_params[] = { "read_only", "removable",
+					 "blocksize", NULL };
+	struct scst_vdisk_dev *virt_dev;
+
+	res = vdev_create(&vdisk_null_devtype, device_name, &virt_dev);
+	if (res != 0)
+		goto out;
+
+	virt_dev->command_set_version = 0x04C0; /* SBC-3 */
+
+	virt_dev->nullio = 1;
+
+	res = vdev_parse_add_dev_params(virt_dev, params, allowed_params);
+	if (res != 0)
+		goto out_destroy;
+
+	list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
+
+	vdisk_report_registering(virt_dev);
+
+	virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
+					virt_dev->name);
+	if (virt_dev->virt_id < 0) {
+		res = virt_dev->virt_id;
+		goto out_del;
+	}
+
+	TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
+		virt_dev->virt_id);
+
+out:
+	return res;
+
+out_del:
+	list_del(&virt_dev->vdev_list_entry);
+
+out_destroy:
+	vdev_destroy(virt_dev);
+	goto out;
+}
+
+static ssize_t vdisk_add_fileio_device(const char *device_name, char *params)
+{
+	int res;
+
+	if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
+	res = vdev_fileio_add_device(device_name, params);
+
+	mutex_unlock(&scst_vdisk_mutex);
+
+out:
+	return res;
+}
+
+static ssize_t vdisk_add_blockio_device(const char *device_name, char *params)
+{
+	int res;
+
+	if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
+	res = vdev_blockio_add_device(device_name, params);
+
+	mutex_unlock(&scst_vdisk_mutex);
+
+out:
+	return res;
+
+}
+
+static ssize_t vdisk_add_nullio_device(const char *device_name, char *params)
+{
+	int res;
+
+	if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
+	res = vdev_nullio_add_device(device_name, params);
+
+	mutex_unlock(&scst_vdisk_mutex);
+
+out:
+	return res;
+
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static void vdev_del_device(struct scst_vdisk_dev *virt_dev)
+{
+
+	scst_unregister_virtual_device(virt_dev->virt_id);
+
+	list_del(&virt_dev->vdev_list_entry);
+
+	PRINT_INFO("Virtual device %s unregistered", virt_dev->name);
+	TRACE_DBG("virt_id %d unregistered", virt_dev->virt_id);
+
+	vdev_destroy(virt_dev);
+
+	return;
+}
+
+static ssize_t vdisk_del_device(const char *device_name)
+{
+	int res = 0;
+	struct scst_vdisk_dev *virt_dev;
+
+	if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
+	virt_dev = vdev_find(device_name);
+	if (virt_dev == NULL) {
+		PRINT_ERROR("Device %s not found", device_name);
+		res = -EINVAL;
+		goto out_unlock;
+	}
+
+	vdev_del_device(virt_dev);
+
+out_unlock:
+	mutex_unlock(&scst_vdisk_mutex);
+
+out:
+	return res;
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static ssize_t __vcdrom_add_device(const char *device_name, char *params)
+{
+	int res = 0;
+	const char *allowed_params[] = { NULL }; /* no params */
+	struct scst_vdisk_dev *virt_dev;
+
+	res = vdev_create(&vcdrom_devtype, device_name, &virt_dev);
+	if (res != 0)
+		goto out;
+
+	virt_dev->command_set_version = 0x02A0; /* MMC-3 */
+
+	virt_dev->rd_only = 1;
+	virt_dev->removable = 1;
+	virt_dev->cdrom_empty = 1;
+
+	virt_dev->block_size = DEF_CDROM_BLOCKSIZE;
+	virt_dev->block_shift = DEF_CDROM_BLOCKSIZE_SHIFT;
+
+	res = vdev_parse_add_dev_params(virt_dev, params, allowed_params);
+	if (res != 0)
+		goto out_destroy;
+
+	list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
+
+	vdisk_report_registering(virt_dev);
+
+	virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
+					virt_dev->name);
+	if (virt_dev->virt_id < 0) {
+		res = virt_dev->virt_id;
+		goto out_del;
+	}
+
+	TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
+		virt_dev->virt_id);
+
+out:
+	return res;
+
+out_del:
+	list_del(&virt_dev->vdev_list_entry);
+
+out_destroy:
+	vdev_destroy(virt_dev);
+	goto out;
+}
+
+static ssize_t vcdrom_add_device(const char *device_name, char *params)
+{
+	int res;
+
+	if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
+	res = __vcdrom_add_device(device_name, params);
+
+	mutex_unlock(&scst_vdisk_mutex);
+
+out:
+	return res;
+
+}
+
+static ssize_t vcdrom_del_device(const char *device_name)
+{
+	int res = 0;
+	struct scst_vdisk_dev *virt_dev;
+
+	if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+		res = -EINTR;
+		goto out;
+	}
+
+	virt_dev = vdev_find(device_name);
+	if (virt_dev == NULL) {
+		PRINT_ERROR("Device %s not found", device_name);
+		res = -EINVAL;
+		goto out_unlock;
+	}
+
+	vdev_del_device(virt_dev);
+
+out_unlock:
+	mutex_unlock(&scst_vdisk_mutex);
+
+out:
+	return res;
+}
+
+static int vcdrom_change(struct scst_vdisk_dev *virt_dev,
+	char *buffer)
+{
+	loff_t err;
+	char *old_fn, *p, *pp;
+	const char *filename = NULL;
+	int length = strlen(buffer);
+	int res = 0;
+
+	p = buffer;
+
+	while (isspace(*p) && *p != '\0')
+		p++;
+	filename = p;
+	p = &buffer[length-1];
+	pp = &buffer[length];
+	while (isspace(*p) && (*p != '\0')) {
+		pp = p;
+		p--;
+	}
+	*pp = '\0';
+
+	res = scst_suspend_activity(true);
+	if (res != 0)
+		goto out;
+
+	/* To sync with detach*() functions */
+	mutex_lock(&scst_mutex);
+
+	if (*filename == '\0') {
+		virt_dev->cdrom_empty = 1;
+		TRACE_DBG("%s", "No media");
+	} else if (*filename != '/') {
+		PRINT_ERROR("File path \"%s\" is not absolute", filename);
+		res = -EINVAL;
+		goto out_unlock;
+	} else
+		virt_dev->cdrom_empty = 0;
+
+	old_fn = virt_dev->filename;
+
+	if (!virt_dev->cdrom_empty) {
+		int len = strlen(filename) + 1;
+		char *fn = kmalloc(len, GFP_KERNEL);
+		if (fn == NULL) {
+			TRACE(TRACE_OUT_OF_MEM, "%s",
+				"Allocation of filename failed");
+			res = -ENOMEM;
+			goto out_unlock;
+		}
+
+		strlcpy(fn, filename, len);
+		virt_dev->filename = fn;
+
+		res = vdisk_get_file_size(virt_dev->filename,
+				virt_dev->blockio, &err);
+		if (res != 0)
+			goto out_free_fn;
+	} else {
+		err = 0;
+		virt_dev->filename = NULL;
+	}
+
+	if (virt_dev->prevent_allow_medium_removal) {
+		PRINT_ERROR("Prevent medium removal for "
+			"virtual device with name %s", virt_dev->name);
+		res = -EINVAL;
+		goto out_free_fn;
+	}
+
+	virt_dev->file_size = err;
+	virt_dev->nblocks = virt_dev->file_size >> virt_dev->block_shift;
+	if (!virt_dev->cdrom_empty)
+		virt_dev->media_changed = 1;
+
+	mutex_unlock(&scst_mutex);
+
+	scst_dev_del_all_thr_data(virt_dev->dev);
+
+	if (!virt_dev->cdrom_empty) {
+		PRINT_INFO("Changed SCSI target virtual cdrom %s "
+			"(file=\"%s\", fs=%lldMB, bs=%d, nblocks=%lld,"
+			" cyln=%lld%s)", virt_dev->name,
+			vdev_get_filename(virt_dev),
+			virt_dev->file_size >> 20, virt_dev->block_size,
+			(long long unsigned int)virt_dev->nblocks,
+			(long long unsigned int)virt_dev->nblocks/64/32,
+			virt_dev->nblocks < 64*32 ? " !WARNING! cyln less "
+							"than 1" : "");
+	} else {
+		PRINT_INFO("Removed media from SCSI target virtual cdrom %s",
+			virt_dev->name);
+	}
+
+	kfree(old_fn);
+
+out_resume:
+	scst_resume_activity();
+
+out:
+	return res;
+
+out_free_fn:
+	kfree(virt_dev->filename);
+	virt_dev->filename = old_fn;
+
+out_unlock:
+	mutex_unlock(&scst_mutex);
+	goto out_resume;
+}
+
+static int vcdrom_sysfs_process_filename_store(struct scst_sysfs_work_item *work)
+{
+	int res;
+	struct scst_device *dev = work->dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	/* It's safe, since we taken dev_kobj and dh_priv NULLed in attach() */
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	res = vcdrom_change(virt_dev, work->buf);
+
+	kobject_put(&dev->dev_kobj);
+	return res;
+}
+
+static ssize_t vcdrom_sysfs_filename_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res;
+	char *i_buf;
+	struct scst_sysfs_work_item *work;
+	struct scst_device *dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+
+	i_buf = kmalloc(count+1, GFP_KERNEL);
+	if (i_buf == NULL) {
+		PRINT_ERROR("Unable to alloc intermediate buffer with size %zd",
+			count+1);
+		res = -ENOMEM;
+		goto out;
+	}
+	memcpy(i_buf, buf, count);
+	i_buf[count] = '\0';
+
+	res = scst_alloc_sysfs_work(vcdrom_sysfs_process_filename_store, &work);
+	if (res != 0)
+		goto out_free;
+
+	work->buf = i_buf;
+	work->dev = dev;
+
+	kobject_get(&dev->dev_kobj);
+
+	res = scst_sysfs_queue_wait_work(work);
+	if (res == 0)
+		res = count;
+
+out:
+	return res;
+
+out_free:
+	kfree(i_buf);
+	goto out;
+}
+
+static ssize_t vdev_sysfs_size_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	pos = sprintf(buf, "%lld\n", virt_dev->file_size / 1024 / 1024);
+	return pos;
+}
+
+static ssize_t vdisk_sysfs_blocksize_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	pos = sprintf(buf, "%d\n%s", (int)virt_dev->block_size,
+		(virt_dev->block_size == DEF_DISK_BLOCKSIZE) ? "" :
+			SCST_SYSFS_KEY_MARK "\n");
+	return pos;
+}
+
+static ssize_t vdisk_sysfs_rd_only_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	pos = sprintf(buf, "%d\n%s", virt_dev->rd_only ? 1 : 0,
+		(virt_dev->rd_only == DEF_RD_ONLY) ? "" :
+			SCST_SYSFS_KEY_MARK "");
+	return pos;
+}
+
+static ssize_t vdisk_sysfs_wt_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	pos = sprintf(buf, "%d\n%s", virt_dev->wt_flag ? 1 : 0,
+		(virt_dev->wt_flag == DEF_WRITE_THROUGH) ? "" :
+			SCST_SYSFS_KEY_MARK "");
+	return pos;
+}
+
+static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	pos = sprintf(buf, "%d\n%s", virt_dev->nv_cache ? 1 : 0,
+		(virt_dev->nv_cache == DEF_NV_CACHE) ? "" :
+			SCST_SYSFS_KEY_MARK "");
+	return pos;
+}
+
+static ssize_t vdisk_sysfs_o_direct_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	pos = sprintf(buf, "%d\n%s", virt_dev->o_direct_flag ? 1 : 0,
+		(virt_dev->o_direct_flag == DEF_O_DIRECT) ? "" :
+			SCST_SYSFS_KEY_MARK "");
+	return pos;
+}
+
+static ssize_t vdisk_sysfs_removable_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	pos = sprintf(buf, "%d\n", virt_dev->removable ? 1 : 0);
+
+	if ((virt_dev->dev->type != TYPE_ROM) &&
+	    (virt_dev->removable != DEF_REMOVABLE))
+		pos += sprintf(&buf[pos], "%s\n", SCST_SYSFS_KEY_MARK);
+	return pos;
+}
+
+static int vdev_sysfs_process_get_filename(struct scst_sysfs_work_item *work)
+{
+	int res = 0;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = work->dev;
+
+	if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+		res = -EINTR;
+		goto out_put;
+	}
+
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	if (virt_dev == NULL)
+		goto out_unlock;
+
+	if (virt_dev->filename != NULL)
+		work->res_buf = kasprintf(GFP_KERNEL, "%s\n%s\n",
+			vdev_get_filename(virt_dev), SCST_SYSFS_KEY_MARK);
+	else
+		work->res_buf = kasprintf(GFP_KERNEL, "%s\n",
+					vdev_get_filename(virt_dev));
+
+out_unlock:
+	mutex_unlock(&scst_vdisk_mutex);
+
+out_put:
+	kobject_put(&dev->dev_kobj);
+	return res;
+}
+
+static ssize_t vdev_sysfs_filename_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int res = 0;
+	struct scst_device *dev;
+	struct scst_sysfs_work_item *work;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+
+	res = scst_alloc_sysfs_work(vdev_sysfs_process_get_filename, &work);
+	if (res != 0)
+		goto out;
+
+	work->dev = dev;
+
+	kobject_get(&dev->dev_kobj);
+
+	scst_sysfs_work_get(work);
+
+	res = scst_sysfs_queue_wait_work(work);
+	if (res != 0)
+		goto out_put;
+
+	res = snprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s\n", work->res_buf);
+
+out_put:
+	scst_sysfs_work_put(work);
+
+out:
+	return res;
+}
+
+static int vdisk_sysfs_process_resync_size_store(
+	struct scst_sysfs_work_item *work)
+{
+	int res;
+	struct scst_device *dev = work->dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	/* It's safe, since we taken dev_kobj and dh_priv NULLed in attach() */
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	res = vdisk_resync_size(virt_dev);
+
+	kobject_put(&dev->dev_kobj);
+	return res;
+}
+
+static ssize_t vdisk_sysfs_resync_size_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res;
+	struct scst_device *dev;
+	struct scst_sysfs_work_item *work;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+
+	res = scst_alloc_sysfs_work(vdisk_sysfs_process_resync_size_store,
+					&work);
+	if (res != 0)
+		goto out;
+
+	work->dev = dev;
+
+	kobject_get(&dev->dev_kobj);
+
+	res = scst_sysfs_queue_wait_work(work);
+	if (res == 0)
+		res = count;
+
+out:
+	return res;
+}
+
+static ssize_t vdev_sysfs_t10_dev_id_store(struct kobject *kobj,
+	struct kobj_attribute *attr, const char *buf, size_t count)
+{
+	int res, i;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	write_lock_bh(&vdisk_t10_dev_id_rwlock);
+
+	if ((count > sizeof(virt_dev->t10_dev_id)) ||
+	    ((count == sizeof(virt_dev->t10_dev_id)) &&
+	     (buf[count-1] != '\n'))) {
+		PRINT_ERROR("T10 device id is too long (max %zd "
+			"characters)", sizeof(virt_dev->t10_dev_id)-1);
+		res = -EINVAL;
+		goto out_unlock;
+	}
+
+	memset(virt_dev->t10_dev_id, 0, sizeof(virt_dev->t10_dev_id));
+	memcpy(virt_dev->t10_dev_id, buf, count);
+
+	i = 0;
+	while (i < sizeof(virt_dev->t10_dev_id)) {
+		if (virt_dev->t10_dev_id[i] == '\n') {
+			virt_dev->t10_dev_id[i] = '\0';
+			break;
+		}
+		i++;
+	}
+
+	virt_dev->t10_dev_id_set = 1;
+
+	res = count;
+
+	PRINT_INFO("T10 device id for device %s changed to %s", virt_dev->name,
+		virt_dev->t10_dev_id);
+
+out_unlock:
+	write_unlock_bh(&vdisk_t10_dev_id_rwlock);
+	return res;
+}
+
+static ssize_t vdev_sysfs_t10_dev_id_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	read_lock_bh(&vdisk_t10_dev_id_rwlock);
+	pos = sprintf(buf, "%s\n%s", virt_dev->t10_dev_id,
+		virt_dev->t10_dev_id_set ? SCST_SYSFS_KEY_MARK "\n" : "");
+	read_unlock_bh(&vdisk_t10_dev_id_rwlock);
+	return pos;
+}
+
+static ssize_t vdev_sysfs_usn_show(struct kobject *kobj,
+	struct kobj_attribute *attr, char *buf)
+{
+	int pos = 0;
+	struct scst_device *dev;
+	struct scst_vdisk_dev *virt_dev;
+
+	dev = container_of(kobj, struct scst_device, dev_kobj);
+	virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+	pos = sprintf(buf, "%s\n", virt_dev->usn);
+	return pos;
+}
+
+static int __init init_scst_vdisk(struct scst_dev_type *devtype)
+{
+	int res = 0;
+
+	devtype->module = THIS_MODULE;
+
+	res = scst_register_virtual_dev_driver(devtype);
+	if (res < 0)
+		goto out;
+
+out:
+	return res;
+
+}
+
+static void exit_scst_vdisk(struct scst_dev_type *devtype)
+{
+
+	mutex_lock(&scst_vdisk_mutex);
+	while (1) {
+		struct scst_vdisk_dev *virt_dev;
+
+		if (list_empty(&vdev_list))
+			break;
+
+		virt_dev = list_entry(vdev_list.next, typeof(*virt_dev),
+				vdev_list_entry);
+
+		vdev_del_device(virt_dev);
+	}
+	mutex_unlock(&scst_vdisk_mutex);
+
+	scst_unregister_virtual_dev_driver(devtype);
+	return;
+}
+
+static int __init init_scst_vdisk_driver(void)
+{
+	int res;
+
+	vdisk_thr_cachep = KMEM_CACHE(scst_vdisk_thr, SCST_SLAB_FLAGS);
+	if (vdisk_thr_cachep == NULL) {
+		res = -ENOMEM;
+		goto out;
+	}
+
+	blockio_work_cachep = KMEM_CACHE(scst_blockio_work, SCST_SLAB_FLAGS);
+	if (blockio_work_cachep == NULL) {
+		res = -ENOMEM;
+		goto out_free_vdisk_cache;
+	}
+
+	if (num_threads < 1) {
+		PRINT_ERROR("num_threads can not be less than 1, use "
+			"default %d", DEF_NUM_THREADS);
+		num_threads = DEF_NUM_THREADS;
+	}
+
+	vdisk_file_devtype.threads_num = num_threads;
+	vcdrom_devtype.threads_num = num_threads;
+
+	atomic_set(&nullio_thr_data.hdr.ref, 1); /* never destroy it */
+
+	res = init_scst_vdisk(&vdisk_file_devtype);
+	if (res != 0)
+		goto out_free_slab;
+
+	res = init_scst_vdisk(&vdisk_blk_devtype);
+	if (res != 0)
+		goto out_free_vdisk;
+
+	res = init_scst_vdisk(&vdisk_null_devtype);
+	if (res != 0)
+		goto out_free_blk;
+
+	res = init_scst_vdisk(&vcdrom_devtype);
+	if (res != 0)
+		goto out_free_null;
+
+out:
+	return res;
+
+out_free_null:
+	exit_scst_vdisk(&vdisk_null_devtype);
+
+out_free_blk:
+	exit_scst_vdisk(&vdisk_blk_devtype);
+
+out_free_vdisk:
+	exit_scst_vdisk(&vdisk_file_devtype);
+
+out_free_slab:
+	kmem_cache_destroy(blockio_work_cachep);
+
+out_free_vdisk_cache:
+	kmem_cache_destroy(vdisk_thr_cachep);
+	goto out;
+}
+
+static void __exit exit_scst_vdisk_driver(void)
+{
+	exit_scst_vdisk(&vdisk_null_devtype);
+	exit_scst_vdisk(&vdisk_blk_devtype);
+	exit_scst_vdisk(&vdisk_file_devtype);
+	exit_scst_vdisk(&vcdrom_devtype);
+
+	kmem_cache_destroy(blockio_work_cachep);
+	kmem_cache_destroy(vdisk_thr_cachep);
+}
+
+module_init(init_scst_vdisk_driver);
+module_exit(exit_scst_vdisk_driver);
+
+MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SCSI disk (type 0) and CDROM (type 5) dev handler for "
+	"SCST using files on file systems or block devices");
+MODULE_VERSION(SCST_VERSION_STRING);

--
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [SCSI Target Devel]     [Linux SCSI Target Infrastructure]     [Kernel Newbies]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Linux IIO]     [Samba]     [Device Mapper]
  Powered by Linux