Description: cmsfs-fuse: Fix EOF detection for fixed record format files
Symptom:     Reading a file fails with -EIO.
Problem:     For reading a file cmsfs-fuse must parse the record data. This
             record data is calculated on-the-fly for fixed record format
             files. The calculation of the remainder of a fixed record on a
             new block can be wrong.
Solution:    Fix the calculation and make the detection of the file end more
             robust.
Problem-ID:  75940 
---
 cmsfs-fuse/cmsfs-fuse.c |   96 +++++++++++++++++++++---------------------------
 1 file changed, 42 insertions(+), 54 deletions(-)

--- a/cmsfs-fuse/cmsfs-fuse.c
+++ b/cmsfs-fuse/cmsfs-fuse.c
@@ -920,87 +920,66 @@ static void set_record_extension(struct
 	f->record_scan_state = RSS_DATA_BLOCK_EXT;
 }
 
-/* check for file end via byte count and return count of bytes left */
-static int crop_file_end(struct file *f, int record, off_t done,
-			    int first)
-{
-	off_t filesize = (off_t) f->fst->nr_records * (off_t) f->fst->record_len;
-	struct record *rec = &f->rlist[record];
-	struct record_ext *rext = rec->ext;
-
-	/* done already includes the complete record length incl. extensions */
-	done -= rec->total_len;
-	/* remove possible linefeeds before comparing with on-disk file size */
-	if (f->linefeed && record)
-		done -= record;
-	done += rec->first_block_len;
-
-	/* add length of all existing extensions */
-	while (rext != NULL) {
-		done += rext->len;
-		rext = rext->next;
-	}
-
-	if (done + first > filesize)
-		first = filesize - done;
-	return first;
-}
-
 /* check for file end by record number */
 static int end_of_file(struct file *f, int record)
 {
-	if (record == f->fst->nr_records)
+	if (record == f->fst->nr_records - 1)
 		return 1;
 	return 0;
 }
 
-static void walk_fixed_data_block(struct file *f, off_t addr, int *record,
+static int walk_fixed_data_block(struct file *f, off_t addr, int *record,
 				  off_t *total, int block, int disp)
 {
 	off_t offset = (off_t ) block * cmsfs.blksize + disp;
-	int rlen = (f->fst->record_len > cmsfs.blksize) ?
+	int len = (f->fst->record_len > cmsfs.blksize) ?
 		cmsfs.blksize : f->fst->record_len;
-	int first = (offset % f->fst->record_len) ?
-		rlen - (offset % rlen) : 0;
 	int left = cmsfs.blksize - disp;
+	int first = 0;
+
+	if (offset % f->fst->record_len) {
+		if (f->fst->record_len - offset >= cmsfs.blksize)
+			first = cmsfs.blksize;
+		else
+			first = (f->fst->record_len % cmsfs.blksize) - (offset % len);
+	}
 
 	if (first) {
 		BUG(first > left);
-
-		first = crop_file_end(f, *record, *total, first);
 		set_record_extension(f, record, addr, first, block);
 		left -= first;
 		if (addr != NULL_BLOCK)
 			addr += first;
 	}
 
-	while (left >= rlen) {
+	while (left >= len) {
 		/*
 		 * Increment record number only after adding a possible
 		 * extension. *record starts with -1 so the first is 0.
 		 */
-		(*record)++;
 		if (end_of_file(f, *record))
-			return;
+			return 1;
+		(*record)++;
 
 		set_record_len(f, *record, f->fst->record_len);
-		set_record(f, record, addr, rlen, total, block);
+		set_record(f, record, addr, len, total, block);
 
-		left -= rlen;
+		left -= len;
 		if (addr != NULL_BLOCK)
-			addr += rlen;
+			addr += len;
 	}
 
 	/* partial record left */
 	if (left > 0) {
-		(*record)++;
 		if (end_of_file(f, *record))
-			return;
+			return 1;
+		(*record)++;
 
 		set_record_len(f, *record, f->fst->record_len);
 		set_record(f, record, addr, left, total, block);
-		return;
+		return 0;
 	}
+	return 0;
 }
 
 static int get_record_unused_bytes(struct file *f, int nr)
@@ -1150,7 +1129,7 @@ static int walk_var_data_block(struct fi
 
 	/* split header */
 	if (left == 1) {
-		if (end_of_file(f, *record + 1))
+		if (end_of_file(f, *record))
 			return 0;
 		rc = _read(&half_len, sizeof(half_len), addr);
 		if (rc < 0)
@@ -1162,21 +1141,23 @@ static int walk_var_data_block(struct fi
 	return 0;
 }
 
-static void cache_fixed_data_block(struct file *f, off_t addr, int *block,
-				   int *record, off_t *total, int disp)
+static int cache_fixed_data_block(struct file *f, off_t addr, int *block,
+				  int *record, off_t *total, int disp)
 {
 	/*
 	 * Cannot distinguish null block pointers from not existing pointers,
 	 * so this fn is called for the whole pointer block and maybe for
 	 * non-existing blocks and records too. Check and bail out if EOF.
 	 */
-	if (*block >= f->fst->nr_blocks)
-		return;
+	if (*block >= f->fst->nr_blocks || *record >= f->fst->nr_records)
+		return 0;
 
-	walk_fixed_data_block(f, addr, record, total, *block, disp);
 	f->blist[*block].disk_addr = addr & ~DATA_BLOCK_MASK;
+	walk_fixed_data_block(f, addr, record, total, *block, disp);
+	/* record starts with 0 but on-disk record number with 1 */
 	f->blist[*block].hi_record_nr = *record + 1;
 	(*block)++;
+	return 0;
 }
 
 /*
@@ -1184,7 +1165,7 @@ static void cache_fixed_data_block(struc
  * data block respecting the sequence of the data.
  */
 static int cache_file_fixed(struct file *f, off_t addr, int level, int *block,
-			     unsigned int *disp, int *record, off_t *total)
+			    unsigned int *disp, int *record, off_t *total)
 {
 	int left = f->ptr_per_block;
 	off_t ptr;
@@ -1195,6 +1176,15 @@ static int cache_file_fixed(struct file
 			ptr = get_fixed_pointer(addr);
 			if (ptr < 0)
 				return ptr;
+			/*
+			 * In difference to variable record format a null pointer
+			 * may be valid for a null block so we cannot determine
+			 * the file end from a pointer entry. Check for the number
+			 * of scanned blocks instead.
+			 */
+			if (*block >= f->fst->nr_blocks
+			    || *record >= f->fst->nr_records)
+				return 0;
 			cache_file_fixed(f, ptr, level, block, disp, record, total);
 			/* don't increment for null block pointers */
 			if (addr)
@@ -1202,8 +1192,7 @@ static int cache_file_fixed(struct file
 		}
 		return 0;
 	}
-	cache_fixed_data_block(f, addr, block, record, total, 0);
-	return 0;
+	return cache_fixed_data_block(f, addr, block, record, total, 0);
 }
 
 static int cache_variable_data_block(struct file *f, off_t addr, int *block,
@@ -1216,15 +1205,14 @@ static int cache_variable_data_block(str
 	 * so this fn is called for the whole pointer block and maybe for
 	 * non-existing blocks and records too. Check and bail out if EOF.
 	 */
-	if (*block >= f->fst->nr_blocks ||
-	    *record >= f->fst->nr_records)
+	if (*block >= f->fst->nr_blocks || *record >= f->fst->nr_records)
 		return 0;
 
+	f->blist[*block].disk_addr = addr & ~DATA_BLOCK_MASK;
 	rc = walk_var_data_block(f, addr, disp, record, total, *block, skip);
 	if (rc < 0)
 		return rc;
 
-	f->blist[*block].disk_addr = addr & ~DATA_BLOCK_MASK;
 	/* record starts with 0 but on-disk record number with 1 */
 	f->blist[*block].hi_record_nr = *record + 1;
 	f->blist[*block].disp = disp;
