255 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			255 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From c001596110e834a85b01a47a20811b318cb3b9e4 Mon Sep 17 00:00:00 2001
 | |
| From: Theodore Ts'o <tytso@mit.edu>
 | |
| Date: Fri, 26 Feb 2021 17:41:06 -0500
 | |
| Subject: [PATCH] libext2fs: fix unix_io's Direct I/O support
 | |
| 
 | |
| The previous Direct I/O support worked on HDD's with 512 byte logical
 | |
| sector sizes, and on FreeBSD which required 4k aligned memory buffers.
 | |
| However, it was incomplete and was not correctly working on HDD's with
 | |
| 4k logical sector sizes (aka Advanced Format Disks).
 | |
| 
 | |
| Based on a patch from Alexey Lyashkov <alexey.lyashkov@hpe.com> but
 | |
| rewritten to work with the latest e2fsprogs and to minimize changes to
 | |
| make it easier to review.
 | |
| 
 | |
| Signed-off-by: Theodore Ts'o <tytso@mit.edu>
 | |
| Reported-by: Artem Blagodarenko <artem.blagodarenko@gmail.com>
 | |
| Signed-off-by: Pavel Reichl <preichl@redhat.com>
 | |
| ---
 | |
|  lib/ext2fs/io_manager.c |  6 ++-
 | |
|  lib/ext2fs/unix_io.c    | 87 +++++++++++++++++++++++++++++++----------
 | |
|  2 files changed, 70 insertions(+), 23 deletions(-)
 | |
| 
 | |
| Index: e2fsprogs-1.45.6/lib/ext2fs/io_manager.c
 | |
| ===================================================================
 | |
| --- e2fsprogs-1.45.6.orig/lib/ext2fs/io_manager.c
 | |
| +++ e2fsprogs-1.45.6/lib/ext2fs/io_manager.c
 | |
| @@ -134,9 +134,11 @@ errcode_t io_channel_alloc_buf(io_channe
 | |
|  	else
 | |
|  		size = -count;
 | |
|  
 | |
| -	if (io->align)
 | |
| +	if (io->align) {
 | |
| +		if (io->align > size)
 | |
| +			size = io->align;
 | |
|  		return ext2fs_get_memalign(size, io->align, ptr);
 | |
| -	else
 | |
| +	} else
 | |
|  		return ext2fs_get_mem(size, ptr);
 | |
|  }
 | |
|  
 | |
| Index: e2fsprogs-1.45.6/lib/ext2fs/unix_io.c
 | |
| ===================================================================
 | |
| --- e2fsprogs-1.45.6.orig/lib/ext2fs/unix_io.c
 | |
| +++ e2fsprogs-1.45.6/lib/ext2fs/unix_io.c
 | |
| @@ -165,13 +165,15 @@ static errcode_t raw_read_blk(io_channel
 | |
|  	int		actual = 0;
 | |
|  	unsigned char	*buf = bufv;
 | |
|  	ssize_t		really_read = 0;
 | |
| +	unsigned long long aligned_blk;
 | |
| +	int		align_size, offset;
 | |
|  
 | |
|  	size = (count < 0) ? -count : (ext2_loff_t) count * channel->block_size;
 | |
|  	data->io_stats.bytes_read += size;
 | |
|  	location = ((ext2_loff_t) block * channel->block_size) + data->offset;
 | |
|  
 | |
|  	if (data->flags & IO_FLAG_FORCE_BOUNCE) {
 | |
| -		if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) {
 | |
| +		if (ext2fs_llseek(data->dev, location, SEEK_SET) < 0) {
 | |
|  			retval = errno ? errno : EXT2_ET_LLSEEK_FAILED;
 | |
|  			goto error_out;
 | |
|  		}
 | |
| @@ -182,6 +184,7 @@ static errcode_t raw_read_blk(io_channel
 | |
|  	/* Try an aligned pread */
 | |
|  	if ((channel->align == 0) ||
 | |
|  	    (IS_ALIGNED(buf, channel->align) &&
 | |
| +	     IS_ALIGNED(location, channel->align) &&
 | |
|  	     IS_ALIGNED(size, channel->align))) {
 | |
|  		actual = pread64(data->dev, buf, size, location);
 | |
|  		if (actual == size)
 | |
| @@ -193,6 +196,7 @@ static errcode_t raw_read_blk(io_channel
 | |
|  	if ((sizeof(off_t) >= sizeof(ext2_loff_t)) &&
 | |
|  	    ((channel->align == 0) ||
 | |
|  	     (IS_ALIGNED(buf, channel->align) &&
 | |
| +	      IS_ALIGNED(location, channel->align) &&
 | |
|  	      IS_ALIGNED(size, channel->align)))) {
 | |
|  		actual = pread(data->dev, buf, size, location);
 | |
|  		if (actual == size)
 | |
| @@ -201,12 +205,13 @@ static errcode_t raw_read_blk(io_channel
 | |
|  	}
 | |
|  #endif /* HAVE_PREAD */
 | |
|  
 | |
| -	if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) {
 | |
| +	if (ext2fs_llseek(data->dev, location, SEEK_SET) < 0) {
 | |
|  		retval = errno ? errno : EXT2_ET_LLSEEK_FAILED;
 | |
|  		goto error_out;
 | |
|  	}
 | |
|  	if ((channel->align == 0) ||
 | |
|  	    (IS_ALIGNED(buf, channel->align) &&
 | |
| +	     IS_ALIGNED(location, channel->align) &&
 | |
|  	     IS_ALIGNED(size, channel->align))) {
 | |
|  		actual = read(data->dev, buf, size);
 | |
|  		if (actual != size) {
 | |
| @@ -231,21 +236,37 @@ static errcode_t raw_read_blk(io_channel
 | |
|  	 * to the O_DIRECT rules, so we need to do this the hard way...
 | |
|  	 */
 | |
|  bounce_read:
 | |
| +	if ((channel->block_size > channel->align) &&
 | |
| +	    (channel->block_size % channel->align) == 0)
 | |
| +		align_size = channel->block_size;
 | |
| +	else
 | |
| +		align_size = channel->align;
 | |
| +	aligned_blk = location / align_size;
 | |
| +	offset = location % align_size;
 | |
| +
 | |
| +	if (ext2fs_llseek(data->dev, aligned_blk * align_size, SEEK_SET) < 0) {
 | |
| +		retval = errno ? errno : EXT2_ET_LLSEEK_FAILED;
 | |
| +		goto error_out;
 | |
| +	}
 | |
|  	while (size > 0) {
 | |
| -		actual = read(data->dev, data->bounce, channel->block_size);
 | |
| -		if (actual != channel->block_size) {
 | |
| +		actual = read(data->dev, data->bounce, align_size);
 | |
| +		if (actual != align_size) {
 | |
|  			actual = really_read;
 | |
|  			buf -= really_read;
 | |
|  			size += really_read;
 | |
|  			goto short_read;
 | |
|  		}
 | |
| -		actual = size;
 | |
| -		if (size > channel->block_size)
 | |
| -			actual = channel->block_size;
 | |
| -		memcpy(buf, data->bounce, actual);
 | |
| +		if ((actual + offset) > align_size)
 | |
| +			actual = align_size - offset;
 | |
| +		if (actual > size)
 | |
| +			actual = size;
 | |
| +		memcpy(buf, data->bounce + offset, actual);
 | |
| +
 | |
|  		really_read += actual;
 | |
|  		size -= actual;
 | |
|  		buf += actual;
 | |
| +		offset = 0;
 | |
| +		aligned_blk++;
 | |
|  	}
 | |
|  	return 0;
 | |
|  
 | |
| @@ -268,6 +289,8 @@ static errcode_t raw_write_blk(io_channe
 | |
|  	int		actual = 0;
 | |
|  	errcode_t	retval;
 | |
|  	const unsigned char *buf = bufv;
 | |
| +	unsigned long long aligned_blk;
 | |
| +	int		align_size, offset;
 | |
|  
 | |
|  	if (count == 1)
 | |
|  		size = channel->block_size;
 | |
| @@ -282,7 +305,7 @@ static errcode_t raw_write_blk(io_channe
 | |
|  	location = ((ext2_loff_t) block * channel->block_size) + data->offset;
 | |
|  
 | |
|  	if (data->flags & IO_FLAG_FORCE_BOUNCE) {
 | |
| -		if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) {
 | |
| +		if (ext2fs_llseek(data->dev, location, SEEK_SET) < 0) {
 | |
|  			retval = errno ? errno : EXT2_ET_LLSEEK_FAILED;
 | |
|  			goto error_out;
 | |
|  		}
 | |
| @@ -293,6 +316,7 @@ static errcode_t raw_write_blk(io_channe
 | |
|  	/* Try an aligned pwrite */
 | |
|  	if ((channel->align == 0) ||
 | |
|  	    (IS_ALIGNED(buf, channel->align) &&
 | |
| +	     IS_ALIGNED(location, channel->align) &&
 | |
|  	     IS_ALIGNED(size, channel->align))) {
 | |
|  		actual = pwrite64(data->dev, buf, size, location);
 | |
|  		if (actual == size)
 | |
| @@ -303,6 +327,7 @@ static errcode_t raw_write_blk(io_channe
 | |
|  	if ((sizeof(off_t) >= sizeof(ext2_loff_t)) &&
 | |
|  	    ((channel->align == 0) ||
 | |
|  	     (IS_ALIGNED(buf, channel->align) &&
 | |
| +	      IS_ALIGNED(location, channel->align) &&
 | |
|  	      IS_ALIGNED(size, channel->align)))) {
 | |
|  		actual = pwrite(data->dev, buf, size, location);
 | |
|  		if (actual == size)
 | |
| @@ -310,13 +335,14 @@ static errcode_t raw_write_blk(io_channe
 | |
|  	}
 | |
|  #endif /* HAVE_PWRITE */
 | |
|  
 | |
| -	if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) {
 | |
| +	if (ext2fs_llseek(data->dev, location, SEEK_SET) < 0) {
 | |
|  		retval = errno ? errno : EXT2_ET_LLSEEK_FAILED;
 | |
|  		goto error_out;
 | |
|  	}
 | |
|  
 | |
|  	if ((channel->align == 0) ||
 | |
|  	    (IS_ALIGNED(buf, channel->align) &&
 | |
| +	     IS_ALIGNED(location, channel->align) &&
 | |
|  	     IS_ALIGNED(size, channel->align))) {
 | |
|  		actual = write(data->dev, buf, size);
 | |
|  		if (actual < 0) {
 | |
| @@ -340,37 +366,56 @@ static errcode_t raw_write_blk(io_channe
 | |
|  	 * to the O_DIRECT rules, so we need to do this the hard way...
 | |
|  	 */
 | |
|  bounce_write:
 | |
| +	if ((channel->block_size > channel->align) &&
 | |
| +	    (channel->block_size % channel->align) == 0)
 | |
| +		align_size = channel->block_size;
 | |
| +	else
 | |
| +		align_size = channel->align;
 | |
| +	aligned_blk = location / align_size;
 | |
| +	offset = location % align_size;
 | |
| +
 | |
|  	while (size > 0) {
 | |
| -		if (size < channel->block_size) {
 | |
| +		int actual_w;
 | |
| +
 | |
| +		if (size < align_size || offset) {
 | |
| +			if (ext2fs_llseek(data->dev, aligned_blk * align_size,
 | |
| +					  SEEK_SET) < 0) {
 | |
| +				retval = errno ? errno : EXT2_ET_LLSEEK_FAILED;
 | |
| +				goto error_out;
 | |
| +			}
 | |
|  			actual = read(data->dev, data->bounce,
 | |
| -				      channel->block_size);
 | |
| -			if (actual != channel->block_size) {
 | |
| +				      align_size);
 | |
| +			if (actual != align_size) {
 | |
|  				if (actual < 0) {
 | |
|  					retval = errno;
 | |
|  					goto error_out;
 | |
|  				}
 | |
|  				memset((char *) data->bounce + actual, 0,
 | |
| -				       channel->block_size - actual);
 | |
| +				       align_size - actual);
 | |
|  			}
 | |
|  		}
 | |
|  		actual = size;
 | |
| -		if (size > channel->block_size)
 | |
| -			actual = channel->block_size;
 | |
| -		memcpy(data->bounce, buf, actual);
 | |
| -		if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) {
 | |
| +		if ((actual + offset) > align_size)
 | |
| +			actual = align_size - offset;
 | |
| +		if (actual > size)
 | |
| +			actual = size;
 | |
| +		memcpy(((char *)data->bounce) + offset, buf, actual);
 | |
| +		if (ext2fs_llseek(data->dev, aligned_blk * align_size, SEEK_SET) < 0) {
 | |
|  			retval = errno ? errno : EXT2_ET_LLSEEK_FAILED;
 | |
|  			goto error_out;
 | |
|  		}
 | |
| -		actual = write(data->dev, data->bounce, channel->block_size);
 | |
| -		if (actual < 0) {
 | |
| +		actual_w = write(data->dev, data->bounce, align_size);
 | |
| +		if (actual_w < 0) {
 | |
|  			retval = errno;
 | |
|  			goto error_out;
 | |
|  		}
 | |
| -		if (actual != channel->block_size)
 | |
| +		if (actual_w != align_size)
 | |
|  			goto short_write;
 | |
|  		size -= actual;
 | |
|  		buf += actual;
 | |
|  		location += actual;
 | |
| +		aligned_blk++;
 | |
| +		offset = 0;
 | |
|  	}
 | |
|  	return 0;
 | |
|  
 |