btrfs: zoned: split ordered extent when bio is sent
[sfrench/cifs-2.6.git] / fs / btrfs / inode.c
index d7a9c770dc3b3352287c599f22a089de60ad241f..750482a06d6760828cbab7374451d17685251348 100644 (file)
@@ -2215,6 +2215,92 @@ static blk_status_t btrfs_submit_bio_start(struct inode *inode, struct bio *bio,
        return btrfs_csum_one_bio(BTRFS_I(inode), bio, 0, 0);
 }
 
+static blk_status_t extract_ordered_extent(struct btrfs_inode *inode,
+                                          struct bio *bio, loff_t file_offset)
+{
+       struct btrfs_ordered_extent *ordered;
+       struct extent_map *em = NULL, *em_new = NULL;
+       struct extent_map_tree *em_tree = &inode->extent_tree;
+       u64 start = (u64)bio->bi_iter.bi_sector << SECTOR_SHIFT;
+       u64 len = bio->bi_iter.bi_size;
+       u64 end = start + len;
+       u64 ordered_end;
+       u64 pre, post;
+       int ret = 0;
+
+       ordered = btrfs_lookup_ordered_extent(inode, file_offset);
+       if (WARN_ON_ONCE(!ordered))
+               return BLK_STS_IOERR;
+
+       /* No need to split */
+       if (ordered->disk_num_bytes == len)
+               goto out;
+
+       /* We cannot split once end_bio'd ordered extent */
+       if (WARN_ON_ONCE(ordered->bytes_left != ordered->disk_num_bytes)) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /* We cannot split a compressed ordered extent */
+       if (WARN_ON_ONCE(ordered->disk_num_bytes != ordered->num_bytes)) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ordered_end = ordered->disk_bytenr + ordered->disk_num_bytes;
+       /* bio must be in one ordered extent */
+       if (WARN_ON_ONCE(start < ordered->disk_bytenr || end > ordered_end)) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       /* Checksum list should be empty */
+       if (WARN_ON_ONCE(!list_empty(&ordered->list))) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       pre = start - ordered->disk_bytenr;
+       post = ordered_end - end;
+
+       ret = btrfs_split_ordered_extent(ordered, pre, post);
+       if (ret)
+               goto out;
+
+       read_lock(&em_tree->lock);
+       em = lookup_extent_mapping(em_tree, ordered->file_offset, len);
+       if (!em) {
+               read_unlock(&em_tree->lock);
+               ret = -EIO;
+               goto out;
+       }
+       read_unlock(&em_tree->lock);
+
+       ASSERT(!test_bit(EXTENT_FLAG_COMPRESSED, &em->flags));
+       /*
+        * We cannot reuse em_new here but have to create a new one, as
+        * unpin_extent_cache() expects the start of the extent map to be the
+        * logical offset of the file, which does not hold true anymore after
+        * splitting.
+        */
+       em_new = create_io_em(inode, em->start + pre, len,
+                             em->start + pre, em->block_start + pre, len,
+                             len, len, BTRFS_COMPRESS_NONE,
+                             BTRFS_ORDERED_REGULAR);
+       if (IS_ERR(em_new)) {
+               ret = PTR_ERR(em_new);
+               goto out;
+       }
+       free_extent_map(em_new);
+
+out:
+       free_extent_map(em);
+       btrfs_put_ordered_extent(ordered);
+
+       return errno_to_blk_status(ret);
+}
+
 /*
  * extent_io.c submission hook. This does the right thing for csum calculation
  * on write, or reading the csums from the tree before a read.
@@ -2250,6 +2336,15 @@ blk_status_t btrfs_submit_data_bio(struct inode *inode, struct bio *bio,
        if (btrfs_is_free_space_inode(BTRFS_I(inode)))
                metadata = BTRFS_WQ_ENDIO_FREE_SPACE;
 
+       if (bio_op(bio) == REQ_OP_ZONE_APPEND) {
+               struct page *page = bio_first_bvec_all(bio)->bv_page;
+               loff_t file_offset = page_offset(page);
+
+               ret = extract_ordered_extent(BTRFS_I(inode), bio, file_offset);
+               if (ret)
+                       goto out;
+       }
+
        if (btrfs_op(bio) != BTRFS_MAP_WRITE) {
                ret = btrfs_bio_wq_end_io(fs_info, bio, metadata);
                if (ret)