return true;
}
+/*
+ * Does every committed extent in [start, start + len) belong solely to
+ * @ino in @root_id -- exactly one reference, a plain data ref, count 1?
+ * Any metadata block, shared reference, or foreign owner fails. Caller
+ * limits the range to full stripes; uncommitted allocations need not be
+ * visible because nothing can allocate into a partially used stripe (the
+ * fully-free claim rule), so a foreign extent can only appear here after
+ * the whole stripe frees and commits -- which the extent tree shows.
+ */
+static bool stripe_extents_owned_by(struct btrfs_fs_info *fs_info,
+ u64 root_id, u64 ino, u64 start, u64 len)
+{
+ struct btrfs_root *extent_root = btrfs_extent_root(fs_info, start);
+ struct btrfs_path *path;
+ struct btrfs_key key;
+ bool ret = true;
+ int err;
+
+ if (!extent_root)
+ return false;
+ path = btrfs_alloc_path();
+ if (!path)
+ return false;
+
+ key.objectid = start;
+ key.type = BTRFS_EXTENT_ITEM_KEY;
+ key.offset = 0;
+ err = btrfs_search_slot(NULL, extent_root, &key, path, 0, 0);
+ if (err < 0) {
+ ret = false;
+ goto out;
+ }
+ /* An extent starting before @start may still overlap it. */
+ err = btrfs_previous_extent_item(extent_root, path, 0);
+ if (err < 0) {
+ ret = false;
+ goto out;
+ }
+ if (err > 0) {
+ btrfs_release_path(path);
+ key.objectid = start;
+ key.type = BTRFS_EXTENT_ITEM_KEY;
+ key.offset = 0;
+ err = btrfs_search_slot(NULL, extent_root, &key, path, 0, 0);
+ if (err < 0) {
+ ret = false;
+ goto out;
+ }
+ }
+
+ while (true) {
+ struct extent_buffer *leaf = path->nodes[0];
+ int slot = path->slots[0];
+ struct btrfs_extent_item *ei;
+ struct btrfs_extent_inline_ref *iref;
+ struct btrfs_extent_data_ref *dref;
+ int type;
+
+ if (slot >= btrfs_header_nritems(leaf)) {
+ err = btrfs_next_leaf(extent_root, path);
+ if (err < 0) {
+ ret = false;
+ goto out;
+ }
+ if (err > 0)
+ break;
+ continue;
+ }
+ btrfs_item_key_to_cpu(leaf, &key, slot);
+ if (key.objectid >= start + len)
+ break;
+ if (key.type == BTRFS_METADATA_ITEM_KEY) {
+ /*
+ * key.offset is the level, not a length; a tree
+ * block spans nodesize bytes. One from before the
+ * range (e.g. where the pre-range positioning landed
+ * on a metadata block group's last item) is harmless.
+ */
+ if (key.objectid + fs_info->nodesize > start) {
+ ret = false;
+ goto out;
+ }
+ path->slots[0]++;
+ continue;
+ }
+ if (key.type != BTRFS_EXTENT_ITEM_KEY ||
+ key.objectid + key.offset <= start) {
+ path->slots[0]++;
+ continue;
+ }
+
+ ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
+ if (btrfs_extent_refs(leaf, ei) != 1 ||
+ !(btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_DATA)) {
+ ret = false;
+ goto out;
+ }
+ iref = (struct btrfs_extent_inline_ref *)(ei + 1);
+ type = btrfs_get_extent_inline_ref_type(leaf, iref,
+ BTRFS_REF_TYPE_DATA);
+ if (type != BTRFS_EXTENT_DATA_REF_KEY) {
+ ret = false;
+ goto out;
+ }
+ dref = (struct btrfs_extent_data_ref *)(&iref->offset);
+ if (btrfs_extent_data_ref_root(leaf, dref) != root_id ||
+ btrfs_extent_data_ref_objectid(leaf, dref) != ino ||
+ btrfs_extent_data_ref_count(leaf, dref) != 1) {
+ ret = false;
+ goto out;
+ }
+ path->slots[0]++;
+ }
+out:
+ btrfs_free_path(path);
+ return ret;
+}
+
+/*
+ * May [start, start + num_bytes) be written in place under
+ * stripe-exclusive allocation? Only when the stripe_alloc_nocow policy
+ * is enabled and every full stripe the range touches is isolated to
+ * @inode: an in-place write RMWs the stripe's parity, so a degraded
+ * crash can tear every extent sharing the stripe -- isolation confines
+ * that blast radius to the writing file, which opted out of COW
+ * protection. Fast path: the stripe belongs to one of the inode's own
+ * NOCOW-class runs (such stripes have only ever held its extents).
+ * Slow path: the committed extent tree proves sole ownership. Extents
+ * that fail simply stay force-COWed -- and their rewrite is steered
+ * into the inode's private NOCOW run, so the next overwrite of the same
+ * data passes.
+ */
+bool btrfs_stripe_nocow_writable(struct btrfs_inode *inode, u64 start,
+ u64 num_bytes)
+{
+ struct btrfs_fs_info *fs_info = inode->root->fs_info;
+ struct btrfs_block_group *bg;
+ const u64 root_id = btrfs_root_id(inode->root);
+ const u64 ino = btrfs_ino(inode);
+ u64 fsl;
+ u64 cur;
+ u64 end;
+ bool ret = true;
+
+ if (!btrfs_test_opt(fs_info, STRIPE_ALLOC_NOCOW))
+ return false;
+ bg = btrfs_lookup_block_group(fs_info, start);
+ if (!bg)
+ return false;
+ if (!btrfs_is_stripe_alloc_bg(bg)) {
+ /* Not a raid56 data group: nothing forces COW here anyway. */
+ btrfs_put_block_group(bg);
+ return true;
+ }
+
+ fsl = bg->full_stripe_len;
+ cur = bg->start + round_down(start - bg->start, fsl);
+ end = start + num_bytes;
+ while (cur < end) {
+ struct btrfs_open_stripe_run *run;
+ unsigned long flags;
+ int verdict = 0; /* 0 = unknown, 1 = ok, -1 = foreign */
+
+ spin_lock_irqsave(&bg->stripe_run_lock, flags);
+ list_for_each_entry(run, &bg->open_stripe_runs, list) {
+ if (cur < run->start || cur >= run->end)
+ continue;
+ if (run->class == BTRFS_STRIPE_RUN_NOCOW &&
+ run->owner == ino)
+ verdict = 1;
+ else
+ verdict = -1;
+ break;
+ }
+ spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
+
+ if (verdict == 0 &&
+ !stripe_extents_owned_by(fs_info, root_id, ino, cur, fsl))
+ verdict = -1;
+ if (verdict < 0) {
+ ret = false;
+ break;
+ }
+ cur += fsl;
+ }
+ btrfs_put_block_group(bg);
+ return ret;
+}
+
/*
* For the raid56 pad-to-full optimization: a sub-stripe write into a
* stripe covered by a live stripe run may zero-fill (instead of read) any
if (bytenr < run->start || bytenr >= run->offset)
continue;
+ /*
+ * A nodatacow file's logged extents get no fsync survival
+ * guarantee -- the file's own later in-place writes can
+ * always tear them -- so closing its run would trap the
+ * tail for nothing.
+ */
+ if (run->class == BTRFS_STRIPE_RUN_NOCOW)
+ break;
flush_start = run->start;
flush_len = run->end - run->start;
/*
if (!bg)
return;
if (!btrfs_is_stripe_alloc_bg(bg) ||
- test_bit(BLOCK_GROUP_FLAG_STRIPE_RELOC_USED, &bg->runtime_flags))
+ test_bit(BLOCK_GROUP_FLAG_STRIPE_RELOC_USED, &bg->runtime_flags) ||
+ test_bit(BLOCK_GROUP_FLAG_STRIPE_NOCOW_USED, &bg->runtime_flags))
goto out;
fsl = bg->full_stripe_len;
spin_lock_irqsave(&bg->stripe_run_lock, flags);
Opt_skip_balance,
Opt_space_cache,
Opt_stripe_alloc,
+ Opt_stripe_alloc_nocow,
Opt_space_cache_version,
Opt_ssd,
Opt_ssd_spread,
fsparam_flag_no("space_cache", Opt_space_cache),
fsparam_enum("space_cache", Opt_space_cache_version, btrfs_parameter_space_cache),
fsparam_flag_no("stripe_alloc", Opt_stripe_alloc),
+ fsparam_flag_no("stripe_alloc_nocow", Opt_stripe_alloc_nocow),
fsparam_flag_no("ssd", Opt_ssd),
fsparam_flag_no("ssd_spread", Opt_ssd_spread),
fsparam_string("subvol", Opt_subvol),
else
btrfs_set_opt(ctx->mount_opt, STRIPE_ALLOC);
break;
+ case Opt_stripe_alloc_nocow:
+ if (result.negated)
+ btrfs_clear_opt(ctx->mount_opt, STRIPE_ALLOC_NOCOW);
+ else
+ btrfs_set_opt(ctx->mount_opt, STRIPE_ALLOC_NOCOW);
+ break;
case Opt_ratio:
ctx->metadata_ratio = result.uint_32;
break;
ret = false;
}
+ if (btrfs_raw_test_opt(*mount_opt, STRIPE_ALLOC_NOCOW) &&
+ !btrfs_raw_test_opt(*mount_opt, STRIPE_ALLOC)) {
+ btrfs_err(info, "stripe_alloc_nocow requires stripe_alloc");
+ ret = false;
+ }
if (btrfs_raw_test_opt(*mount_opt, STRIPE_ALLOC)) {
/*
* Only v1 is a problem: its cache inode is nodatacow and
seq_puts(seq, ",flushoncommit");
if (btrfs_test_opt(info, STRIPE_ALLOC))
seq_puts(seq, ",stripe_alloc");
+ if (btrfs_test_opt(info, STRIPE_ALLOC_NOCOW))
+ seq_puts(seq, ",stripe_alloc_nocow");
if (btrfs_test_opt(info, DISCARD_SYNC))
seq_puts(seq, ",discard");
if (btrfs_test_opt(info, DISCARD_ASYNC))