diff mbox series

[U-Boot,09/17] fs: fat: support write with non-zero offset

Message ID 20180720025723.6736-10-takahiro.akashi@linaro.org
State Changes Requested
Delegated to: Tom Rini
Headers show
Series fs: fat: extend FAT write operations | expand

Commit Message

AKASHI Takahiro July 20, 2018, 2:57 a.m. UTC
In this patch, all the necessary code for allowing for a file offset
at write is implemented. What plays a major roll here is get_set_cluster(),
which, in contrast to its counterpart, set_cluster(), only operates on
already-allocated clusters, overwriting with data.

So, with a file offset specified, set_contents() seeks and writes data
with set_get_cluster() until the end of a file, and, once it reaches
there, continues writing with set_cluster() for the rest.

Please note that a file will be trimmed as a result of write operation if
write ends before reaching file's end. This is an intended behavior
in order to maitain compatibility with the current interface.

Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
---
 fs/fat/fat_write.c | 287 ++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 272 insertions(+), 15 deletions(-)

Comments

Heinrich Schuchardt July 20, 2018, 5:46 p.m. UTC | #1
On 07/20/2018 04:57 AM, AKASHI Takahiro wrote:
> In this patch, all the necessary code for allowing for a file offset
> at write is implemented. What plays a major roll here is get_set_cluster(),
> which, in contrast to its counterpart, set_cluster(), only operates on
> already-allocated clusters, overwriting with data.
> 
> So, with a file offset specified, set_contents() seeks and writes data
> with set_get_cluster() until the end of a file, and, once it reaches
> there, continues writing with set_cluster() for the rest.
> 
> Please note that a file will be trimmed as a result of write operation if
> write ends before reaching file's end. This is an intended behavior
> in order to maitain compatibility with the current interface.

This does not match the EFI spec.

> 
> Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>

When testing I observed that I could not write at an offset larger than
the file size. This does not match the EFI spec:

EFI_FILE_PROTOCOL.SetPosition():
seeking past the end of the file is allowed (a subsequent write would
grow the file).

Best regards

Heinrich


> ---
>  fs/fat/fat_write.c | 287 ++++++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 272 insertions(+), 15 deletions(-)
> 
> diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c
> index 3a9c53e253..cc45a33876 100644
> --- a/fs/fat/fat_write.c
> +++ b/fs/fat/fat_write.c
> @@ -450,6 +450,120 @@ set_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer,
>  	return 0;
>  }
>  
> +static __u8 tmpbuf_cluster[MAX_CLUSTSIZE] __aligned(ARCH_DMA_MINALIGN);
> +
> +/*
> + * Read and modify data on existing and consecutive cluster blocks
> + */
> +static int
> +get_set_cluster(fsdata *mydata, __u32 clustnum, loff_t pos, __u8 *buffer,
> +		loff_t size, loff_t *gotsize)
> +{
> +	unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
> +	__u32 startsect;
> +	loff_t wsize;
> +	int clustcount, i, ret;
> +
> +	*gotsize = 0;
> +	if (!size)
> +		return 0;
> +
> +	assert(pos < bytesperclust);
> +	startsect = clust_to_sect(mydata, clustnum);
> +
> +	debug("clustnum: %d, startsect: %d, pos: %lld\n", clustnum, startsect,
> +									pos);
> +
> +	/* partial write at beginning */
> +	if (pos) {
> +		wsize = min(bytesperclust - pos, size);
> +		ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
> +		if (ret != mydata->clust_size) {
> +			debug("Error reading data (got %d)\n", ret);
> +			return -1;
> +		}
> +
> +		memcpy(tmpbuf_cluster + pos, buffer, wsize);
> +		ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
> +		if (ret != mydata->clust_size) {
> +			debug("Error writing data (got %d)\n", ret);
> +			return -1;
> +		}
> +
> +		size -= wsize;
> +		buffer += wsize;
> +		*gotsize += wsize;
> +
> +		startsect += mydata->clust_size;
> +
> +		if (!size)
> +			return 0;
> +	}
> +
> +	/* full-cluster write */
> +	if (size >= bytesperclust) {
> +		clustcount = lldiv(size, bytesperclust);
> +
> +		if (!((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1))) {
> +			wsize = clustcount * bytesperclust;
> +			ret = disk_write(startsect,
> +					clustcount * mydata->clust_size,
> +					buffer);
> +			if (ret != clustcount * mydata->clust_size) {
> +				debug("Error writing data (got %d)\n", ret);
> +				return -1;
> +			}
> +
> +			size -= wsize;
> +			buffer += wsize;
> +			*gotsize += wsize;
> +
> +			startsect += clustcount * mydata->clust_size;
> +		} else {
> +			for (i = 0; i < clustcount; i++) {
> +				memcpy(tmpbuf_cluster, buffer, bytesperclust);
> +				ret = disk_write(startsect, mydata->clust_size,
> +								tmpbuf_cluster);
> +				if (ret != mydata->clust_size) {
> +					debug("Error writing data (got %d)\n",
> +									ret);
> +					return -1;
> +				}
> +
> +				size -= bytesperclust;
> +				buffer += bytesperclust;
> +				*gotsize += bytesperclust;
> +
> +				startsect += mydata->clust_size;
> +			}
> +		}
> +	}
> +
> +	/* partial write at end */
> +	if (size) {
> +		wsize = size;
> +		ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
> +		if (ret != mydata->clust_size) {
> +			debug("Error reading data (got %d)\n", ret);
> +			return -1;
> +		}
> +		memcpy(tmpbuf_cluster, buffer, wsize);
> +		ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
> +		if (ret != mydata->clust_size) {
> +			debug("Error writing data (got %d)\n", ret);
> +			return -1;
> +		}
> +
> +		size -= wsize;
> +		buffer += wsize;
> +		*gotsize += wsize;
> +	}
> +
> +	assert(!size);
> +
> +	return 0;
> +}
> +
>  /*
>   * Find the first empty cluster
>   */
> @@ -579,26 +693,158 @@ set_contents(fsdata *mydata, dir_entry *dentptr, loff_t pos, __u8 *buffer,
>  	unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
>  	__u32 curclust = START(dentptr);
>  	__u32 endclust = 0, newclust = 0;
> -	loff_t actsize;
> +	loff_t cur_pos, offset, actsize, wsize;
>  
>  	*gotsize = 0;
> -	filesize = maxsize;
> +	filesize = pos + maxsize;
>  
>  	debug("%llu bytes\n", filesize);
>  
> -	if (curclust) {
> -		/*
> -		 * release already-allocated clusters anyway
> -		 */
> -		if (clear_fatent(mydata, curclust)) {
> -			printf("Error: clearing FAT entries\n");
> +	if (!filesize) {
> +		if (!curclust)
> +			return 0;
> +		if (!CHECK_CLUST(curclust, mydata->fatsize) ||
> +		    IS_LAST_CLUST(curclust, mydata->fatsize)) {
> +			clear_fatent(mydata, curclust);
> +			set_start_cluster(mydata, dentptr, 0);
> +			return 0;
> +		}
> +		debug("curclust: 0x%x\n", curclust);
> +		debug("Invalid FAT entry\n");
> +		return -1;
> +	}
> +
> +	if (!curclust) {
> +		assert(pos == 0);
> +		goto set_clusters;
> +	}
> +
> +	/* go to cluster at pos */
> +	cur_pos = bytesperclust;
> +	while (1) {
> +		if (pos <= cur_pos)
> +			break;
> +		if (IS_LAST_CLUST(curclust, mydata->fatsize))
> +			break;
> +
> +		newclust = get_fatent(mydata, curclust);
> +		if (!IS_LAST_CLUST(newclust, mydata->fatsize) &&
> +		    CHECK_CLUST(newclust, mydata->fatsize)) {
> +			debug("curclust: 0x%x\n", curclust);
> +			debug("Invalid FAT entry\n");
>  			return -1;
>  		}
> +
> +		cur_pos += bytesperclust;
> +		curclust = newclust;
> +	}
> +	if (IS_LAST_CLUST(curclust, mydata->fatsize)) {
> +		assert(pos == cur_pos);
> +		goto set_clusters;
>  	}
>  
> -	curclust = find_empty_cluster(mydata);
> -	set_start_cluster(mydata, dentptr, curclust);
> +	assert(pos < cur_pos);
> +	cur_pos -= bytesperclust;
>  
> +	/* overwrite */
> +	assert(IS_LAST_CLUST(curclust, mydata->fatsize) ||
> +	       !CHECK_CLUST(curclust, mydata->fatsize));
> +
> +	while (1) {
> +		/* search for allocated consecutive clusters */
> +		actsize = bytesperclust;
> +		endclust = curclust;
> +		while (1) {
> +			if (filesize <= (cur_pos + actsize))
> +				break;
> +
> +			newclust = get_fatent(mydata, endclust);
> +
> +			if (IS_LAST_CLUST(newclust, mydata->fatsize))
> +				break;
> +			if (CHECK_CLUST(newclust, mydata->fatsize)) {
> +				debug("curclust: 0x%x\n", curclust);
> +				debug("Invalid FAT entry\n");
> +				return -1;
> +			}
> +
> +			actsize += bytesperclust;
> +			endclust = newclust;
> +		}
> +
> +		/* overwrite to <curclust..endclust> */
> +		if (pos < cur_pos)
> +			offset = 0;
> +		else
> +			offset = pos - cur_pos;
> +		wsize = min(cur_pos + actsize, filesize) - pos;
> +		if (get_set_cluster(mydata, curclust, offset, buffer, wsize,
> +								&actsize)) {
> +			printf("Error get-and-setting cluster\n");
> +			return -1;
> +		}
> +		buffer += wsize;
> +		*gotsize += wsize;
> +		cur_pos += offset + wsize;
> +
> +		if (filesize <= cur_pos)
> +			break;
> +
> +		/* CHECK: newclust = get_fatent(mydata, endclust); */
> +
> +		if (IS_LAST_CLUST(newclust, mydata->fatsize))
> +			/* no more clusters */
> +			break;
> +
> +		curclust = newclust;
> +	}
> +
> +	if (filesize <= cur_pos) {
> +		/* no more write */
> +		newclust = get_fatent(mydata, endclust);
> +		if (!IS_LAST_CLUST(newclust, mydata->fatsize)) {
> +			/* truncate the rest */
> +			clear_fatent(mydata, newclust);
> +
> +			/* Mark end of file in FAT */
> +			if (mydata->fatsize == 12)
> +				newclust = 0xfff;
> +			else if (mydata->fatsize == 16)
> +				newclust = 0xffff;
> +			else if (mydata->fatsize == 32)
> +				newclust = 0xfffffff;
> +			set_fatent_value(mydata, endclust, newclust);
> +		}
> +
> +		return 0;
> +	}
> +
> +	curclust = endclust;
> +	filesize -= cur_pos;
> +	assert(!(cur_pos % bytesperclust));
> +
> +set_clusters:
> +	/* allocate and write */
> +	assert(!pos);
> +
> +	/* Assure that curclust is valid */
> +	if (!curclust) {
> +		curclust = find_empty_cluster(mydata);
> +		set_start_cluster(mydata, dentptr, curclust);
> +	} else {
> +		newclust = get_fatent(mydata, curclust);
> +
> +		if (IS_LAST_CLUST(newclust, mydata->fatsize)) {
> +			newclust = determine_fatent(mydata, curclust);
> +			set_fatent_value(mydata, curclust, newclust);
> +			curclust = newclust;
> +		} else {
> +			debug("error: something wrong\n");
> +			return -1;
> +		}
> +	}
> +
> +	/* TODO: already partially written */
>  	if (check_overflow(mydata, curclust, filesize)) {
>  		printf("Error: no space left: %llu\n", filesize);
>  		return -1;
> @@ -852,6 +1098,16 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
>  			goto exit;
>  		}
>  
> +		/* A file exists */
> +		if (pos == -1)
> +			/* Append to the end */
> +			pos = FAT2CPU32(retdent->size);
> +		if (pos > retdent->size) {
> +			/* No hole allowed */
> +			ret = -EINVAL;
> +			goto exit;
> +		}
> +
>  		/* Update file size in a directory entry */
>  		retdent->size = cpu_to_le32(pos + size);
>  	} else {
> @@ -872,6 +1128,12 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
>  			goto exit;
>  		}
>  
> +		if (pos) {
> +			/* No hole allowed */
> +			ret = -EINVAL;
> +			goto exit;
> +		}
> +
>  		memset(itr->dent, 0, sizeof(*itr->dent));
>  
>  		/* Set short name to set alias checksum field in dir_slot */
> @@ -921,10 +1183,5 @@ exit:
>  int file_fat_write(const char *filename, void *buffer, loff_t offset,
>  		   loff_t maxsize, loff_t *actwrite)
>  {
> -	if (offset != 0) {
> -		printf("Error: non zero offset is currently not supported.\n");
> -		return -EINVAL;
> -	}
> -
>  	return file_fat_write_at(filename, offset, buffer, maxsize, actwrite);
>  }
>
AKASHI Takahiro July 23, 2018, 8:41 a.m. UTC | #2
On Fri, Jul 20, 2018 at 07:46:49PM +0200, Heinrich Schuchardt wrote:
> On 07/20/2018 04:57 AM, AKASHI Takahiro wrote:
> > In this patch, all the necessary code for allowing for a file offset
> > at write is implemented. What plays a major roll here is get_set_cluster(),
> > which, in contrast to its counterpart, set_cluster(), only operates on
> > already-allocated clusters, overwriting with data.
> > 
> > So, with a file offset specified, set_contents() seeks and writes data
> > with set_get_cluster() until the end of a file, and, once it reaches
> > there, continues writing with set_cluster() for the rest.
> > 
> > Please note that a file will be trimmed as a result of write operation if
> > write ends before reaching file's end. This is an intended behavior
> > in order to maitain compatibility with the current interface.
> 
> This does not match the EFI spec.

First, please understand my standpoint that I mentioned in a reply
to your comment on my cover letter.

> > 
> > Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
> 
> When testing I observed that I could not write at an offset larger than
> the file size. This does not match the EFI spec:

So this is an intended behavior.

> EFI_FILE_PROTOCOL.SetPosition():
> seeking past the end of the file is allowed (a subsequent write would
> grow the file).

Surely, this is a discussion point.
Any comment from other folks?

-Takahiro AKASHI

> Best regards
> 
> Heinrich
> 
> 
> > ---
> >  fs/fat/fat_write.c | 287 ++++++++++++++++++++++++++++++++++++++++++---
> >  1 file changed, 272 insertions(+), 15 deletions(-)
> > 
> > diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c
> > index 3a9c53e253..cc45a33876 100644
> > --- a/fs/fat/fat_write.c
> > +++ b/fs/fat/fat_write.c
> > @@ -450,6 +450,120 @@ set_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer,
> >  	return 0;
> >  }
> >  
> > +static __u8 tmpbuf_cluster[MAX_CLUSTSIZE] __aligned(ARCH_DMA_MINALIGN);
> > +
> > +/*
> > + * Read and modify data on existing and consecutive cluster blocks
> > + */
> > +static int
> > +get_set_cluster(fsdata *mydata, __u32 clustnum, loff_t pos, __u8 *buffer,
> > +		loff_t size, loff_t *gotsize)
> > +{
> > +	unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
> > +	__u32 startsect;
> > +	loff_t wsize;
> > +	int clustcount, i, ret;
> > +
> > +	*gotsize = 0;
> > +	if (!size)
> > +		return 0;
> > +
> > +	assert(pos < bytesperclust);
> > +	startsect = clust_to_sect(mydata, clustnum);
> > +
> > +	debug("clustnum: %d, startsect: %d, pos: %lld\n", clustnum, startsect,
> > +									pos);
> > +
> > +	/* partial write at beginning */
> > +	if (pos) {
> > +		wsize = min(bytesperclust - pos, size);
> > +		ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
> > +		if (ret != mydata->clust_size) {
> > +			debug("Error reading data (got %d)\n", ret);
> > +			return -1;
> > +		}
> > +
> > +		memcpy(tmpbuf_cluster + pos, buffer, wsize);
> > +		ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
> > +		if (ret != mydata->clust_size) {
> > +			debug("Error writing data (got %d)\n", ret);
> > +			return -1;
> > +		}
> > +
> > +		size -= wsize;
> > +		buffer += wsize;
> > +		*gotsize += wsize;
> > +
> > +		startsect += mydata->clust_size;
> > +
> > +		if (!size)
> > +			return 0;
> > +	}
> > +
> > +	/* full-cluster write */
> > +	if (size >= bytesperclust) {
> > +		clustcount = lldiv(size, bytesperclust);
> > +
> > +		if (!((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1))) {
> > +			wsize = clustcount * bytesperclust;
> > +			ret = disk_write(startsect,
> > +					clustcount * mydata->clust_size,
> > +					buffer);
> > +			if (ret != clustcount * mydata->clust_size) {
> > +				debug("Error writing data (got %d)\n", ret);
> > +				return -1;
> > +			}
> > +
> > +			size -= wsize;
> > +			buffer += wsize;
> > +			*gotsize += wsize;
> > +
> > +			startsect += clustcount * mydata->clust_size;
> > +		} else {
> > +			for (i = 0; i < clustcount; i++) {
> > +				memcpy(tmpbuf_cluster, buffer, bytesperclust);
> > +				ret = disk_write(startsect, mydata->clust_size,
> > +								tmpbuf_cluster);
> > +				if (ret != mydata->clust_size) {
> > +					debug("Error writing data (got %d)\n",
> > +									ret);
> > +					return -1;
> > +				}
> > +
> > +				size -= bytesperclust;
> > +				buffer += bytesperclust;
> > +				*gotsize += bytesperclust;
> > +
> > +				startsect += mydata->clust_size;
> > +			}
> > +		}
> > +	}
> > +
> > +	/* partial write at end */
> > +	if (size) {
> > +		wsize = size;
> > +		ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
> > +		if (ret != mydata->clust_size) {
> > +			debug("Error reading data (got %d)\n", ret);
> > +			return -1;
> > +		}
> > +		memcpy(tmpbuf_cluster, buffer, wsize);
> > +		ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
> > +		if (ret != mydata->clust_size) {
> > +			debug("Error writing data (got %d)\n", ret);
> > +			return -1;
> > +		}
> > +
> > +		size -= wsize;
> > +		buffer += wsize;
> > +		*gotsize += wsize;
> > +	}
> > +
> > +	assert(!size);
> > +
> > +	return 0;
> > +}
> > +
> >  /*
> >   * Find the first empty cluster
> >   */
> > @@ -579,26 +693,158 @@ set_contents(fsdata *mydata, dir_entry *dentptr, loff_t pos, __u8 *buffer,
> >  	unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
> >  	__u32 curclust = START(dentptr);
> >  	__u32 endclust = 0, newclust = 0;
> > -	loff_t actsize;
> > +	loff_t cur_pos, offset, actsize, wsize;
> >  
> >  	*gotsize = 0;
> > -	filesize = maxsize;
> > +	filesize = pos + maxsize;
> >  
> >  	debug("%llu bytes\n", filesize);
> >  
> > -	if (curclust) {
> > -		/*
> > -		 * release already-allocated clusters anyway
> > -		 */
> > -		if (clear_fatent(mydata, curclust)) {
> > -			printf("Error: clearing FAT entries\n");
> > +	if (!filesize) {
> > +		if (!curclust)
> > +			return 0;
> > +		if (!CHECK_CLUST(curclust, mydata->fatsize) ||
> > +		    IS_LAST_CLUST(curclust, mydata->fatsize)) {
> > +			clear_fatent(mydata, curclust);
> > +			set_start_cluster(mydata, dentptr, 0);
> > +			return 0;
> > +		}
> > +		debug("curclust: 0x%x\n", curclust);
> > +		debug("Invalid FAT entry\n");
> > +		return -1;
> > +	}
> > +
> > +	if (!curclust) {
> > +		assert(pos == 0);
> > +		goto set_clusters;
> > +	}
> > +
> > +	/* go to cluster at pos */
> > +	cur_pos = bytesperclust;
> > +	while (1) {
> > +		if (pos <= cur_pos)
> > +			break;
> > +		if (IS_LAST_CLUST(curclust, mydata->fatsize))
> > +			break;
> > +
> > +		newclust = get_fatent(mydata, curclust);
> > +		if (!IS_LAST_CLUST(newclust, mydata->fatsize) &&
> > +		    CHECK_CLUST(newclust, mydata->fatsize)) {
> > +			debug("curclust: 0x%x\n", curclust);
> > +			debug("Invalid FAT entry\n");
> >  			return -1;
> >  		}
> > +
> > +		cur_pos += bytesperclust;
> > +		curclust = newclust;
> > +	}
> > +	if (IS_LAST_CLUST(curclust, mydata->fatsize)) {
> > +		assert(pos == cur_pos);
> > +		goto set_clusters;
> >  	}
> >  
> > -	curclust = find_empty_cluster(mydata);
> > -	set_start_cluster(mydata, dentptr, curclust);
> > +	assert(pos < cur_pos);
> > +	cur_pos -= bytesperclust;
> >  
> > +	/* overwrite */
> > +	assert(IS_LAST_CLUST(curclust, mydata->fatsize) ||
> > +	       !CHECK_CLUST(curclust, mydata->fatsize));
> > +
> > +	while (1) {
> > +		/* search for allocated consecutive clusters */
> > +		actsize = bytesperclust;
> > +		endclust = curclust;
> > +		while (1) {
> > +			if (filesize <= (cur_pos + actsize))
> > +				break;
> > +
> > +			newclust = get_fatent(mydata, endclust);
> > +
> > +			if (IS_LAST_CLUST(newclust, mydata->fatsize))
> > +				break;
> > +			if (CHECK_CLUST(newclust, mydata->fatsize)) {
> > +				debug("curclust: 0x%x\n", curclust);
> > +				debug("Invalid FAT entry\n");
> > +				return -1;
> > +			}
> > +
> > +			actsize += bytesperclust;
> > +			endclust = newclust;
> > +		}
> > +
> > +		/* overwrite to <curclust..endclust> */
> > +		if (pos < cur_pos)
> > +			offset = 0;
> > +		else
> > +			offset = pos - cur_pos;
> > +		wsize = min(cur_pos + actsize, filesize) - pos;
> > +		if (get_set_cluster(mydata, curclust, offset, buffer, wsize,
> > +								&actsize)) {
> > +			printf("Error get-and-setting cluster\n");
> > +			return -1;
> > +		}
> > +		buffer += wsize;
> > +		*gotsize += wsize;
> > +		cur_pos += offset + wsize;
> > +
> > +		if (filesize <= cur_pos)
> > +			break;
> > +
> > +		/* CHECK: newclust = get_fatent(mydata, endclust); */
> > +
> > +		if (IS_LAST_CLUST(newclust, mydata->fatsize))
> > +			/* no more clusters */
> > +			break;
> > +
> > +		curclust = newclust;
> > +	}
> > +
> > +	if (filesize <= cur_pos) {
> > +		/* no more write */
> > +		newclust = get_fatent(mydata, endclust);
> > +		if (!IS_LAST_CLUST(newclust, mydata->fatsize)) {
> > +			/* truncate the rest */
> > +			clear_fatent(mydata, newclust);
> > +
> > +			/* Mark end of file in FAT */
> > +			if (mydata->fatsize == 12)
> > +				newclust = 0xfff;
> > +			else if (mydata->fatsize == 16)
> > +				newclust = 0xffff;
> > +			else if (mydata->fatsize == 32)
> > +				newclust = 0xfffffff;
> > +			set_fatent_value(mydata, endclust, newclust);
> > +		}
> > +
> > +		return 0;
> > +	}
> > +
> > +	curclust = endclust;
> > +	filesize -= cur_pos;
> > +	assert(!(cur_pos % bytesperclust));
> > +
> > +set_clusters:
> > +	/* allocate and write */
> > +	assert(!pos);
> > +
> > +	/* Assure that curclust is valid */
> > +	if (!curclust) {
> > +		curclust = find_empty_cluster(mydata);
> > +		set_start_cluster(mydata, dentptr, curclust);
> > +	} else {
> > +		newclust = get_fatent(mydata, curclust);
> > +
> > +		if (IS_LAST_CLUST(newclust, mydata->fatsize)) {
> > +			newclust = determine_fatent(mydata, curclust);
> > +			set_fatent_value(mydata, curclust, newclust);
> > +			curclust = newclust;
> > +		} else {
> > +			debug("error: something wrong\n");
> > +			return -1;
> > +		}
> > +	}
> > +
> > +	/* TODO: already partially written */
> >  	if (check_overflow(mydata, curclust, filesize)) {
> >  		printf("Error: no space left: %llu\n", filesize);
> >  		return -1;
> > @@ -852,6 +1098,16 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
> >  			goto exit;
> >  		}
> >  
> > +		/* A file exists */
> > +		if (pos == -1)
> > +			/* Append to the end */
> > +			pos = FAT2CPU32(retdent->size);
> > +		if (pos > retdent->size) {
> > +			/* No hole allowed */
> > +			ret = -EINVAL;
> > +			goto exit;
> > +		}
> > +
> >  		/* Update file size in a directory entry */
> >  		retdent->size = cpu_to_le32(pos + size);
> >  	} else {
> > @@ -872,6 +1128,12 @@ int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
> >  			goto exit;
> >  		}
> >  
> > +		if (pos) {
> > +			/* No hole allowed */
> > +			ret = -EINVAL;
> > +			goto exit;
> > +		}
> > +
> >  		memset(itr->dent, 0, sizeof(*itr->dent));
> >  
> >  		/* Set short name to set alias checksum field in dir_slot */
> > @@ -921,10 +1183,5 @@ exit:
> >  int file_fat_write(const char *filename, void *buffer, loff_t offset,
> >  		   loff_t maxsize, loff_t *actwrite)
> >  {
> > -	if (offset != 0) {
> > -		printf("Error: non zero offset is currently not supported.\n");
> > -		return -EINVAL;
> > -	}
> > -
> >  	return file_fat_write_at(filename, offset, buffer, maxsize, actwrite);
> >  }
> > 
>
diff mbox series

Patch

diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c
index 3a9c53e253..cc45a33876 100644
--- a/fs/fat/fat_write.c
+++ b/fs/fat/fat_write.c
@@ -450,6 +450,120 @@  set_cluster(fsdata *mydata, __u32 clustnum, __u8 *buffer,
 	return 0;
 }
 
+static __u8 tmpbuf_cluster[MAX_CLUSTSIZE] __aligned(ARCH_DMA_MINALIGN);
+
+/*
+ * Read and modify data on existing and consecutive cluster blocks
+ */
+static int
+get_set_cluster(fsdata *mydata, __u32 clustnum, loff_t pos, __u8 *buffer,
+		loff_t size, loff_t *gotsize)
+{
+	unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
+	__u32 startsect;
+	loff_t wsize;
+	int clustcount, i, ret;
+
+	*gotsize = 0;
+	if (!size)
+		return 0;
+
+	assert(pos < bytesperclust);
+	startsect = clust_to_sect(mydata, clustnum);
+
+	debug("clustnum: %d, startsect: %d, pos: %lld\n", clustnum, startsect,
+									pos);
+
+	/* partial write at beginning */
+	if (pos) {
+		wsize = min(bytesperclust - pos, size);
+		ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
+		if (ret != mydata->clust_size) {
+			debug("Error reading data (got %d)\n", ret);
+			return -1;
+		}
+
+		memcpy(tmpbuf_cluster + pos, buffer, wsize);
+		ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
+		if (ret != mydata->clust_size) {
+			debug("Error writing data (got %d)\n", ret);
+			return -1;
+		}
+
+		size -= wsize;
+		buffer += wsize;
+		*gotsize += wsize;
+
+		startsect += mydata->clust_size;
+
+		if (!size)
+			return 0;
+	}
+
+	/* full-cluster write */
+	if (size >= bytesperclust) {
+		clustcount = lldiv(size, bytesperclust);
+
+		if (!((unsigned long)buffer & (ARCH_DMA_MINALIGN - 1))) {
+			wsize = clustcount * bytesperclust;
+			ret = disk_write(startsect,
+					clustcount * mydata->clust_size,
+					buffer);
+			if (ret != clustcount * mydata->clust_size) {
+				debug("Error writing data (got %d)\n", ret);
+				return -1;
+			}
+
+			size -= wsize;
+			buffer += wsize;
+			*gotsize += wsize;
+
+			startsect += clustcount * mydata->clust_size;
+		} else {
+			for (i = 0; i < clustcount; i++) {
+				memcpy(tmpbuf_cluster, buffer, bytesperclust);
+				ret = disk_write(startsect, mydata->clust_size,
+								tmpbuf_cluster);
+				if (ret != mydata->clust_size) {
+					debug("Error writing data (got %d)\n",
+									ret);
+					return -1;
+				}
+
+				size -= bytesperclust;
+				buffer += bytesperclust;
+				*gotsize += bytesperclust;
+
+				startsect += mydata->clust_size;
+			}
+		}
+	}
+
+	/* partial write at end */
+	if (size) {
+		wsize = size;
+		ret = disk_read(startsect, mydata->clust_size, tmpbuf_cluster);
+		if (ret != mydata->clust_size) {
+			debug("Error reading data (got %d)\n", ret);
+			return -1;
+		}
+		memcpy(tmpbuf_cluster, buffer, wsize);
+		ret = disk_write(startsect, mydata->clust_size, tmpbuf_cluster);
+		if (ret != mydata->clust_size) {
+			debug("Error writing data (got %d)\n", ret);
+			return -1;
+		}
+
+		size -= wsize;
+		buffer += wsize;
+		*gotsize += wsize;
+	}
+
+	assert(!size);
+
+	return 0;
+}
+
 /*
  * Find the first empty cluster
  */
@@ -579,26 +693,158 @@  set_contents(fsdata *mydata, dir_entry *dentptr, loff_t pos, __u8 *buffer,
 	unsigned int bytesperclust = mydata->clust_size * mydata->sect_size;
 	__u32 curclust = START(dentptr);
 	__u32 endclust = 0, newclust = 0;
-	loff_t actsize;
+	loff_t cur_pos, offset, actsize, wsize;
 
 	*gotsize = 0;
-	filesize = maxsize;
+	filesize = pos + maxsize;
 
 	debug("%llu bytes\n", filesize);
 
-	if (curclust) {
-		/*
-		 * release already-allocated clusters anyway
-		 */
-		if (clear_fatent(mydata, curclust)) {
-			printf("Error: clearing FAT entries\n");
+	if (!filesize) {
+		if (!curclust)
+			return 0;
+		if (!CHECK_CLUST(curclust, mydata->fatsize) ||
+		    IS_LAST_CLUST(curclust, mydata->fatsize)) {
+			clear_fatent(mydata, curclust);
+			set_start_cluster(mydata, dentptr, 0);
+			return 0;
+		}
+		debug("curclust: 0x%x\n", curclust);
+		debug("Invalid FAT entry\n");
+		return -1;
+	}
+
+	if (!curclust) {
+		assert(pos == 0);
+		goto set_clusters;
+	}
+
+	/* go to cluster at pos */
+	cur_pos = bytesperclust;
+	while (1) {
+		if (pos <= cur_pos)
+			break;
+		if (IS_LAST_CLUST(curclust, mydata->fatsize))
+			break;
+
+		newclust = get_fatent(mydata, curclust);
+		if (!IS_LAST_CLUST(newclust, mydata->fatsize) &&
+		    CHECK_CLUST(newclust, mydata->fatsize)) {
+			debug("curclust: 0x%x\n", curclust);
+			debug("Invalid FAT entry\n");
 			return -1;
 		}
+
+		cur_pos += bytesperclust;
+		curclust = newclust;
+	}
+	if (IS_LAST_CLUST(curclust, mydata->fatsize)) {
+		assert(pos == cur_pos);
+		goto set_clusters;
 	}
 
-	curclust = find_empty_cluster(mydata);
-	set_start_cluster(mydata, dentptr, curclust);
+	assert(pos < cur_pos);
+	cur_pos -= bytesperclust;
 
+	/* overwrite */
+	assert(IS_LAST_CLUST(curclust, mydata->fatsize) ||
+	       !CHECK_CLUST(curclust, mydata->fatsize));
+
+	while (1) {
+		/* search for allocated consecutive clusters */
+		actsize = bytesperclust;
+		endclust = curclust;
+		while (1) {
+			if (filesize <= (cur_pos + actsize))
+				break;
+
+			newclust = get_fatent(mydata, endclust);
+
+			if (IS_LAST_CLUST(newclust, mydata->fatsize))
+				break;
+			if (CHECK_CLUST(newclust, mydata->fatsize)) {
+				debug("curclust: 0x%x\n", curclust);
+				debug("Invalid FAT entry\n");
+				return -1;
+			}
+
+			actsize += bytesperclust;
+			endclust = newclust;
+		}
+
+		/* overwrite to <curclust..endclust> */
+		if (pos < cur_pos)
+			offset = 0;
+		else
+			offset = pos - cur_pos;
+		wsize = min(cur_pos + actsize, filesize) - pos;
+		if (get_set_cluster(mydata, curclust, offset, buffer, wsize,
+								&actsize)) {
+			printf("Error get-and-setting cluster\n");
+			return -1;
+		}
+		buffer += wsize;
+		*gotsize += wsize;
+		cur_pos += offset + wsize;
+
+		if (filesize <= cur_pos)
+			break;
+
+		/* CHECK: newclust = get_fatent(mydata, endclust); */
+
+		if (IS_LAST_CLUST(newclust, mydata->fatsize))
+			/* no more clusters */
+			break;
+
+		curclust = newclust;
+	}
+
+	if (filesize <= cur_pos) {
+		/* no more write */
+		newclust = get_fatent(mydata, endclust);
+		if (!IS_LAST_CLUST(newclust, mydata->fatsize)) {
+			/* truncate the rest */
+			clear_fatent(mydata, newclust);
+
+			/* Mark end of file in FAT */
+			if (mydata->fatsize == 12)
+				newclust = 0xfff;
+			else if (mydata->fatsize == 16)
+				newclust = 0xffff;
+			else if (mydata->fatsize == 32)
+				newclust = 0xfffffff;
+			set_fatent_value(mydata, endclust, newclust);
+		}
+
+		return 0;
+	}
+
+	curclust = endclust;
+	filesize -= cur_pos;
+	assert(!(cur_pos % bytesperclust));
+
+set_clusters:
+	/* allocate and write */
+	assert(!pos);
+
+	/* Assure that curclust is valid */
+	if (!curclust) {
+		curclust = find_empty_cluster(mydata);
+		set_start_cluster(mydata, dentptr, curclust);
+	} else {
+		newclust = get_fatent(mydata, curclust);
+
+		if (IS_LAST_CLUST(newclust, mydata->fatsize)) {
+			newclust = determine_fatent(mydata, curclust);
+			set_fatent_value(mydata, curclust, newclust);
+			curclust = newclust;
+		} else {
+			debug("error: something wrong\n");
+			return -1;
+		}
+	}
+
+	/* TODO: already partially written */
 	if (check_overflow(mydata, curclust, filesize)) {
 		printf("Error: no space left: %llu\n", filesize);
 		return -1;
@@ -852,6 +1098,16 @@  int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
 			goto exit;
 		}
 
+		/* A file exists */
+		if (pos == -1)
+			/* Append to the end */
+			pos = FAT2CPU32(retdent->size);
+		if (pos > retdent->size) {
+			/* No hole allowed */
+			ret = -EINVAL;
+			goto exit;
+		}
+
 		/* Update file size in a directory entry */
 		retdent->size = cpu_to_le32(pos + size);
 	} else {
@@ -872,6 +1128,12 @@  int file_fat_write_at(const char *filename, loff_t pos, void *buffer,
 			goto exit;
 		}
 
+		if (pos) {
+			/* No hole allowed */
+			ret = -EINVAL;
+			goto exit;
+		}
+
 		memset(itr->dent, 0, sizeof(*itr->dent));
 
 		/* Set short name to set alias checksum field in dir_slot */
@@ -921,10 +1183,5 @@  exit:
 int file_fat_write(const char *filename, void *buffer, loff_t offset,
 		   loff_t maxsize, loff_t *actwrite)
 {
-	if (offset != 0) {
-		printf("Error: non zero offset is currently not supported.\n");
-		return -EINVAL;
-	}
-
 	return file_fat_write_at(filename, offset, buffer, maxsize, actwrite);
 }