Files
scoutfs/kmod/src/ext.c
Zach Brown 4d7191dc48 Print messages on extent ins/rem errors
Signed-off-by: Zach Brown <zab@versity.com>
2021-08-24 09:11:40 -07:00

401 lines
11 KiB
C

/*
* Copyright (C) 2020 Versity Software, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License v2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/fs.h>
#include "msg.h"
#include "ext.h"
#include "counters.h"
#include "scoutfs_trace.h"
/*
* Extents are used to track free block regions and to map logical file
* regions to device blocks. Extents can be split and merged as
* they're modified. These helpers implement all the fiddly extent
* manipulations. Callers provide callbacks which implement the actual
* storage of extents in either the item cache or btree items.
*/
static void ext_zero(struct scoutfs_extent *ext)
{
memset(ext, 0, sizeof(struct scoutfs_extent));
}
static bool ext_overlap(struct scoutfs_extent *ext, u64 start, u64 len)
{
u64 e_end = ext->start + ext->len - 1;
u64 end = start + len - 1;
return !(e_end < start || ext->start > end);
}
bool scoutfs_ext_inside(u64 start, u64 len, struct scoutfs_extent *out)
{
u64 in_end = start + len - 1;
u64 out_end = out->start + out->len - 1;
return out->start <= start && out_end >= in_end;
}
/* we only translate mappings when they exist */
static inline u64 ext_map_add(u64 map, u64 diff)
{
return map ? map + diff : 0;
}
/*
* Extents can merge if they're logically contiguous, both don't have
* mappings or have mappings which are also contiguous, and have
* matching flags.
*/
bool scoutfs_ext_can_merge(struct scoutfs_extent *left,
struct scoutfs_extent *right)
{
return (left->start + left->len == right->start) &&
((!left->map && !right->map) ||
(left->map + left->len == right->map)) &&
(left->flags == right->flags);
}
/*
* Split an existing extent in to left and right extents by removing
* an interior range. The split extents are all zeros if the range
* extends to their end of the extent.
*/
static void ext_split(struct scoutfs_extent *ext, u64 start, u64 len,
struct scoutfs_extent *left,
struct scoutfs_extent *right)
{
if (ext->start < start) {
left->start = ext->start;
left->len = start - ext->start;
left->map = ext->map;
left->flags = ext->flags;
} else {
ext_zero(left);
}
if (ext->start + ext->len > start + len) {
right->start = start + len;
right->len = ext->start + ext->len - right->start;
right->map = ext_map_add(ext->map, right->start - ext->start);
right->flags = ext->flags;
} else {
ext_zero(right);
}
}
#define op_call(sb, ops, arg, which, args...) \
({ \
int _ret; \
_ret = ops->which(sb, arg, ##args); \
scoutfs_inc_counter(sb, ext_op_##which); \
trace_scoutfs_ext_op_##which(sb, ##args, _ret); \
_ret; \
})
struct extent_changes {
struct scoutfs_extent exts[4];
bool ins[4];
u8 nr;
};
static void add_change(struct extent_changes *chg,
struct scoutfs_extent *ext, bool ins)
{
BUILD_BUG_ON(ARRAY_SIZE(chg->ins) != ARRAY_SIZE(chg->exts));
if (ext->len) {
BUG_ON(chg->nr == ARRAY_SIZE(chg->exts));
chg->exts[chg->nr] = *ext;
chg->ins[chg->nr] = !!ins;
chg->nr++;
}
}
static int apply_changes(struct super_block *sb, struct scoutfs_ext_ops *ops,
void *arg, struct extent_changes *chg)
{
int ret = 0;
int err;
int i;
for (i = 0; i < chg->nr; i++) {
if (chg->ins[i])
ret = op_call(sb, ops, arg, insert, chg->exts[i].start,
chg->exts[i].len, chg->exts[i].map,
chg->exts[i].flags);
else
ret = op_call(sb, ops, arg, remove, chg->exts[i].start,
chg->exts[i].len, chg->exts[i].map,
chg->exts[i].flags);
if (ret < 0)
break;
}
while (ret < 0 && --i >= 0) {
if (chg->ins[i])
err = op_call(sb, ops, arg, remove, chg->exts[i].start,
chg->exts[i].len, chg->exts[i].map,
chg->exts[i].flags);
else
err = op_call(sb, ops, arg, insert, chg->exts[i].start,
chg->exts[i].len, chg->exts[i].map,
chg->exts[i].flags);
BUG_ON(err); /* inconsistent */
}
return ret;
}
int scoutfs_ext_next(struct super_block *sb, struct scoutfs_ext_ops *ops,
void *arg, u64 start, u64 len, struct scoutfs_extent *ext)
{
int ret;
ret = op_call(sb, ops, arg, next, start, len, ext);
trace_scoutfs_ext_next(sb, start, len, ext, ret);
return ret;
}
/*
* Insert the given extent. EINVAL is returned if there's already an existing
* overlapping extent. This can merge with its neighbours.
*/
int scoutfs_ext_insert(struct super_block *sb, struct scoutfs_ext_ops *ops,
void *arg, u64 start, u64 len, u64 map, u8 flags)
{
struct extent_changes chg = { .nr = 0 };
struct scoutfs_extent found;
struct scoutfs_extent ins;
int ret;
ins.start = start;
ins.len = len;
ins.map = map;
ins.flags = flags;
/* find right neighbour and check for overlap */
ret = op_call(sb, ops, arg, next, start, 1, &found);
if (ret < 0 && ret != -ENOENT)
goto out;
/* inserting extent must not overlap */
if (found.len && ext_overlap(&ins, found.start, found.len)) {
if (ops->insert_overlap_warn)
scoutfs_err(sb, "inserting extent %llu.%llu overlaps existing %llu.%llu",
start, len, found.start, found.len);
ret = -EINVAL;
goto out;
}
/* merge with right if we can */
if (found.len && scoutfs_ext_can_merge(&ins, &found)) {
ins.len += found.len;
add_change(&chg, &found, false);
}
/* see if we can merge with a left neighbour */
if (start > 0) {
ret = op_call(sb, ops, arg, next, start - 1, 1, &found);
if (ret < 0 && ret != -ENOENT)
goto out;
if (ret == 0 && scoutfs_ext_can_merge(&found, &ins)) {
ins.start = found.start;
ins.map = found.map;
ins.len += found.len;
add_change(&chg, &found, false);
}
}
add_change(&chg, &ins, true);
ret = apply_changes(sb, ops, arg, &chg);
out:
trace_scoutfs_ext_insert(sb, start, len, map, flags, ret);
return ret;
}
/*
* Remove the given extent. The extent to remove must be found entirely
* in an existing extent. If the existing extent is larger then we leave
* behind the remaining extent. The existing extent can be split.
*/
int scoutfs_ext_remove(struct super_block *sb, struct scoutfs_ext_ops *ops,
void *arg, u64 start, u64 len)
{
struct extent_changes chg = { .nr = 0 };
struct scoutfs_extent found;
struct scoutfs_extent left;
struct scoutfs_extent right;
int ret;
ret = op_call(sb, ops, arg, next, start, 1, &found);
if (ret < 0)
goto out;
/* removed extent must be entirely within found */
if (!scoutfs_ext_inside(start, len, &found)) {
scoutfs_err(sb, "error removing extent %llu.%llu, isn't inside existing %llu.%llu",
start, len, found.start, found.len);
ret = -EINVAL;
goto out;
}
ext_split(&found, start, len, &left, &right);
add_change(&chg, &found, false);
add_change(&chg, &left, true);
add_change(&chg, &right, true);
ret = apply_changes(sb, ops, arg, &chg);
out:
trace_scoutfs_ext_remove(sb, start, len, 0, 0, ret);
return ret;
}
/*
* Find and remove the next extent, removing only a portion if the
* extent is larger than the count. Returns ENOENT if it didn't
* find any extents.
*
* This does not search for merge candidates so it's safe to call with
* extents indexed by length.
*/
int scoutfs_ext_alloc(struct super_block *sb, struct scoutfs_ext_ops *ops,
void *arg, u64 start, u64 len, u64 count,
struct scoutfs_extent *ext)
{
struct extent_changes chg = { .nr = 0 };
struct scoutfs_extent found;
struct scoutfs_extent ins;
int ret;
ret = op_call(sb, ops, arg, next, start, len, &found);
if (ret < 0)
goto out;
add_change(&chg, &found, false);
if (found.len > count) {
ins.start = found.start + count;
ins.len = found.len - count;
ins.map = ext_map_add(found.map, count);
ins.flags = found.flags;
add_change(&chg, &ins, true);
}
ret = apply_changes(sb, ops, arg, &chg);
out:
if (ret == 0) {
ext->start = found.start;
ext->len = min(found.len, count);
ext->map = found.map;
ext->flags = found.flags;
} else {
ext_zero(ext);
}
trace_scoutfs_ext_alloc(sb, start, len, count, ext, ret);
return ret;
}
/*
* Set the map and flags for an extent region, with the magical property
* that extents with map and flags set to 0 are removed.
*
* If we're modifying an existing extent then the modification must be
* fully inside the existing extent. The modification can leave edges
* of the extent which need to be inserted. If the modification extends
* to the end of the existing extent then we need to check for adjacent
* neighbouring extents which might now be able to be merged.
*
* Inserting a new extent is like the case of modifying the entire
* existing extent. We need to check neighbours of the inserted extent
* to see if they can be merged.
*/
int scoutfs_ext_set(struct super_block *sb, struct scoutfs_ext_ops *ops,
void *arg, u64 start, u64 len, u64 map, u8 flags)
{
struct extent_changes chg = { .nr = 0 };
struct scoutfs_extent found;
struct scoutfs_extent left;
struct scoutfs_extent right;
struct scoutfs_extent set;
int ret;
set.start = start;
set.len = len;
set.map = map;
set.flags = flags;
/* find extent to remove */
ret = op_call(sb, ops, arg, next, start, 1, &found);
if (ret < 0 && ret != -ENOENT)
goto out;
if (ret == 0 && ext_overlap(&found, start, len)) {
/* set extent must be entirely within found */
if (!scoutfs_ext_inside(start, len, &found)) {
ret = -EINVAL;
goto out;
}
add_change(&chg, &found, false);
ext_split(&found, start, len, &left, &right);
} else {
ext_zero(&found);
ext_zero(&left);
ext_zero(&right);
}
if (left.len) {
/* inserting split left, won't merge */
add_change(&chg, &left, true);
} else if (start > 0) {
ret = op_call(sb, ops, arg, next, start - 1, 1, &left);
if (ret < 0 && ret != -ENOENT)
goto out;
else if (ret == 0 && scoutfs_ext_can_merge(&left, &set)) {
/* remove found left, merging */
set.start = left.start;
set.map = left.map;
set.len += left.len;
add_change(&chg, &left, false);
}
}
if (right.len) {
/* inserting split right, won't merge */
add_change(&chg, &right, true);
} else {
ret = op_call(sb, ops, arg, next, start + len, 1, &right);
if (ret < 0 && ret != -ENOENT)
goto out;
else if (ret == 0 && scoutfs_ext_can_merge(&set, &right)) {
/* remove found right, merging */
set.len += right.len;
add_change(&chg, &right, false);
}
}
if (set.flags || set.map)
add_change(&chg, &set, true);
ret = apply_changes(sb, ops, arg, &chg);
out:
trace_scoutfs_ext_set(sb, start, len, map, flags, ret);
return ret;
}