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++;