File s390-tools-sles11sp2-cmsfs-fuse-amap.patch of Package s390-tools

Description: cmsfs-fuse: Fix block allocation on large disks
Symptom:     Write failure on a disk with block size 512 bytes if the disk
             is larger than 256 MB.
Problem:     The calculation of the block address to be allocated or
             freed is wrong for addresses that exceed one pointer block.
Solution:    Fix the calculation of the disk address. Also, speed up the
             block allocation by providing a hint where the last free block
             was found.
Problem-ID:  75958 
---
 cmsfs-fuse/amap.c |  159 ++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 114 insertions(+), 45 deletions(-)

--- a/cmsfs-fuse/amap.c
+++ b/cmsfs-fuse/amap.c
@@ -19,7 +19,29 @@
 #include "cmsfs-fuse.h"
 
 /*
- * Get block number from address.
+ * Hint where to look for the next free block (level 0 only).
+ * Updated if a free block is found. If the level 0 amap bitmap
+ * block is exhausted we still scan all amap blocks.
+ */
+struct amap_alloction_hint {
+	/* addr of amap bitmap block to check */
+	off_t amap_addr;
+	/* disk addr of the last allocated or freed block */
+	off_t addr;
+	/* offset to start of the amap data block */
+	off_t offset;
+};
+
+static struct amap_alloction_hint amap_hint;
+
+static void update_amap_hint(off_t amap_addr, off_t addr)
+{
+	amap_hint.amap_addr = amap_addr;
+	amap_hint.addr = addr;
+}
+
+/*
+ * Get L1 block number from address.
  */
 static int amap_blocknumber(off_t addr)
 {
@@ -43,7 +65,7 @@ static int amap_blocknumber_level(int le
  */
 static off_t get_amap_addr(int level, off_t addr, off_t ptr)
 {
-	int block = amap_blocknumber_level(level, addr);
+	int block = amap_blocknumber_level(level, addr) % PTRS_PER_BLOCK;
 
 	if (cmsfs.amap_levels == 0)
 		return cmsfs.amap;
@@ -59,30 +81,21 @@ static off_t get_amap_addr(int level, of
 }
 
 /*
- * Mark disk address as allocated in alloc map. Unaligned addr is tolerated.
+ * Mark disk address as allocated in alloc map.
  */
-static void amap_block_set(off_t addr)
+static void amap_block_set(off_t amap, int bit)
 {
-	off_t amap = get_amap_addr(cmsfs.amap_levels, addr, cmsfs.amap);
-	int rc, block = amap_blocknumber(addr);
-	unsigned int byte, bit;
 	u8 entry;
+	int rc;
 
-	if (block > 0)
-		addr -= (off_t) block * BYTES_PER_BLOCK;
-
-	addr >>= BITS_PER_DATA_BLOCK;
-	byte = addr / 8;
-	bit = addr % 8;
-
-	rc = _read(&entry, sizeof(entry), amap + byte);
+	rc = _read(&entry, sizeof(entry), amap);
 	BUG(rc < 0);
 
 	/* already used */
 	BUG(entry & (1 << (7 - bit)));
 
 	entry |= (1 << (7 - bit));
-	rc = _write(&entry, sizeof(entry), amap + byte);
+	rc = _write(&entry, sizeof(entry), amap);
 	BUG(rc < 0);
 }
 
@@ -93,6 +106,7 @@ static void amap_block_clear(off_t addr)
 {
 	off_t amap = get_amap_addr(cmsfs.amap_levels, addr, cmsfs.amap);
 	int rc, block = amap_blocknumber(addr);
+	off_t disk_addr = addr;
 	unsigned int byte, bit;
 	u8 entry;
 
@@ -112,12 +126,21 @@ static void amap_block_clear(off_t addr)
 	entry &= ~(1 << (7 - bit));
 	rc = _write(&entry, sizeof(entry), amap + byte);
 	BUG(rc < 0);
+
+	/*
+	 * If the freed addr is lower set the hint to it to ensure
+	 * the amap bitmap is packed from the start. That way we do not
+	 * need an extra check if the bitmap entry is above disk end, the
+	 * check if we overflow the total block limit is sufficient.
+	 */
+	if (disk_addr < amap_hint.addr)
+		update_amap_hint(amap + byte, disk_addr);
 }
 
 /*
  * Return the first free bit in one byte.
  */
-static int find_first_empty_bit(u8 entry)
+static inline int find_first_empty_bit(u8 entry)
 {
 	u8 i;
 
@@ -129,46 +152,89 @@ static int find_first_empty_bit(u8 entry
 }
 
 /*
- * Look for the first unallocated block and return addr of allocated block.
+ * Return the number of bytes addressed by one pointer entry for the
+ * specified level.
  */
-static off_t __get_free_block(int level, off_t amap, int block_nr)
+static off_t bytes_per_level(int level)
+{
+	off_t mult = BYTES_PER_BLOCK;
+
+	if (!level)
+		return 0;
+	level--;
+	while (level--)
+		mult *= PTRS_PER_BLOCK;
+	return mult;
+}
+
+static inline int get_amap_entry_bit(off_t amap)
 {
-	off_t ptr, addr = amap;
-	unsigned int bit;
-	int left, rc, i;
 	u8 entry;
+	int rc;
+
+	rc = _read(&entry, sizeof(entry), amap);
+	BUG(rc < 0);
+
+	if (entry == 0xff)
+		return -1;
+	return find_first_empty_bit(entry);
+}
+
+static off_t __get_free_block_fast(void)
+{
+	off_t addr, amap = amap_hint.amap_addr & ~DATA_BLOCK_MASK;
+	int bit, i = amap_hint.amap_addr & DATA_BLOCK_MASK;
+
+	for (; i < cmsfs.blksize; i++) {
+		bit = get_amap_entry_bit(amap + i);
+		if (bit == -1)
+			continue;
+
+		/* Calculate the addr for the free block we've found. */
+		addr = (off_t) amap_blocknumber(amap_hint.addr) * BYTES_PER_BLOCK;
+		addr += i * 8 * cmsfs.blksize;
+		addr += bit * cmsfs.blksize;
+
+		amap_block_set(amap + i, bit);
+		update_amap_hint(amap + i, addr);
+		return addr;
+	}
+	return 0;
+}
+
+/*
+ * Look for the first unallocated block and return addr of allocated block.
+ */
+static off_t __get_free_block(int level, off_t amap, off_t addr)
+{
+	off_t ptr;
+	int bit, i;
 
 	if (level > 0) {
-		level--;
-		left = PTRS_PER_BLOCK;
-		while (left--) {
-			ptr = get_fixed_pointer(addr);
+		for (i = 0; i < PTRS_PER_BLOCK; i++) {
+			ptr = get_fixed_pointer(amap);
 			if (!ptr)
 				return 0;
-			ptr = __get_free_block(level, ptr, block_nr);
+			ptr = __get_free_block(level - 1, ptr,
+				addr + i * bytes_per_level(level));
 			if (ptr)
 				return ptr;
-			addr += PTR_SIZE;
-			block_nr++;
+			amap += PTR_SIZE;
 		}
 		return 0;
 	}
 
 	for (i = 0; i < cmsfs.blksize; i++) {
-		rc = _read(&entry, sizeof(entry), amap + i);
-		BUG(rc < 0);
-
-		if (entry != 0xff) {
-			/* get first empty bit and add to addr */
-			bit = find_first_empty_bit(entry);
-
-			/* bit -> addr */
-			addr = ((off_t) cmsfs.blksize * i * 8 +
-				(off_t) cmsfs.blksize * bit) +
-				(off_t) block_nr * BYTES_PER_BLOCK;
-			amap_block_set(addr);
-			return addr;
-		}
+		bit = get_amap_entry_bit(amap + i);
+		if (bit == -1)
+			continue;
+		amap_block_set(amap + i, bit);
+		/* add byte offset */
+		addr += i * 8 * cmsfs.blksize;
+		/* add bit offset */
+		addr += bit * cmsfs.blksize;
+		update_amap_hint(amap + i, addr);
+		return addr;
 	}
 	return 0;
 }
@@ -178,11 +244,14 @@ static off_t __get_free_block(int level,
  */
 off_t get_free_block(void)
 {
-	off_t addr;
+	off_t addr = 0;
 
 	if (cmsfs.used_blocks + cmsfs.reserved_blocks >= cmsfs.total_blocks)
 		return -ENOSPC;
-	addr = __get_free_block(cmsfs.amap_levels, cmsfs.amap, 0);
+	if (amap_hint.amap_addr)
+		addr = __get_free_block_fast();
+	if (!addr)
+		addr = __get_free_block(cmsfs.amap_levels, cmsfs.amap, 0);
 	BUG(!addr);
 
 	cmsfs.used_blocks++;