vfs: move the generic write and copy checks out of mm
[sfrench/cifs-2.6.git] / fs / read_write.c
index f0877f1c0c4956299e93991d740c60bdac78e7a6..016444255d3e6f1170652757e54117e96e89d2c3 100644 (file)
@@ -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
@@ -1832,3 +1885,93 @@ out1:
 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;
+}