Description: cmsfs-fuse: Multiple write requests for fixed record format files
Symptom:     Writing to a fixed record format file may fail. 
Problem:     If multiple write requests are performed on a fixed record format
             file cmsfs-fuse looks at the record information to detect the
             current write position. That can be wrong if a write request on a
             fixed record format file is not issued on record boundaries.
Solution:    Maintain a write pointer as long as the file is open and use this
             write pointer for contiguous writes to a file.
Problem-ID:  75939
---
 cmsfs-fuse/cmsfs-fuse.c |  105 ++++++++++++++++++++++++++++++------------------
 1 file changed, 67 insertions(+), 38 deletions(-)

--- a/cmsfs-fuse/cmsfs-fuse.c
+++ b/cmsfs-fuse/cmsfs-fuse.c
@@ -116,6 +116,7 @@ static char CODEPAGE_LINUX[] = "ISO-8859
 #define RWS_RECORD_INCOMPLETE	0x4
 #define RWS_RECORD_COMPLETE	0x8
 
+#define BWS_BLOCK_NOT_INIT	0x0
 #define BWS_BLOCK_NEW		0x1
 #define BWS_BLOCK_USED		0x2
 
@@ -172,6 +173,15 @@ struct file_operations {
 static struct file_operations fops_fixed;
 static struct file_operations fops_variable;
 
+struct write_state {
+	int		block_state;
+	/* number of free bytes in the current block */
+	int		block_free;
+	int		var_record_state;
+	/* remaining record bytes for a write request */
+	int		var_record_len;
+};
+
 /*
  * File object for operations that follow open
  */
@@ -204,8 +214,8 @@ struct file {
 	int		pad_bytes;
 	/* old levels value, needed to rewrite pointers */
 	int		old_levels;
-	/* record write state for variable headers */
-	struct var_record_state *vrstate;
+	/* state information needed for a write request */
+	struct write_state *wstate;
 	/* path name for open and unlink */
 	char		path[MAX_FNAME + 1];
 	/* counter for pseudo null length records */
@@ -232,12 +242,6 @@ struct file {
 	int		unlinked;
 };
 
-struct var_record_state {
-	int		rlen;
-	int		record_state;
-	int		block_state;
-};
-
 struct xattr {
 	char		name[20];
 	size_t		size;
@@ -3388,6 +3392,12 @@ static int examine_last_block(struct fil
 	if (!f->fst->nr_blocks || !f->fst->nr_records)
 		return 0;
 
+	/*
+	 * For subsequent writes we know how much is left on the current block.
+	 * */
+	if (f->wstate->block_state != BWS_BLOCK_NOT_INIT)
+		return f->wstate->block_free;
+
 	last_bnr = f->fst->nr_blocks - 1;
 	rec = &f->rlist[f->fst->nr_records - 1];
 
@@ -3454,6 +3464,16 @@ static off_t disk_end(struct file *f)
 	if (!f->fst->nr_records)
 		return 0;
 
+	/*
+	 * Only the first write on a newly opened file should set the write_ptr
+	 * according to what is on the disk. Subsequent writes should use the
+	 * actual write_ptr. This avoids the problem for fixed records that
+	 * for a record that is not yet completely written the calculated
+	 * write_ptr would be wrong.
+	 */
+	if (f->wstate->block_state != BWS_BLOCK_NOT_INIT)
+		return f->write_ptr;
+
 	rec = &f->rlist[f->fst->nr_records - 1];
 	if (rec->ext == NULL) {
 		if (rec->disk_start == NULL_BLOCK)
@@ -3481,7 +3501,7 @@ static off_t disk_end(struct file *f)
 void store_displacement(struct file *f, int disp)
 {
 	f->blist[f->fst->nr_blocks - 1].disp = disp;
-	f->vrstate->block_state = BWS_BLOCK_USED;
+	f->wstate->block_state = BWS_BLOCK_USED;
 }
 
 /*
@@ -3537,13 +3557,12 @@ static int write_prepare_block(struct fi
 	}
 
 	if (new_block) {
-		if (f->fst->record_format == RECORD_LEN_VARIABLE)
-			f->vrstate->block_state = BWS_BLOCK_NEW;
+		f->wstate->block_state = BWS_BLOCK_NEW;
 		f->blist[f->fst->nr_blocks].disk_addr = f->write_ptr;
 
 		if (!f->write_ptr && f->fst->record_format == RECORD_LEN_FIXED)
 			f->nr_null_blocks++;
-
+		f->wstate->block_free = cmsfs.blksize;
 		f->fst->nr_blocks++;
 	}
 	return len;
@@ -3557,23 +3576,24 @@ static int write_var_header(struct file
 	u8 half_vheader;
 	int rc;
 
-	if (f->vrstate->record_state == RWS_HEADER_COMPLETE ||
-	    f->vrstate->record_state == RWS_RECORD_INCOMPLETE)
+	if (f->wstate->var_record_state == RWS_HEADER_COMPLETE ||
+	    f->wstate->var_record_state == RWS_RECORD_INCOMPLETE)
 		return 0;
 
-	if (f->vrstate->record_state == RWS_HEADER_STARTED) {
+	if (f->wstate->var_record_state == RWS_HEADER_STARTED) {
 		/* write secord header byte */
 		half_vheader = vheader & 0xff;
 		rc = _write(&half_vheader, 1, f->write_ptr);
 		if (rc < 0)
 			return rc;
 		f->write_ptr++;
-		f->vrstate->record_state = RWS_HEADER_COMPLETE;
+		f->wstate->block_free--;
+		f->wstate->var_record_state = RWS_HEADER_COMPLETE;
 		return 1;
 	}
 
 	/* block cannot be spanned if a header starts on it */
-	if (f->vrstate->block_state == BWS_BLOCK_NEW)
+	if (f->wstate->block_state == BWS_BLOCK_NEW)
 		store_displacement(f, f->write_ptr & DATA_BLOCK_MASK);
 
 	if (len >= 2) {
@@ -3581,8 +3601,9 @@ static int write_var_header(struct file
 		if (rc < 0)
 			return rc;
 		f->write_ptr += 2;
-		f->vrstate->rlen = vheader;
-		f->vrstate->record_state = RWS_HEADER_COMPLETE;
+		f->wstate->block_free -= 2;
+		f->wstate->var_record_len = vheader;
+		f->wstate->var_record_state = RWS_HEADER_COMPLETE;
 		return 2;
 	} else {
 		/* len = 1, write first header byte */
@@ -3591,8 +3612,9 @@ static int write_var_header(struct file
 		if (rc < 0)
 			return rc;
 		f->write_ptr++;
-		f->vrstate->rlen = vheader;
-		f->vrstate->record_state = RWS_HEADER_STARTED;
+		f->wstate->block_free--;
+		f->wstate->var_record_len = vheader;
+		f->wstate->var_record_state = RWS_HEADER_STARTED;
 		return 1;
 	}
 }
@@ -3611,6 +3633,7 @@ static int extend_block_fixed(struct fil
 		if (rc < 0)
 			return rc;
 		f->write_ptr += len;
+		f->wstate->block_free -= len;
 	}
 	return len;
 }
@@ -3638,15 +3661,16 @@ static int extend_block_variable(struct
 		if (len < rlen)
 			rlen = len;
 		/* remaining record data less than block len */
-		if (f->vrstate->rlen < rlen)
-			rlen = f->vrstate->rlen;
+		if (f->wstate->var_record_len < rlen)
+			rlen = f->wstate->var_record_len;
 		rc = _write(buf, rlen, f->write_ptr);
 		if (rc < 0)
 			return rc;
 
 		f->write_ptr += rlen;
+		f->wstate->block_free -= rlen;
 
-		if (f->vrstate->block_state == BWS_BLOCK_NEW) {
+		if (f->wstate->block_state == BWS_BLOCK_NEW) {
 			/*
 			 * If the second byte of a split header was written
 			 * (blocksize - 1) is enough to make the block spanned.
@@ -3663,11 +3687,11 @@ static int extend_block_variable(struct
 		copied += rlen;
 		size -= rlen;
 		len -= rlen;
-		f->vrstate->rlen -= rlen;
+		f->wstate->var_record_len -= rlen;
 
-		BUG(f->vrstate->rlen < 0);
-		if (!f->vrstate->rlen) {
-			f->vrstate->record_state = RWS_RECORD_COMPLETE;
+		BUG(f->wstate->var_record_len < 0);
+		if (!f->wstate->var_record_len) {
+			f->wstate->var_record_state = RWS_RECORD_COMPLETE;
 			/* reset rlen for the next record */
 			rlen = get_record_len(f, size);
 		}
@@ -3678,7 +3702,7 @@ static int extend_block_variable(struct
 	}
 
 	/* record is not yet finished */
-	f->vrstate->record_state = RWS_RECORD_INCOMPLETE;
+	f->wstate->var_record_state = RWS_RECORD_INCOMPLETE;
 	return copied;
 }
 
@@ -3923,6 +3947,12 @@ static int update_last_block_vptr(struct
 	return update_last_block_vptr(f, ABS(vptr->next), level, vptr);
 }
 
+static void reset_write_state(struct file *f)
+{
+	f->wstate->var_record_state = RWS_RECORD_COMPLETE;
+	f->wstate->var_record_len = 0;
+}
+
 /*
  * Append records at current file end. If buf is NULL write zero bytes.
  */
@@ -3950,8 +3980,7 @@ static int write_append(struct file *f,
 	if (f->fst->nr_blocks + nblocks > 1)
 		reserve_meta_blocks(f, f->fst->nr_blocks, nblocks, 1);
 
-	if (f->fst->record_format == RECORD_LEN_VARIABLE)
-		f->vrstate->record_state = RWS_RECORD_COMPLETE;
+	reset_write_state(f);
 
 	/* first use existing last block */
 	if (last) {
@@ -4286,10 +4315,10 @@ static struct file *create_file_object(s
 	else
 		f->ptr_per_block = cmsfs.var_ptrs_per_block;
 
-	f->vrstate = malloc(sizeof(*f->vrstate));
-	if (f->vrstate == NULL)
+	f->wstate = malloc(sizeof(*f->wstate));
+	if (f->wstate == NULL)
 		goto oom_fst;
-	memset(f->vrstate, 0, sizeof(*f->vrstate));
+	memset(f->wstate, 0, sizeof(*f->wstate));
 
 	/*
 	 * Prevent calloc for zero records since it returns a pointer != NULL
@@ -4300,7 +4329,7 @@ static struct file *create_file_object(s
 
 	f->rlist = calloc(f->fst->nr_records, sizeof(struct record));
 	if (f->rlist == NULL)
-		goto oom_vrstate;
+		goto oom_wstate;
 
 	f->blist = calloc(f->fst->nr_blocks, sizeof(struct block));
 	if (f->blist == NULL)
@@ -4316,8 +4345,8 @@ error:
 		*rc = -ENOMEM;
 oom_rlist:
 	free(f->rlist);
-oom_vrstate:
-	free(f->vrstate);
+oom_wstate:
+	free(f->wstate);
 oom_fst:
 	free(f->fst);
 oom_f:
@@ -4333,7 +4362,7 @@ static void destroy_file_object(struct f
 	int i;
 
 	free(f->wcache);
-	free(f->vrstate);
+	free(f->wstate);
 
 	for (i = 0; i < f->fst->nr_records; i++) {
 		rec = &f->rlist[i];
