From 2830abc3935294eba621b781f72aecda604c65a7 Mon Sep 17 00:00:00 2001
From: Ronnie Sahlberg <ronniesahlberg@gmail.com>
Date: Sun, 1 Apr 2012 08:04:06 +1000
Subject: [PATCH 2/2] SBC UNMAP: Add support for thin-provisioning and the UNMAP command.

The UNMAP command is implemented using FALLOC_FL_PUNCH_HOLE and will
release UNMAPPED blocks back to the underlying filesystem.

FALLOC_FL_PUNCH_HOLE is fairly new addition to Linux but works on
ext4 and XFS filesystems currently.

Signed-off-by: Ronnie Sahlberg <ronniesahlberg@gmail.com>
---
 doc/tgtadm.8.xml |   25 ++++++++++++++
 usr/bs_rdwr.c    |   97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 usr/sbc.c        |   76 +++++++++++++++++++++++++++++++++++++++++-
 usr/scsi.h       |    1 +
 usr/spc.c        |   43 +++++++++++++++++++++--
 usr/target.c     |    2 +
 usr/tgtd.h       |    2 +
 7 files changed, 241 insertions(+), 5 deletions(-)

diff --git a/doc/tgtadm.8.xml b/doc/tgtadm.8.xml
index 668e184..a40f659 100644
--- a/doc/tgtadm.8.xml
+++ b/doc/tgtadm.8.xml
@@ -352,6 +352,31 @@ tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 \
          --params readonly=1
       </screen>
 
+      <varlistentry><term><option>thin_provisioning=&lt;0|1&gt;</option></term>
+        <listitem>
+          <para>
+	    This controls the provisioning for the LUN. A thin-provisioned
+	    LUN is represented as a sparse file.
+	    TGTD supports provisioning type 2 for sparse files.
+	    When initiators use the SCSI UNMAP command TGTD will release
+	    the affected areas back to the filesystem using 
+	    FALLOC_FL_PUNCH_HOLE.
+          </para>
+          <para>
+	    This parameter only applies to DISK devices.
+          </para>
+          <para>
+	    Thin-provisioning only works for LUNs stored on filesystems
+	    that support FALLOC_FL_PUNCH_HOLE.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <screen format="linespecific">
+tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 \
+         --params thin_provisioning=1
+      </screen>
+
     </variablelist>
   </refsect1>
 
diff --git a/usr/bs_rdwr.c b/usr/bs_rdwr.c
index 84ed278..7d614f8 100644
--- a/usr/bs_rdwr.c
+++ b/usr/bs_rdwr.c
@@ -27,6 +27,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <unistd.h>
 
 #include <linux/fs.h>
@@ -63,6 +65,7 @@ static void bs_rdwr_request(struct scsi_cmd *cmd)
 	uint8_t key;
 	uint16_t asc;
 	char *tmpbuf;
+	struct stat st;
 
 	ret = length = 0;
 	key = asc = 0;
@@ -160,6 +163,100 @@ static void bs_rdwr_request(struct scsi_cmd *cmd)
 
 		free(tmpbuf);
 		break;
+	case UNMAP:
+		if (!cmd->dev->attrs.thinprovisioning) {
+			result = SAM_STAT_CHECK_CONDITION;
+			key = ILLEGAL_REQUEST;
+			asc = ASC_INVALID_FIELD_IN_CDB;
+			break;
+		}
+
+		if (fstat(fd, &st) != 0) {
+			eprintf("Failed to fstat() file for UNMAP\n");
+			result = SAM_STAT_CHECK_CONDITION;
+			key = HARDWARE_ERROR;
+			asc = ASC_INTERNAL_TGT_FAILURE;
+			break;
+		}
+		
+		length = scsi_get_out_length(cmd);
+		tmpbuf = scsi_get_out_buffer(cmd);
+
+		if (length < 8)
+			break;
+
+		length -= 8;
+		tmpbuf += 8;
+
+		while (length >= 16) {
+			uint64_t offset;
+			uint64_t len;
+
+			offset = get_unaligned_be64(&tmpbuf[0]);
+			offset = offset << cmd->dev->blk_shift;
+
+			len = get_unaligned_be32(&tmpbuf[8]);
+			len = len << cmd->dev->blk_shift;
+
+			if (offset + len > st.st_size) {
+				eprintf("UNMAP beyond EOF\n");
+				result = SAM_STAT_CHECK_CONDITION;
+				key = ILLEGAL_REQUEST;
+				asc = ASC_LBA_OUT_OF_RANGE;
+				break;
+			}
+
+			/* we can only punch holes aligned for full blocks.
+			 * if not aligned to a block boundary we overwrite
+			 * the first partial block with zero
+			 */
+			if (len > 0 && offset % st.st_blksize) {
+				char *buf;
+				uint64_t l;
+
+				l = st.st_blksize - offset % st.st_blksize;
+				if (l > len)
+					l = len;
+
+				buf = zalloc(l);
+				pwrite64(fd, buf, l, offset);
+				free(buf);
+				
+				offset += l;
+				len -= l;
+			}
+
+			if (len >= st.st_blksize) {
+				if (unmap_file_region(fd, offset,
+					len - len % st.st_blksize) != 0) {
+					eprintf("Failed to punch hole for"
+						" UNMAP at offset:" PRIu64
+						" length: " PRIu64 "\n",
+						offset,
+						len - len % st.st_blksize);
+					result = SAM_STAT_CHECK_CONDITION;
+					key = HARDWARE_ERROR;
+					asc = ASC_INTERNAL_TGT_FAILURE;
+					break;
+				}
+				offset += len - len % st.st_blksize;
+				len     = len % st.st_blksize;
+
+			}
+
+			/* zero the tail if there is one */
+			if (len > 0) {
+				char *buf;
+
+				buf = zalloc(len);
+				pwrite64(fd, buf, len, offset);
+				free(buf);
+			}
+
+			length -= 16;
+			tmpbuf += 16;
+		}
+		break;
 	default:
 		break;
 	}
diff --git a/usr/sbc.c b/usr/sbc.c
index 0c3681e..c93d5de 100644
--- a/usr/sbc.c
+++ b/usr/sbc.c
@@ -141,6 +141,60 @@ sense:
 	return SAM_STAT_CHECK_CONDITION;
 }
 
+static int sbc_unmap(int host_no, struct scsi_cmd *cmd)
+{
+	int ret;
+	unsigned char key = ILLEGAL_REQUEST;
+	uint16_t asc = ASC_LUN_NOT_SUPPORTED;
+	struct scsi_lu *lu = cmd->dev;
+	int anchor;
+
+	ret = device_reserved(cmd);
+	if (ret)
+		return SAM_STAT_RESERVATION_CONFLICT;
+
+	/* We dont support anchored blocks */
+	anchor = cmd->scb[1] & 0x01;
+	if (anchor) {
+		key = ILLEGAL_REQUEST;
+		asc = ASC_INVALID_FIELD_IN_CDB;
+		goto sense;
+	}
+
+	if (!lu->attrs.thinprovisioning) {
+		key = ILLEGAL_REQUEST;
+		asc = ASC_INVALID_OP_CODE;
+		goto sense;
+	}
+
+	if (lu->attrs.removable && !lu->attrs.online) {
+		key = NOT_READY;
+		asc = ASC_MEDIUM_NOT_PRESENT;
+		goto sense;
+	}
+
+	if (lu->attrs.readonly) {
+		key = DATA_PROTECT;
+		asc = ASC_WRITE_PROTECT;
+		goto sense;
+	}
+
+	ret = cmd->dev->bst->bs_cmd_submit(cmd);
+	if (ret) {
+		key = HARDWARE_ERROR;
+		asc = ASC_INTERNAL_TGT_FAILURE;
+		goto sense;
+	}
+
+sense:
+	cmd->offset = 0;
+	scsi_set_in_resid_by_actual(cmd, 0);
+	scsi_set_out_resid_by_actual(cmd, 0);
+
+	sense_data_build(cmd, key, asc);
+	return SAM_STAT_CHECK_CONDITION;
+}
+
 static int sbc_rw(int host_no, struct scsi_cmd *cmd)
 {
 	int ret;
@@ -370,8 +424,11 @@ static int sbc_service_action(int host_no, struct scsi_cmd *cmd)
 	data[2] = __cpu_to_be32(1UL << bshift);
 
 	val = (cmd->dev->attrs.lbppbe << 16) | cmd->dev->attrs.la_lba;
+	if (cmd->dev->attrs.thinprovisioning)
+		val |= (3 << 14); /* set LBPME and LBPRZ */
 	data[3] = __cpu_to_be32(val);
 
+
 overflow:
 	scsi_set_in_resid_by_actual(cmd, len);
 	return SAM_STAT_GOOD;
@@ -549,7 +606,24 @@ static struct device_type_template sbc_template = {
 		{spc_illegal_op,},
 		{spc_illegal_op,},
 
-		[0x40 ... 0x4f] = {spc_illegal_op,},
+		/* 0x40 */
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+		{sbc_unmap,},
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+		{spc_illegal_op,},
+		{spc_illegal_op,},
 
 		/* 0x50 */
 		{spc_illegal_op,},
diff --git a/usr/scsi.h b/usr/scsi.h
index ca1109a..1508cc6 100644
--- a/usr/scsi.h
+++ b/usr/scsi.h
@@ -60,6 +60,7 @@
 #define WRITE_LONG            0x3f
 #define CHANGE_DEFINITION     0x40
 #define WRITE_SAME            0x41
+#define UNMAP		      0x42
 #define READ_TOC              0x43
 #define LOG_SELECT            0x4c
 #define LOG_SENSE             0x4d
diff --git a/usr/spc.c b/usr/spc.c
index 44cd193..1834038 100644
--- a/usr/spc.c
+++ b/usr/spc.c
@@ -139,6 +139,24 @@ static void update_vpd_83(struct scsi_lu *lu, void *id)
 	strncpy((char *)data + 4, id, SCSI_ID_LEN);
 }
 
+static void update_vpd_b2(struct scsi_lu *lu, void *id)
+{
+	struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0xb2)];
+	uint8_t	*data = vpd_pg->data;
+
+	if (lu->attrs.thinprovisioning) {
+		data[0] = 0;		/* threshold exponent */
+		data[1] = 0x84;		/* LBPU LBPRZ */
+		data[2] = 0x02;		/* provisioning type */
+		data[3] = 0;
+	} else {
+		data[0] = 0;
+		data[1] = 0;
+		data[2] = 0;
+		data[3] = 0;
+	}
+}
+
 static void update_b0_opt_xfer_gran(struct scsi_lu *lu, int opt_xfer_gran)
 {
 	struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0xb0)];
@@ -1661,7 +1679,7 @@ enum {
 	Opt_removable, Opt_readonly, Opt_online,
 	Opt_mode_page,
 	Opt_path,
-	Opt_bsoflags,
+	Opt_bsoflags, Opt_thinprovisioning,
 	Opt_err,
 };
 
@@ -1682,6 +1700,7 @@ static match_table_t tokens = {
 	{Opt_mode_page, "mode_page=%s"},
 	{Opt_path, "path=%s"},
 	{Opt_bsoflags, "bsoflags=%s"},
+	{Opt_thinprovisioning, "thin_provisioning=%s"},
 	{Opt_err, NULL},
 };
 
@@ -1772,6 +1791,12 @@ tgtadm_err lu_config(struct scsi_lu *lu, char *params, match_fn_t *fn)
 			match_strncpy(buf, &args[0], sizeof(buf));
 			attrs->readonly = atoi(buf);
 			break;
+		case Opt_thinprovisioning:
+			match_strncpy(buf, &args[0], sizeof(buf));
+			attrs->thinprovisioning = atoi(buf);
+			/* update the provisioning vpd page */
+			lu_vpd[PCODE_OFFSET(0xb2)]->vpd_update(lu, NULL);
+			break;
 		case Opt_online:
 			match_strncpy(buf, &args[0], sizeof(buf));
 			if (atoi(buf))
@@ -1807,6 +1832,10 @@ int spc_lu_init(struct scsi_lu *lu)
 
 	lu->attrs.device_type = lu->dev_type_template.type;
 	lu->attrs.qualifier = 0x0;
+	lu->attrs.thinprovisioning = 0;
+	lu->attrs.removable = 0;
+	lu->attrs.readonly = 0;
+	lu->attrs.sense_format = 0;
 
 	snprintf(lu->attrs.vendor_id, sizeof(lu->attrs.vendor_id),
 		 "%-16s", VENDOR_ID);
@@ -1839,9 +1868,15 @@ int spc_lu_init(struct scsi_lu *lu)
 	if (!lu_vpd[pg])
 		return -ENOMEM;
 
-	lu->attrs.removable = 0;
-	lu->attrs.readonly = 0;
-	lu->attrs.sense_format = 0;
+	/* VPD page 0xb2 LOGICAL BLOCK PROVISIONING*/
+	pg = PCODE_OFFSET(0xb2);
+	lu_vpd[pg] = alloc_vpd(LBP_VPD_LEN);
+	if (!lu_vpd[pg])
+		return -ENOMEM;
+	lu_vpd[pg]->vpd_update = update_vpd_b2;
+	lu_vpd[pg]->vpd_update(lu, NULL);
+
+
 	lu->dev_type_template.lu_offline(lu);
 
 	return 0;
diff --git a/usr/target.c b/usr/target.c
index 0b6be13..dd3ca91 100644
--- a/usr/target.c
+++ b/usr/target.c
@@ -1857,6 +1857,7 @@ tgtadm_err tgt_target_show_all(struct concat_buf *b)
 				 _TAB3 "Removable media: %s\n"
 				 _TAB3 "Prevent removal: %s\n"
 				 _TAB3 "Readonly: %s\n"
+				 _TAB3 "Thin-provisioning: %s\n"
 				 _TAB3 "Backing store type: %s\n"
 				 _TAB3 "Backing store path: %s\n"
 				 _TAB3 "Backing store flags: %s\n",
@@ -1871,6 +1872,7 @@ tgtadm_err tgt_target_show_all(struct concat_buf *b)
 				 lu_prevent_removal(lu) ?
 					"Yes" : "No",
 				 lu->attrs.readonly ? "Yes" : "No",
+				 lu->attrs.thinprovisioning ? "Yes" : "No",
 				 lu->bst ?
 					(lu->bst->bs_name ? : "Unknown") :
 					"None",
diff --git a/usr/tgtd.h b/usr/tgtd.h
index 726a3f5..03036ba 100644
--- a/usr/tgtd.h
+++ b/usr/tgtd.h
@@ -14,6 +14,7 @@ struct concat_buf;
 #define PRODUCT_ID_LEN		16
 #define PRODUCT_REV_LEN		4
 #define BLOCK_LIMITS_VPD_LEN	0x3C
+#define LBP_VPD_LEN		4
 
 #define PCODE_SHIFT		7
 #define PCODE_OFFSET(x) (x & ((1 << PCODE_SHIFT) - 1))
@@ -72,6 +73,7 @@ struct lu_phy_attr {
 	char qualifier;		/* Peripheral Qualifier */
 	char removable;		/* Removable media */
 	char readonly;          /* Read-Only media */
+	char thinprovisioning;  /* Use thin-provisioning for this LUN */
 	char online;		/* Logical Unit online */
 	char sense_format;	/* Descrptor format sense data supported */
 				/* For the following see READ CAPACITY (16) */
-- 
1.7.3.1
