File s390-tools-sles11sp2-cmsfs-fuse-eof.patch of Package s390-tools
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;