Skip to content

Commit

Permalink
vfs: move the generic write and copy checks out of mm
Browse files Browse the repository at this point in the history
The generic write check helpers also don't have much to do with the page
cache, so move them to the vfs.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
  • Loading branch information
djwong committed Oct 15, 2020
1 parent 1b2c54d commit 407e9c6
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 146 deletions.
143 changes: 143 additions & 0 deletions fs/read_write.c
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,59 @@ static ssize_t do_copy_file_range(struct file *file_in, loff_t pos_in,
flags);
}

/*
* Performs necessary checks before doing a file copy
*
* Can adjust amount of bytes to copy via @req_count argument.
* Returns appropriate error code that caller should return or
* zero in case the copy should be allowed.
*/
static int generic_copy_file_checks(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
size_t *req_count, unsigned int flags)
{
struct inode *inode_in = file_inode(file_in);
struct inode *inode_out = file_inode(file_out);
uint64_t count = *req_count;
loff_t size_in;
int ret;

ret = generic_file_rw_checks(file_in, file_out);
if (ret)
return ret;

/* Don't touch certain kinds of inodes */
if (IS_IMMUTABLE(inode_out))
return -EPERM;

if (IS_SWAPFILE(inode_in) || IS_SWAPFILE(inode_out))
return -ETXTBSY;

/* Ensure offsets don't wrap. */
if (pos_in + count < pos_in || pos_out + count < pos_out)
return -EOVERFLOW;

/* Shorten the copy to EOF */
size_in = i_size_read(inode_in);
if (pos_in >= size_in)
count = 0;
else
count = min(count, size_in - (uint64_t)pos_in);

ret = generic_write_check_limits(file_out, pos_out, &count);
if (ret)
return ret;

/* Don't allow overlapped copying within the same file. */
if (inode_in == inode_out &&
pos_out + count > pos_in &&
pos_out < pos_in + count)
return -EINVAL;

*req_count = count;
return 0;
}

/*
* copy_file_range() differs from regular file read and write in that it
* specifically allows return partial success. When it does so is up to
Expand Down Expand Up @@ -1832,3 +1885,93 @@ SYSCALL_DEFINE6(copy_file_range, int, fd_in, loff_t __user *, off_in,
out2:
return ret;
}

/*
* 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.
*/
int generic_write_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;
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);
}

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;
}

/*
* Performs necessary checks before doing a write
*
* Can adjust writing position or amount of bytes to write.
* Returns appropriate error code that caller should return or
* zero in case that write should be allowed.
*/
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;
loff_t count;
int ret;

if (IS_SWAPFILE(inode))
return -ETXTBSY;

if (!iov_iter_count(from))
return 0;

/* FIXME: this is for backwards compatibility with 2.4 */
if (iocb->ki_flags & IOCB_APPEND)
iocb->ki_pos = i_size_read(inode);

if ((iocb->ki_flags & IOCB_NOWAIT) && !(iocb->ki_flags & IOCB_DIRECT))
return -EINVAL;

count = iov_iter_count(from);
ret = generic_write_check_limits(file, iocb->ki_pos, &count);
if (ret)
return ret;

iov_iter_truncate(from, count);
return iov_iter_count(from);
}
EXPORT_SYMBOL(generic_write_checks);

/*
* Performs common checks before doing a file copy/clone
* from @file_in to @file_out.
*/
int generic_file_rw_checks(struct file *file_in, struct file *file_out)
{
struct inode *inode_in = file_inode(file_in);
struct inode *inode_out = file_inode(file_out);

/* Don't copy dirs, pipes, sockets... */
if (S_ISDIR(inode_in->i_mode) || S_ISDIR(inode_out->i_mode))
return -EISDIR;
if (!S_ISREG(inode_in->i_mode) || !S_ISREG(inode_out->i_mode))
return -EINVAL;

if (!(file_in->f_mode & FMODE_READ) ||
!(file_out->f_mode & FMODE_WRITE) ||
(file_out->f_flags & O_APPEND))
return -EBADF;

return 0;
}
3 changes: 0 additions & 3 deletions include/linux/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -3012,9 +3012,6 @@ extern ssize_t generic_write_checks(struct kiocb *, struct iov_iter *);
extern int generic_write_check_limits(struct file *file, loff_t pos,
loff_t *count);
extern int generic_file_rw_checks(struct file *file_in, struct file *file_out);
extern int generic_copy_file_checks(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
size_t *count, unsigned int flags);
extern ssize_t generic_file_buffered_read(struct kiocb *iocb,
struct iov_iter *to, ssize_t already_read);
extern ssize_t generic_file_read_iter(struct kiocb *, struct iov_iter *);
Expand Down
143 changes: 0 additions & 143 deletions mm/filemap.c
Original file line number Diff line number Diff line change
Expand Up @@ -3093,149 +3093,6 @@ 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.
*/
int generic_write_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;
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);
}

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;
}

/*
* Performs necessary checks before doing a write
*
* Can adjust writing position or amount of bytes to write.
* Returns appropriate error code that caller should return or
* zero in case that write should be allowed.
*/
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;
loff_t count;
int ret;

if (IS_SWAPFILE(inode))
return -ETXTBSY;

if (!iov_iter_count(from))
return 0;

/* FIXME: this is for backwards compatibility with 2.4 */
if (iocb->ki_flags & IOCB_APPEND)
iocb->ki_pos = i_size_read(inode);

if ((iocb->ki_flags & IOCB_NOWAIT) && !(iocb->ki_flags & IOCB_DIRECT))
return -EINVAL;

count = iov_iter_count(from);
ret = generic_write_check_limits(file, iocb->ki_pos, &count);
if (ret)
return ret;

iov_iter_truncate(from, count);
return iov_iter_count(from);
}
EXPORT_SYMBOL(generic_write_checks);

/*
* Performs common checks before doing a file copy/clone
* from @file_in to @file_out.
*/
int generic_file_rw_checks(struct file *file_in, struct file *file_out)
{
struct inode *inode_in = file_inode(file_in);
struct inode *inode_out = file_inode(file_out);

/* Don't copy dirs, pipes, sockets... */
if (S_ISDIR(inode_in->i_mode) || S_ISDIR(inode_out->i_mode))
return -EISDIR;
if (!S_ISREG(inode_in->i_mode) || !S_ISREG(inode_out->i_mode))
return -EINVAL;

if (!(file_in->f_mode & FMODE_READ) ||
!(file_out->f_mode & FMODE_WRITE) ||
(file_out->f_flags & O_APPEND))
return -EBADF;

return 0;
}

/*
* Performs necessary checks before doing a file copy
*
* Can adjust amount of bytes to copy via @req_count argument.
* Returns appropriate error code that caller should return or
* zero in case the copy should be allowed.
*/
int generic_copy_file_checks(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
size_t *req_count, unsigned int flags)
{
struct inode *inode_in = file_inode(file_in);
struct inode *inode_out = file_inode(file_out);
uint64_t count = *req_count;
loff_t size_in;
int ret;

ret = generic_file_rw_checks(file_in, file_out);
if (ret)
return ret;

/* Don't touch certain kinds of inodes */
if (IS_IMMUTABLE(inode_out))
return -EPERM;

if (IS_SWAPFILE(inode_in) || IS_SWAPFILE(inode_out))
return -ETXTBSY;

/* Ensure offsets don't wrap. */
if (pos_in + count < pos_in || pos_out + count < pos_out)
return -EOVERFLOW;

/* Shorten the copy to EOF */
size_in = i_size_read(inode_in);
if (pos_in >= size_in)
count = 0;
else
count = min(count, size_in - (uint64_t)pos_in);

ret = generic_write_check_limits(file_out, pos_out, &count);
if (ret)
return ret;

/* Don't allow overlapped copying within the same file. */
if (inode_in == inode_out &&
pos_out + count > pos_in &&
pos_out < pos_in + count)
return -EINVAL;

*req_count = count;
return 0;
}

int pagecache_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata)
Expand Down

0 comments on commit 407e9c6

Please sign in to comment.