return true;
}
+/*
+ * Parse a stripe_alloc_allow_rmw word list ("nodatacow",
+ * "prealloc", "fsync") into its mask. Words are separated by comma,
+ * space or colon; the mount option form must use colon, since mount
+ * itself splits options at commas. An empty value yields an empty
+ * mask. Returns -EINVAL on any unknown word. Shared by the mount
+ * option and the filesystem property.
+ */
+int btrfs_parse_stripe_rmw(const char *value, size_t len, u32 *mask)
+{
+ u32 m = 0;
+ size_t i = 0;
+
+ while (i < len) {
+ size_t j = i;
+
+ while (j < len && value[j] != ',' && value[j] != ' ' &&
+ value[j] != ':')
+ j++;
+ if (j - i == strlen("nodatacow") &&
+ !strncmp(value + i, "nodatacow", j - i))
+ m |= BTRFS_STRIPE_RMW_NODATACOW;
+ else if (j - i == strlen("prealloc") &&
+ !strncmp(value + i, "prealloc", j - i))
+ m |= BTRFS_STRIPE_RMW_PREALLOC;
+ else if (j - i == strlen("fsync") &&
+ !strncmp(value + i, "fsync", j - i))
+ m |= BTRFS_STRIPE_RMW_FSYNC;
+ else if (j != i)
+ return -EINVAL;
+ i = j + 1;
+ }
+ *mask = m;
+ return 0;
+}
+
+void btrfs_show_stripe_rmw(struct seq_file *seq, u32 mask)
+{
+ const char *sep = "";
+
+ if (mask & BTRFS_STRIPE_RMW_NODATACOW) {
+ seq_printf(seq, "%snodatacow", sep);
+ sep = ":";
+ }
+ if (mask & BTRFS_STRIPE_RMW_PREALLOC) {
+ seq_printf(seq, "%sprealloc", sep);
+ sep = ":";
+ }
+ if (mask & BTRFS_STRIPE_RMW_FSYNC)
+ seq_printf(seq, "%sfsync", sep);
+}
+
/*
* Does every committed extent in [start, start + len) belong solely to
* @ino in @root_id -- exactly one reference, a plain data ref, count 1?
* data passes.
*/
bool btrfs_stripe_nocow_writable(struct btrfs_inode *inode, u64 start,
- u64 num_bytes)
+ u64 num_bytes, u32 which)
{
struct btrfs_fs_info *fs_info = inode->root->fs_info;
struct btrfs_block_group *bg;
u64 end;
bool ret = true;
- if (!btrfs_test_opt(fs_info, STRIPE_ALLOC_NOCOW))
+ if (!(btrfs_stripe_allow_rmw(fs_info) & which))
return false;
bg = btrfs_lookup_block_group(fs_info, start);
if (!bg)
u64 ps_start = 0;
u64 ps_len = 0;
+ /*
+ * The fsync overwrite policy waives the log-window guarantee:
+ * logged stripes may be extended and RMWed like before the
+ * close-at-log-commit machinery existed.
+ */
+ if (btrfs_stripe_allow_rmw(fs_info) & BTRFS_STRIPE_RMW_FSYNC)
+ return;
if (list_empty_careful(&fs_info->open_stripe_bgs))
return;
bg = btrfs_lookup_block_group(fs_info, bytenr);
* is either filled before the run closes or never.
*/
bool btrfs_stripe_in_open_run(struct btrfs_fs_info *fs_info, u64 logical)
+{
+ return btrfs_stripe_open_run_class(fs_info, logical, NULL);
+}
+
+/*
+ * Like btrfs_stripe_in_open_run(), also reporting the covering run's
+ * class: the raid56 parking gate treats NOCOW-class runs specially
+ * (sync writes into them never park; see rbio_try_park()).
+ */
+bool btrfs_stripe_open_run_class(struct btrfs_fs_info *fs_info, u64 logical,
+ enum btrfs_stripe_run_class *class)
{
struct btrfs_block_group *bg;
struct btrfs_open_stripe_run *run;
spin_lock_irqsave(&bg->stripe_run_lock, flags);
list_for_each_entry(run, &bg->open_stripe_runs, list) {
if (run->open && logical >= run->start && logical < run->end) {
+ if (class)
+ *class = run->class;
ret = true;
break;
}
void btrfs_close_bg_open_stripes(struct btrfs_block_group *bg);
void btrfs_retire_block_group_stripes(struct btrfs_block_group *bg);
bool btrfs_stripe_in_open_run(struct btrfs_fs_info *fs_info, u64 logical);
+bool btrfs_stripe_open_run_class(struct btrfs_fs_info *fs_info, u64 logical,
+ enum btrfs_stripe_run_class *class);
bool btrfs_stripe_run_range_usable(struct btrfs_block_group *bg,
u64 run_start, u64 *run_len);
bool btrfs_stripe_run_pad_start(struct btrfs_fs_info *fs_info,
u64 stripe_start, u64 stripe_len,
u64 *pad_from);
bool btrfs_stripe_nocow_writable(struct btrfs_inode *inode, u64 start,
- u64 num_bytes);
+ u64 num_bytes, u32 which);
+int btrfs_parse_stripe_rmw(const char *value, size_t len, u32 *mask);
+void btrfs_show_stripe_rmw(struct seq_file *seq, u32 mask);
void btrfs_log_settle_stripes(struct btrfs_inode *inode, u64 file_start,
u64 file_len, u64 bytenr, u64 num_bytes);
int btrfs_stripe_alloc_check_support(struct btrfs_fs_info *fs_info);
if (!btrfs_test_opt(inode->root->fs_info, STRIPE_ALLOC))
return;
+ if (btrfs_stripe_allow_rmw(inode->root->fs_info) &
+ BTRFS_STRIPE_RMW_FSYNC)
+ return;
if (!READ_ONCE(inode->log_carry))
return;
copy = kmalloc(sizeof(*copy), GFP_NOFS);
* one fsync is taken as a signal that more will follow.
*/
if (btrfs_test_opt(fs_info, STRIPE_ALLOC) &&
+ !(btrfs_stripe_allow_rmw(fs_info) &
+ BTRFS_STRIPE_RMW_FSYNC) &&
!test_bit(BTRFS_INODE_LOG_ALLOC, &inode->runtime_flags))
set_bit(BTRFS_INODE_LOG_ALLOC, &inode->runtime_flags);
BTRFS_MOUNT_IGNORESUPERFLAGS = (1ULL << 32),
BTRFS_MOUNT_REF_TRACKER = (1ULL << 33),
BTRFS_MOUNT_STRIPE_ALLOC = (1ULL << 34),
- BTRFS_MOUNT_STRIPE_ALLOC_NOCOW = (1ULL << 35),
+};
+
+/*
+ * Cases in which stripe_alloc may permit the legacy unsafe RMW-in-place
+ * (the write hole returns for the stripes such a write touches, confined
+ * as each case's machinery allows). Set from the
+ * btrfs.stripe_alloc_allow_rmw property and/or the mount option of
+ * the same name; the effective policy is their union.
+ */
+enum {
+ BTRFS_STRIPE_RMW_NODATACOW = (1U << 0),
+ BTRFS_STRIPE_RMW_PREALLOC = (1U << 1),
+ BTRFS_STRIPE_RMW_FSYNC = (1U << 2),
};
/* These mount options require a full read-only fs, no new transaction is allowed. */
u32 data_chunk_allocations;
u32 metadata_ratio;
+ /* BTRFS_STRIPE_RMW_* masks; see btrfs_stripe_allow_rmw(). */
+ u32 stripe_rmw_opt;
+ u32 stripe_rmw_prop;
/* Private scrub information */
struct mutex scrub_lock;
btrfs_fs_closing(fs_info);
}
+static inline u32 btrfs_stripe_allow_rmw(const struct btrfs_fs_info *fs_info)
+{
+ if (!btrfs_test_opt(fs_info, STRIPE_ALLOC))
+ return 0;
+ return READ_ONCE(fs_info->stripe_rmw_opt) |
+ READ_ONCE(fs_info->stripe_rmw_prop);
+}
+
static inline void btrfs_wake_unfinished_drop(struct btrfs_fs_info *fs_info)
{
clear_and_wake_up_bit(BTRFS_FS_UNFINISHED_DROPS, &fs_info->flags);
if (!btrfs_is_data_reloc_root(root) &&
btrfs_stripe_alloc_forces_cow(root->fs_info, io_start) &&
!btrfs_stripe_nocow_writable(inode, io_start,
- args->file_extent.num_bytes))
+ args->file_extent.num_bytes,
+ extent_type == BTRFS_FILE_EXTENT_PREALLOC ?
+ BTRFS_STRIPE_RMW_PREALLOC :
+ BTRFS_STRIPE_RMW_NODATACOW))
goto out;
/*
return NULL;
}
+static int prop_stripe_rmw_validate(const struct btrfs_inode *inode,
+ const char *value, size_t len)
+{
+ u32 mask;
+
+ if (!prop_stripe_alloc_scope_ok(inode))
+ return -EINVAL;
+ if (!value || len == 0)
+ return 0;
+ return btrfs_parse_stripe_rmw(value, len, &mask);
+}
+
+static int prop_stripe_rmw_apply(struct btrfs_inode *inode,
+ const char *value, size_t len)
+{
+ struct btrfs_fs_info *fs_info = inode->root->fs_info;
+ u32 mask = 0;
+
+ /* Ignore stray copies; only the top-level root carries policy. */
+ if (!prop_stripe_alloc_scope_ok(inode))
+ return 0;
+ if (value && len &&
+ btrfs_parse_stripe_rmw(value, len, &mask))
+ return -EINVAL;
+ WRITE_ONCE(fs_info->stripe_rmw_prop, mask);
+ return 0;
+}
+
+static const char *prop_stripe_rmw_extract(const struct btrfs_inode *inode)
+{
+ /* Not inheritable; nothing regenerates the value from inode state. */
+ return NULL;
+}
+
static bool prop_stripe_alloc_ignore(const struct btrfs_inode *inode)
{
return false;
.ignore = prop_stripe_alloc_ignore,
.inheritable = 0
},
+ {
+ .xattr_name = XATTR_BTRFS_PREFIX "stripe_alloc_allow_rmw",
+ .validate = prop_stripe_rmw_validate,
+ .apply = prop_stripe_rmw_apply,
+ .extract = prop_stripe_rmw_extract,
+ .ignore = prop_stripe_alloc_ignore,
+ .inheritable = 0
+ },
};
int btrfs_inode_inherit_props(struct btrfs_trans_handle *trans,
u32 commit_interval;
u32 metadata_ratio;
u32 thread_pool_size;
+ u32 stripe_rmw_opt;
unsigned long long mount_opt;
unsigned long compress_type:4;
int compress_level;
Opt_skip_balance,
Opt_space_cache,
Opt_stripe_alloc,
- Opt_stripe_alloc_nocow,
+ Opt_stripe_alloc_allow_rmw,
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_string("stripe_alloc_allow_rmw",
+ Opt_stripe_alloc_allow_rmw),
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);
+ case Opt_stripe_alloc_allow_rmw:
+ if (btrfs_parse_stripe_rmw(param->string,
+ strlen(param->string),
+ &ctx->stripe_rmw_opt)) {
+ btrfs_err(NULL,
+ "invalid stripe_alloc_allow_rmw value %s",
+ param->string);
+ return -EINVAL;
+ }
break;
case Opt_ratio:
ctx->metadata_ratio = result.uint_32;
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 (info->stripe_rmw_opt) {
+ seq_puts(seq, ",stripe_alloc_allow_rmw=");
+ btrfs_show_stripe_rmw(seq, info->stripe_rmw_opt);
+ }
if (btrfs_test_opt(info, DISCARD_SYNC))
seq_puts(seq, ",discard");
if (btrfs_test_opt(info, DISCARD_ASYNC))
fs_info->max_inline = ctx->max_inline;
fs_info->commit_interval = ctx->commit_interval;
fs_info->metadata_ratio = ctx->metadata_ratio;
+ fs_info->stripe_rmw_opt = ctx->stripe_rmw_opt;
fs_info->thread_pool_size = ctx->thread_pool_size;
fs_info->mount_opt = ctx->mount_opt;
fs_info->compress_type = ctx->compress_type;