* the file range, inode's io_tree).
*/
BTRFS_INODE_NO_DELALLOC_FLUSH,
+ /*
+ * Set when we are dedupe'ing a file in order to block any mmap writes
+ * from occurring. This is because we use the generic checking to
+ * validate that the pages are the same, but we do not have the extent
+ * locked at this point to block mmaps. The trade-off of using the
+ * generic code is we need a separate mechanism to block mmaps in this
+ * case, otherwise we could race and modify pages in between checking if
+ * the pages are the same and locking the extents to do the
+ * deduplication.
+ */
+ BTRFS_INODE_DEDUPE,
};
/* in memory btrfs inode */
mod);
}
+static inline void btrfs_inode_dedupe(struct btrfs_inode *inode)
+{
+ set_bit(BTRFS_INODE_DEDUPE, &inode->runtime_flags);
+}
+
+static inline int btrfs_inode_dedupe_wait(struct btrfs_inode *inode)
+{
+ return wait_on_bit(&inode->runtime_flags, BTRFS_INODE_DEDUPE,
+ TASK_INTERRUPTIBLE);
+}
+
+static inline void btrfs_inode_dedupe_done(struct btrfs_inode *inode)
+{
+ clear_bit(BTRFS_INODE_DEDUPE, &inode->runtime_flags);
+ /*
+ * This is necessary because clear_bit doesn't imply a memory barrier,
+ * and we need the memory barrier for wake_up_bit().
+ */
+ smp_mb__after_atomic();
+ wake_up_bit(&inode->runtime_flags, BTRFS_INODE_DEDUPE);
+}
+
static inline int btrfs_inode_in_log(struct btrfs_inode *inode, u64 generation)
{
int ret = 0;
ret = VM_FAULT_NOPAGE; /* make the VM retry the fault */
again:
+ /* We must wait on dedupes to complete. */
+ if (btrfs_inode_dedupe_wait(BTRFS_I(inode)))
+ goto out;
lock_page(page);
+
+ /*
+ * If we raced and dedupe got set before we locked then we need to retry.
+ * If dedup comes in after this point we're OK because the verification
+ * step must lock this page for the filemap_flush(), so we will block
+ * that step of the dedup until we exit mkwrite, at which point we will
+ * be written out and marked clean again.
+ */
+ if (test_bit(BTRFS_INODE_DEDUPE, &BTRFS_I(inode)->runtime_flags)) {
+ unlock_page(page);
+ goto again;
+ }
size = i_size_read(inode);
if ((page->mapping != inode->i_mapping) ||
if (remap_flags & ~(REMAP_FILE_DEDUP | REMAP_FILE_ADVISORY))
return -EINVAL;
- if (same_inode)
+ /*
+ * We use btrfs_inode_dedup here to block concurrent mmaps during dedup.
+ * We do this because we use the generic helpers to validate that the
+ * ranges are indeed the same, however the appropriate locking is not
+ * done which makes it racy for us. The alternative is to stop using
+ * the generic checks and do the pages are the same checks internally
+ * inside btrfs, but since mmap is the only issue here simply block
+ * concurrent mmaps.
+ */
+ if (same_inode) {
inode_lock(src_inode);
- else
+ if (remap_flags & REMAP_FILE_DEDUP)
+ btrfs_inode_dedupe(BTRFS_I(src_inode));
+ } else {
lock_two_nondirectories(src_inode, dst_inode);
+ if (remap_flags & REMAP_FILE_DEDUP) {
+ btrfs_inode_dedupe(BTRFS_I(src_inode));
+ btrfs_inode_dedupe(BTRFS_I(dst_inode));
+ }
+ }
ret = btrfs_remap_file_range_prep(src_file, off, dst_file, destoff,
&len, remap_flags);
ret = btrfs_clone_files(dst_file, src_file, off, len, destoff);
out_unlock:
- if (same_inode)
+ if (same_inode) {
inode_unlock(src_inode);
- else
+ if (remap_flags & REMAP_FILE_DEDUP)
+ btrfs_inode_dedupe_done(BTRFS_I(src_inode));
+ } else {
unlock_two_nondirectories(src_inode, dst_inode);
+ if (remap_flags & REMAP_FILE_DEDUP) {
+ btrfs_inode_dedupe_done(BTRFS_I(src_inode));
+ btrfs_inode_dedupe_done(BTRFS_I(dst_inode));
+ }
+ }
return ret < 0 ? ret : len;
}