diff options
Diffstat (limited to 'mm')
| -rw-r--r-- | mm/filemap.c | 84 | 
1 files changed, 52 insertions, 32 deletions
diff --git a/mm/filemap.c b/mm/filemap.c index 47e6bfd45a91..84b7301e41a0 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -2916,6 +2916,42 @@ struct page *read_cache_page_gfp(struct address_space *mapping,  EXPORT_SYMBOL(read_cache_page_gfp);  /* + * Don't operate on ranges the page cache doesn't support, and don't exceed the + * LFS limits.  If pos is under the limit it becomes a short access.  If it + * exceeds the limit we return -EFBIG. + */ +static int generic_access_check_limits(struct file *file, loff_t pos, +				       loff_t *count) +{ +	struct inode *inode = file->f_mapping->host; +	loff_t max_size = inode->i_sb->s_maxbytes; + +	if (!(file->f_flags & O_LARGEFILE)) +		max_size = MAX_NON_LFS; + +	if (unlikely(pos >= max_size)) +		return -EFBIG; +	*count = min(*count, max_size - pos); +	return 0; +} + +static int generic_write_check_limits(struct file *file, loff_t pos, +				      loff_t *count) +{ +	loff_t limit = rlimit(RLIMIT_FSIZE); + +	if (limit != RLIM_INFINITY) { +		if (pos >= limit) { +			send_sig(SIGXFSZ, current, 0); +			return -EFBIG; +		} +		*count = min(*count, limit - pos); +	} + +	return generic_access_check_limits(file, pos, count); +} + +/*   * Performs necessary checks before doing a write   *   * Can adjust writing position or amount of bytes to write. @@ -2926,8 +2962,8 @@ inline ssize_t generic_write_checks(struct kiocb *iocb, struct iov_iter *from)  {  	struct file *file = iocb->ki_filp;  	struct inode *inode = file->f_mapping->host; -	unsigned long limit = rlimit(RLIMIT_FSIZE); -	loff_t pos; +	loff_t count; +	int ret;  	if (!iov_iter_count(from))  		return 0; @@ -2936,40 +2972,15 @@ inline ssize_t generic_write_checks(struct kiocb *iocb, struct iov_iter *from)  	if (iocb->ki_flags & IOCB_APPEND)  		iocb->ki_pos = i_size_read(inode); -	pos = iocb->ki_pos; -  	if ((iocb->ki_flags & IOCB_NOWAIT) && !(iocb->ki_flags & IOCB_DIRECT))  		return -EINVAL; -	if (limit != RLIM_INFINITY) { -		if (iocb->ki_pos >= limit) { -			send_sig(SIGXFSZ, current, 0); -			return -EFBIG; -		} -		iov_iter_truncate(from, limit - (unsigned long)pos); -	} - -	/* -	 * LFS rule -	 */ -	if (unlikely(pos + iov_iter_count(from) > MAX_NON_LFS && -				!(file->f_flags & O_LARGEFILE))) { -		if (pos >= MAX_NON_LFS) -			return -EFBIG; -		iov_iter_truncate(from, MAX_NON_LFS - (unsigned long)pos); -	} - -	/* -	 * Are we about to exceed the fs block limit ? -	 * -	 * If we have written data it becomes a short write.  If we have -	 * exceeded without writing data we send a signal and return EFBIG. -	 * Linus frestrict idea will clean these up nicely.. -	 */ -	if (unlikely(pos >= inode->i_sb->s_maxbytes)) -		return -EFBIG; +	count = iov_iter_count(from); +	ret = generic_write_check_limits(file, iocb->ki_pos, &count); +	if (ret) +		return ret; -	iov_iter_truncate(from, inode->i_sb->s_maxbytes - pos); +	iov_iter_truncate(from, count);  	return iov_iter_count(from);  }  EXPORT_SYMBOL(generic_write_checks); @@ -2991,6 +3002,7 @@ int generic_remap_checks(struct file *file_in, loff_t pos_in,  	uint64_t bcount;  	loff_t size_in, size_out;  	loff_t bs = inode_out->i_sb->s_blocksize; +	int ret;  	/* The start of both ranges must be aligned to an fs block. */  	if (!IS_ALIGNED(pos_in, bs) || !IS_ALIGNED(pos_out, bs)) @@ -3014,6 +3026,14 @@ int generic_remap_checks(struct file *file_in, loff_t pos_in,  		return -EINVAL;  	count = min(count, size_in - (uint64_t)pos_in); +	ret = generic_access_check_limits(file_in, pos_in, &count); +	if (ret) +		return ret; + +	ret = generic_write_check_limits(file_out, pos_out, &count); +	if (ret) +		return ret; +  	/*  	 * If the user wanted us to link to the infile's EOF, round up to the  	 * next block boundary for this check.  | 
