mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 19:01:57 -04:00
Replace physical reach tracking with the natural file and parity EOF
Some checks are pending
Build / build-mac (push) Waiting to run
Build / build-linux (push) Waiting to run
Build / build-arm32 (push) Waiting to run
Build / build-win (push) Waiting to run
Build / build-freebsd (push) Waiting to run
Build / build-musl (push) Waiting to run
CodeQL Advanced / Analyze (c-cpp) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
Some checks are pending
Build / build-mac (push) Waiting to run
Build / build-linux (push) Waiting to run
Build / build-arm32 (push) Waiting to run
Build / build-win (push) Waiting to run
Build / build-freebsd (push) Waiting to run
Build / build-musl (push) Waiting to run
CodeQL Advanced / Analyze (c-cpp) (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Waiting to run
This commit is contained in:
parent
873a5561e4
commit
62ae6c3552
7 changed files with 195 additions and 278 deletions
|
|
@ -2442,9 +2442,8 @@ static int state_check_process(struct snapraid_state* state, int fix, struct sna
|
|||
* If you check/fix after a partial sync, you do not want to fix parity
|
||||
* for blocks that are going to have it computed in the sync completion.
|
||||
*
|
||||
* For unused parity there is no need to write it, because when fixing
|
||||
* we already have allocated space for it on parity file creation,
|
||||
* and its content doesn't matter.
|
||||
* For unused parity there is no need to write or allocate it because no live
|
||||
* file depends on that position and its physical contents do not matter.
|
||||
*/
|
||||
if (used_parity && valid_parity) {
|
||||
/* update the parity */
|
||||
|
|
@ -2456,7 +2455,7 @@ static int state_check_process(struct snapraid_state* state, int fix, struct sna
|
|||
/* and the parity is not excluded */
|
||||
&& !state->parity[l].is_excluded_by_filter
|
||||
) {
|
||||
ret = parity_write(parity[l], i, buffer[diskmax + l], state->block_size);
|
||||
ret = parity_write(parity[l], i, buffer[diskmax + l], state->block_size, state->opt.skip_fallocate);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("%s:%" PRIu64 ":%s: Write error. %s.\n", es(errno), i, lev_config_name(l), strerror(errno));
|
||||
|
|
@ -3148,17 +3147,21 @@ int state_check(struct snapraid_state* state, int fix, block_off_t blockstart, b
|
|||
}
|
||||
|
||||
/*
|
||||
* Fix must preserve the split layout stored in the content file.
|
||||
* Fix restores the physical state described by the content file.
|
||||
*
|
||||
* Unlike sync, fix does not persist parity split boundary changes. Allowing a
|
||||
* partially restored elastic split to become shorter would move the remaining
|
||||
* logical parity into following splits, and repaired parity could then be
|
||||
* written using a transient mapping that is lost on the next invocation.
|
||||
* For an existing parity level, preserve every persisted split boundary
|
||||
* while limiting the physical layout to the parity extent actually required
|
||||
* by the content. Preceding splits needed to reach an existing later split
|
||||
* are restored immediately; the remaining tail grows lazily as repaired
|
||||
* parity blocks are written.
|
||||
*
|
||||
* An exception is made for new parity levels not yet present in the content
|
||||
* file (PARITY_SIZE_INVALID), which are being created from scratch.
|
||||
* A new parity level has no persisted layout yet (PARITY_SIZE_INVALID), so
|
||||
* its split layout must instead be allocated from scratch.
|
||||
*/
|
||||
ret = parity_chsize(parity_ptr[l], &state->parity[l], 0, size, state->block_size, state->opt.skip_fallocate, state->opt.skip_space_holder, state->parity[l].split_map[0].size == PARITY_SIZE_INVALID);
|
||||
if (state->parity[l].split_map[0].size == PARITY_SIZE_INVALID)
|
||||
ret = parity_chsize(parity_ptr[l], &state->parity[l], 0, size, state->block_size, state->opt.skip_fallocate, state->opt.skip_space_holder);
|
||||
else
|
||||
ret = parity_restore(parity_ptr[l], size, state->block_size, state->opt.skip_fallocate);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("parity_%s:%u:%s: Create error. %s.\n", es(errno), 0, lev_config_name(l), strerror(errno));
|
||||
|
|
@ -3211,20 +3214,6 @@ int state_check(struct snapraid_state* state, int fix, block_off_t blockstart, b
|
|||
/* try to close only if opened */
|
||||
for (l = 0; l < state->level; ++l) {
|
||||
if (parity_ptr[l]) {
|
||||
/* if fixing and not excluded, truncate parity to physical_reach_size */
|
||||
if (fix && !state->parity[l].is_excluded_by_filter) {
|
||||
ret = parity_truncate(parity_ptr[l]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("parity_%s:%" PRIu64 ":%s: Truncate error. %s.\n", es(errno), blockmax, lev_config_name(l), strerror(errno));
|
||||
log_fatal_errno(errno, lev_config_name(l));
|
||||
|
||||
++process_error;
|
||||
/* continue, as we are already exiting */
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
ret = parity_close(parity_ptr[l]);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
|
|
|
|||
|
|
@ -129,9 +129,6 @@ int handle_create(struct snapraid_handle* handle, struct snapraid_file* file, in
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* get the size of the existing data */
|
||||
handle->physical_reach_size = handle->st.st_size;
|
||||
|
||||
ret = advise_open(&handle->advise, handle->f);
|
||||
if (ret != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
|
|
@ -259,8 +256,8 @@ int handle_truncate(struct snapraid_handle* handle, struct snapraid_file* file)
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* adjust the size to the truncated size */
|
||||
handle->physical_reach_size = file->size;
|
||||
/* keep the cached EOF current; the other stat fields remain the open snapshot */
|
||||
handle->st.st_size = file->size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -318,9 +315,6 @@ int handle_open(struct snapraid_handle* handle, struct snapraid_file* file, int
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* get the size of the existing data */
|
||||
handle->physical_reach_size = handle->st.st_size;
|
||||
|
||||
ret = advise_open(&handle->advise, handle->f);
|
||||
if (ret != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
|
|
@ -349,7 +343,6 @@ int handle_close(struct snapraid_handle* handle)
|
|||
/* invalidate for error */
|
||||
handle->file = 0;
|
||||
handle->f = -1;
|
||||
handle->physical_reach_size = 0;
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
|
@ -358,7 +351,6 @@ int handle_close(struct snapraid_handle* handle)
|
|||
/* reset the descriptor */
|
||||
handle->file = 0;
|
||||
handle->f = -1;
|
||||
handle->physical_reach_size = 0;
|
||||
handle->readonly_errno = 0;
|
||||
|
||||
return 0;
|
||||
|
|
@ -378,9 +370,9 @@ ssize_t handle_read(struct snapraid_handle* handle, block_off_t file_pos, unsign
|
|||
out_missing = log_error;
|
||||
|
||||
/* check if we are going to read only not initialized data */
|
||||
if (offset >= handle->physical_reach_size) {
|
||||
if (offset >= handle->st.st_size) {
|
||||
/* if the file is missing, it's at 0 size, or it's rebuilt while reading */
|
||||
if (offset == handle->physical_reach_size || handle->physical_reach_size == 0) {
|
||||
if (offset == handle->st.st_size || handle->st.st_size == 0) {
|
||||
errno = ENOENT;
|
||||
if (offset == 0) {
|
||||
out_missing(errno, "Missing file '%s'.\n", handle->path);
|
||||
|
|
@ -482,10 +474,9 @@ int handle_write(struct snapraid_handle* handle, block_off_t file_pos, unsigned
|
|||
count += write_ret;
|
||||
} while (count < write_size);
|
||||
|
||||
/* adjust the size of the written/retained data */
|
||||
if (handle->physical_reach_size < offset + write_size) {
|
||||
handle->physical_reach_size = offset + write_size;
|
||||
}
|
||||
/* keep the cached EOF current; the other stat fields remain the open snapshot */
|
||||
if (handle->st.st_size < offset + write_size)
|
||||
handle->st.st_size = offset + write_size;
|
||||
|
||||
ret = advise_write(&handle->advise, handle->f, offset, block_size);
|
||||
if (ret != 0) {
|
||||
|
|
@ -551,7 +542,6 @@ struct snapraid_handle* handle_mapping(struct snapraid_state* state, unsigned* h
|
|||
handle[j].disk = 0;
|
||||
handle[j].file = 0;
|
||||
handle[j].f = -1;
|
||||
handle[j].physical_reach_size = 0;
|
||||
handle[j].readonly_errno = 0;
|
||||
handle[j].bw = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,8 @@ struct snapraid_handle {
|
|||
struct snapraid_disk* disk; /**< Disk of the file. */
|
||||
struct snapraid_file* file; /**< File opened. When the file is closed, it's set to 0. */
|
||||
int f; /**< Handle of the file. */
|
||||
struct stat st; /**< Stat info of the opened file. */
|
||||
struct stat st; /**< Stat info of the opened file. st_size tracks the current EOF after recovery writes. */
|
||||
struct advise_struct advise; /**< Advise information. */
|
||||
|
||||
/**
|
||||
* High-water mark size of the written/retained data.
|
||||
*
|
||||
* High-water size does not indicate that data below this offset is valid;
|
||||
* it only marks the physical boundary beyond which data is known to be invalid
|
||||
* and can be discarded upon truncation. Data within the physical_reach_size may
|
||||
* or may not be valid. Final verification is determined independently by
|
||||
* block states and hashes.
|
||||
*/
|
||||
data_off_t physical_reach_size;
|
||||
int created; /**< If the file was created, otherwise it was already existing. */
|
||||
int readonly_errno; /**< Non-zero if opened read-only as fallback. */
|
||||
struct snapraid_bw* bw; /**< Context for bandwidth limiting. */
|
||||
|
|
|
|||
291
cmdline/parity.c
291
cmdline/parity.c
|
|
@ -127,29 +127,24 @@ void parity_size(struct snapraid_parity_handle* handle, data_off_t* out_size)
|
|||
*out_size = size;
|
||||
}
|
||||
|
||||
void parity_physical_reach_size(struct snapraid_parity_handle* handle, data_off_t* out_size)
|
||||
void parity_physical_size(struct snapraid_parity_handle* handle, data_off_t* out_size)
|
||||
{
|
||||
unsigned s;
|
||||
data_off_t size;
|
||||
|
||||
/*
|
||||
* Compute the physical reach of the logical parity layout across splits.
|
||||
* Compute the physical size of the logical parity layout across splits.
|
||||
*
|
||||
* physical_reach_size represents physical file presence, not parity validity
|
||||
* or contiguity. Invalid, stale, unwritten, or sparse stripes may exist below
|
||||
* this boundary, for example when fix cannot recover a stripe but continues
|
||||
* with later ones. Such holes are allowed and need not be represented in the
|
||||
* content state.
|
||||
*
|
||||
* For split parity, splits concatenate linearly. If an earlier split is
|
||||
* physically shorter than its logical size, logical offsets beyond that
|
||||
* missing region cannot be addressed, so subsequent splits are not counted.
|
||||
* Physical presence is determined directly from EOF and is independent of
|
||||
* parity validity. For split parity, if an earlier split is physically shorter
|
||||
* than its logical size, subsequent splits cannot extend the logical extent
|
||||
* past that missing region.
|
||||
*/
|
||||
size = 0;
|
||||
|
||||
for (s = 0; s < handle->split_mac; ++s) {
|
||||
struct snapraid_split_handle* split = &handle->split_map[s];
|
||||
data_off_t run = split->physical_reach_size;
|
||||
data_off_t run = split->st.st_size;
|
||||
|
||||
/* don't count physical data outside the logical split */
|
||||
if (run > split->size)
|
||||
|
|
@ -212,27 +207,6 @@ int parity_create(struct snapraid_parity_handle* handle, const struct snapraid_p
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the physical read/truncation boundary from the current EOF.
|
||||
*
|
||||
* physical_reach_size does NOT imply that parity below this offset is valid.
|
||||
* Parity may be invalid, stale, unwritten, or sparse in any region below
|
||||
* this boundary. It only records the physical EOF boundary for I/O and
|
||||
* truncation. The physical contents below this boundary are not necessarily
|
||||
* described by the content state. Recovery correctness is validated
|
||||
* independently using data hashes and recomputation.
|
||||
*
|
||||
* While a handle is open, physical_reach_size can intentionally differ from EOF.
|
||||
* In particular, preallocation may extend EOF without extending physical_reach_size.
|
||||
* Writable fix paths normally truncate the file back to physical_reach_size before
|
||||
* closing.
|
||||
*
|
||||
* Across close/reopen there is no separate persistent physical_reach value, so EOF
|
||||
* is used to reconstruct this physical boundary. This reconstruction must not
|
||||
* be interpreted as validation of the parity contained below EOF.
|
||||
*/
|
||||
split->physical_reach_size = split->st.st_size;
|
||||
|
||||
/**
|
||||
* If the parity size is not yet set, set it now.
|
||||
* This happens when expanding the number of parities,
|
||||
|
|
@ -389,6 +363,29 @@ static int parity_handle_shrink(struct snapraid_split_handle* split, data_off_t
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an exact physical split size without attempting a partial allocation.
|
||||
*/
|
||||
static int parity_setsize(struct snapraid_split_handle* split, data_off_t size, int skip_fallocate)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (split->st.st_size < size)
|
||||
ret = parity_handle_grow(split, split->st.st_size, size, skip_fallocate);
|
||||
else if (split->st.st_size > size)
|
||||
ret = parity_handle_shrink(split, size);
|
||||
else
|
||||
return 0;
|
||||
|
||||
if (ret != 0)
|
||||
return -1;
|
||||
|
||||
/* the exact resize succeeded, so keep the cached EOF current */
|
||||
split->st.st_size = size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the highest bit set.
|
||||
*/
|
||||
|
|
@ -566,23 +563,73 @@ static int parity_handle_chsize(struct snapraid_split_handle* split, data_off_t
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parity_restore(struct snapraid_parity_handle* handle, data_off_t size, uint32_t block_size, int skip_fallocate)
|
||||
{
|
||||
int ret;
|
||||
unsigned s;
|
||||
unsigned physical_split;
|
||||
data_off_t block_mask;
|
||||
|
||||
/* mask of bits used by the block size */
|
||||
block_mask = ((data_off_t)block_size) - 1;
|
||||
|
||||
if (size < 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
errno = ENXIO;
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
physical_split = 0;
|
||||
for (s = 0; s < handle->split_mac; ++s) {
|
||||
struct snapraid_split_handle* split = &handle->split_map[s];
|
||||
data_off_t run = split->size;
|
||||
|
||||
/* preserve persisted split boundaries and cut only the logical tail */
|
||||
if (run > size)
|
||||
run = size;
|
||||
|
||||
if ((run & block_mask) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
errno = ENXIO;
|
||||
log_fatal(EINTERNAL, "Internal inconsistency in split '%s' size with extra '%" PRIu64 "' bytes.\n", split->path, run & block_mask);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* defer growth until a parity block is actually written */
|
||||
if (split->st.st_size > run) {
|
||||
ret = parity_setsize(split, run, 0);
|
||||
if (ret != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
if (split->st.st_size != 0)
|
||||
physical_split = s;
|
||||
|
||||
size -= run;
|
||||
}
|
||||
|
||||
/*
|
||||
* Keep the physical read/truncation boundary consistent with resizing.
|
||||
*
|
||||
* Shrinking reduces physical_reach_size because bytes beyond the new EOF no longer
|
||||
* exist physically and therefore cannot be read.
|
||||
*
|
||||
* Growing leaves physical_reach_size unchanged. Allocation or preallocation alone
|
||||
* does not make the newly created physical extent readable as parity; the
|
||||
* boundary is advanced only after a parity write has completed.
|
||||
*
|
||||
* This must not be confused with parity validity. Advancing physical_reach_size
|
||||
* after a completed write only records the physical offset reached by writes.
|
||||
* It does not certify preceding bytes as correct parity; parity may remain
|
||||
* invalid or unwritten in any region below this boundary.
|
||||
* After capping the tail, a non-empty later split belongs to the required
|
||||
* layout and requires every preceding persisted boundary to exist exactly.
|
||||
*/
|
||||
if (split->physical_reach_size > split->st.st_size)
|
||||
split->physical_reach_size = split->st.st_size;
|
||||
for (s = 0; s < physical_split; ++s) {
|
||||
struct snapraid_split_handle* split = &handle->split_map[s];
|
||||
|
||||
ret = parity_setsize(split, split->size, skip_fallocate);
|
||||
if (ret != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -616,7 +663,7 @@ static int parity_split_is_fixed(struct snapraid_parity_handle* handle, unsigned
|
|||
return 1;
|
||||
}
|
||||
|
||||
int parity_chsize(struct snapraid_parity_handle* handle, struct snapraid_parity* parity, int* is_modified, data_off_t size, uint32_t block_size, int skip_fallocate, int skip_space_holder, int allow_split_realloc)
|
||||
int parity_chsize(struct snapraid_parity_handle* handle, struct snapraid_parity* parity, int* is_modified, data_off_t size, uint32_t block_size, int skip_fallocate, int skip_space_holder)
|
||||
{
|
||||
int ret;
|
||||
unsigned s;
|
||||
|
|
@ -677,21 +724,15 @@ int parity_chsize(struct snapraid_parity_handle* handle, struct snapraid_parity*
|
|||
log_fatal(errno, "Unexpected over resizing parity file '%s' to size %" PRIu64 " resulting in size %" PRIu64 ".\n", split->path, run, (uint64_t)split->st.st_size);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
} else if ((is_fixed || !allow_split_realloc) && split->st.st_size < run) {
|
||||
} else if (is_fixed && split->st.st_size < run) {
|
||||
/*
|
||||
* A fixed split must always reach its logical boundary because changing
|
||||
* it would shift the logical offsets of data already present in following
|
||||
* splits.
|
||||
*
|
||||
* Some callers also require the existing split layout to remain unchanged
|
||||
* even when this split would normally be elastic. In particular, fix does
|
||||
* not persist a changed split mapping. Accepting a partial allocation here
|
||||
* would make the remaining size move into a following split and fix could
|
||||
* then write parity using a transient layout that is lost on the next run.
|
||||
*/
|
||||
/* LCOV_EXCL_START */
|
||||
errno = ENXIO;
|
||||
log_fatal(errno, "Failed restoring parity file '%s' to size %" PRIu64 " resulting in size %" PRIu64 ".\n", split->path, run, (uint64_t)split->st.st_size);
|
||||
log_fatal(errno, "Failed resizing parity file '%s' to size %" PRIu64 " resulting in size %" PRIu64 ".\n", split->path, run, (uint64_t)split->st.st_size);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
} else {
|
||||
|
|
@ -805,27 +846,6 @@ int parity_open(struct snapraid_parity_handle* handle, const struct snapraid_par
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the physical read/truncation boundary from the current EOF.
|
||||
*
|
||||
* physical_reach_size does NOT imply that parity below this offset is valid.
|
||||
* Parity may be invalid, stale, unwritten, or sparse in any region below
|
||||
* this boundary. It only records the physical EOF boundary for I/O and
|
||||
* truncation. The physical contents below this boundary are not necessarily
|
||||
* described by the content state. Recovery correctness is validated
|
||||
* independently using data hashes and recomputation.
|
||||
*
|
||||
* While a handle is open, physical_reach_size can intentionally differ from EOF.
|
||||
* In particular, preallocation may extend EOF without extending physical_reach_size.
|
||||
* Writable fix paths normally truncate the file back to physical_reach_size before
|
||||
* closing.
|
||||
*
|
||||
* Across close/reopen there is no separate persistent physical_reach value, so EOF
|
||||
* is used to reconstruct this physical boundary. This reconstruction must not
|
||||
* be interpreted as validation of the parity contained below EOF.
|
||||
*/
|
||||
split->physical_reach_size = split->st.st_size;
|
||||
|
||||
/**
|
||||
* If the parity size is not yet set, set it now.
|
||||
* This happens when expanding the number of parities,
|
||||
|
|
@ -977,67 +997,6 @@ int parity_sync(struct snapraid_parity_handle* handle)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Truncate parity splits from the logical tail backwards.
|
||||
*
|
||||
* This removes preallocated or otherwise disposable data beyond the reached extent.
|
||||
*
|
||||
* A split can only be truncated to physical_reach_size if it represents the logical
|
||||
* tail of the parity (i.e. all following splits are empty). Intermediate splits
|
||||
* followed by non-empty splits must maintain their allocated logical boundary
|
||||
* (split->size) so that logical parity offsets in subsequent splits are not shifted,
|
||||
* and physical file lengths match the split boundaries expected on subsequent reopen.
|
||||
*/
|
||||
int parity_truncate(struct snapraid_parity_handle* handle)
|
||||
{
|
||||
unsigned s;
|
||||
int f_ret = 0;
|
||||
int following_is_zero = 1;
|
||||
|
||||
for (s = handle->split_mac; s > 0; --s) {
|
||||
struct snapraid_split_handle* split = &handle->split_map[s - 1];
|
||||
data_off_t trunc_size;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* A split can only be truncated to its physical_reach_size if it is the
|
||||
* logical tail of the parity file (i.e. all following splits are zero-sized).
|
||||
*
|
||||
* Once a following split contains data, any preceding split is an internal
|
||||
* segment whose allocated boundary must be preserved at split->size.
|
||||
* Truncating an internal split would shift logical offsets in all subsequent
|
||||
* splits, invalidating their parity positions, and if split boundaries are not
|
||||
* yet stored in the content file, subsequent reopenings would infer an incorrect
|
||||
* layout from physical EOF.
|
||||
*/
|
||||
if (following_is_zero) {
|
||||
trunc_size = split->physical_reach_size;
|
||||
} else {
|
||||
trunc_size = split->size;
|
||||
}
|
||||
|
||||
ret = ftruncate(split->f, trunc_size);
|
||||
if (ret != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal(errno, "Error truncating the parity file '%s' to size %" PRIu64 ". %s.\n", split->path, trunc_size, strerror(errno));
|
||||
f_ret = -1;
|
||||
|
||||
/*
|
||||
* On error, assume this split might not be zero-sized physically,
|
||||
* so preceding splits must preserve their full allocated size.
|
||||
*/
|
||||
following_is_zero = 0;
|
||||
|
||||
/* continue to truncate the others */
|
||||
/* LCOV_EXCL_STOP */
|
||||
} else if (trunc_size != 0) {
|
||||
following_is_zero = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return f_ret;
|
||||
}
|
||||
|
||||
int parity_close(struct snapraid_parity_handle* handle)
|
||||
{
|
||||
unsigned s;
|
||||
|
|
@ -1090,11 +1049,13 @@ struct snapraid_split_handle* parity_split_find(struct snapraid_parity_handle* h
|
|||
return 0;
|
||||
}
|
||||
|
||||
int parity_write(struct snapraid_parity_handle* handle, block_off_t pos, unsigned char* block_buffer, unsigned block_size)
|
||||
int parity_write(struct snapraid_parity_handle* handle, block_off_t pos, unsigned char* block_buffer, unsigned block_size, int skip_fallocate)
|
||||
{
|
||||
ssize_t write_ret;
|
||||
data_off_t offset;
|
||||
struct snapraid_split_handle* split;
|
||||
unsigned long split_idx;
|
||||
unsigned s;
|
||||
unsigned count;
|
||||
int ret;
|
||||
|
||||
|
|
@ -1108,12 +1069,39 @@ int parity_write(struct snapraid_parity_handle* handle, block_off_t pos, unsigne
|
|||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
split_idx = split - handle->split_map;
|
||||
|
||||
/*
|
||||
* Parity blocks cannot cross split boundaries: offsets and split sizes
|
||||
* are block-aligned, so the entire block belongs to this split.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Grow parity only as writes reach it. A write in a later split requires
|
||||
* every preceding split to reach its persisted logical boundary, otherwise
|
||||
* the physical files would not represent a contiguous logical high-water mark.
|
||||
* Allocation still says nothing about parity validity, which is tracked and
|
||||
* verified independently.
|
||||
*/
|
||||
for (s = 0; s <= split_idx; ++s) {
|
||||
struct snapraid_split_handle* grow_split = &handle->split_map[s];
|
||||
data_off_t grow_size;
|
||||
|
||||
if (s < split_idx)
|
||||
grow_size = grow_split->size;
|
||||
else
|
||||
grow_size = offset;
|
||||
|
||||
if (grow_split->st.st_size < grow_size) {
|
||||
ret = parity_setsize(grow_split, grow_size, skip_fallocate);
|
||||
if (ret != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bw_limit(handle->bw, block_size);
|
||||
|
||||
count = 0;
|
||||
|
|
@ -1143,17 +1131,8 @@ int parity_write(struct snapraid_parity_handle* handle, block_off_t pos, unsigne
|
|||
count += write_ret;
|
||||
} while (count < block_size);
|
||||
|
||||
/*
|
||||
* Extend the physical read/truncation limit only after the complete block
|
||||
* has been written.
|
||||
*
|
||||
* This is not a validity marker. In particular, writes may occur non-contiguously,
|
||||
* so bytes between the old boundary and this block are not implied to contain
|
||||
* valid parity. Parity correctness is tracked independently per block in the
|
||||
* content state.
|
||||
*/
|
||||
if (split->physical_reach_size < offset + block_size)
|
||||
split->physical_reach_size = offset + block_size;
|
||||
if (split->st.st_size < offset + block_size)
|
||||
split->st.st_size = offset + block_size;
|
||||
|
||||
ret = advise_write(&split->advise, split->f, offset, block_size);
|
||||
if (ret != 0) {
|
||||
|
|
@ -1191,12 +1170,11 @@ int parity_read(struct snapraid_parity_handle* handle, block_off_t pos, unsigned
|
|||
*/
|
||||
|
||||
/*
|
||||
* physical_reach_size only limits the physical offset that this handle considers
|
||||
* readable. Passing this check does not mean that the parity bytes are valid.
|
||||
* A readable region may contain stale, sparse, unwritten, or corrupted parity,
|
||||
* which higher-level check/recovery logic must validate independently.
|
||||
* Physical EOF only limits whether bytes can be read. It does not imply that
|
||||
* the parity is valid, synchronized, or initialized; validity is determined
|
||||
* independently by the higher-level check/recovery logic.
|
||||
*/
|
||||
if (offset >= split->physical_reach_size) {
|
||||
if (offset >= split->st.st_size) {
|
||||
/* LCOV_EXCL_START */
|
||||
errno = ENXIO;
|
||||
log_error(errno, "Reading over the end from parity file '%s' at offset %" PRIu64 " for size %u.\n", split->path, offset, block_size);
|
||||
|
|
@ -1239,4 +1217,3 @@ int parity_read(struct snapraid_parity_handle* handle, block_off_t pos, unsigned
|
|||
|
||||
return block_size;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,31 +25,6 @@ struct snapraid_split_handle {
|
|||
*/
|
||||
data_off_t size;
|
||||
|
||||
/**
|
||||
* High-water mark size of the parity split.
|
||||
*
|
||||
* This is the physical truncation and read limit of the split.
|
||||
*
|
||||
* physical_reach_size does NOT indicate that data below this offset is
|
||||
* valid or contiguous parity. Parity may be invalid, stale, unwritten,
|
||||
* or sparse at any point below this boundary. In particular, fix may
|
||||
* leave an unrecoverable stripe unfixed and continue repairing later
|
||||
* stripes, intentionally leaving holes inside the physical reach.
|
||||
*
|
||||
* Such holes are allowed and are not necessarily represented in the
|
||||
* content state. Recovery correctness is validated independently using
|
||||
* the stored data hashes or the available recovery validation.
|
||||
*
|
||||
* Data beyond physical_reach_size is disposable/preallocated and discarded by
|
||||
* parity_truncate(). A completed write advances physical_reach_size to the end of
|
||||
* the written block so it is not truncated away.
|
||||
*
|
||||
* Before closing writable parity handles during fix operations, parity_truncate()
|
||||
* persists this boundary into the physical EOF. On the next open, physical_reach_size can
|
||||
* therefore be initialized from st_size.
|
||||
*/
|
||||
data_off_t physical_reach_size;
|
||||
|
||||
/**
|
||||
* Artificial size limit for testing.
|
||||
* 0 means unlimited.
|
||||
|
|
@ -99,15 +74,22 @@ int parity_create(struct snapraid_parity_handle* handle, const struct snapraid_p
|
|||
/**
|
||||
* Change the parity size.
|
||||
*
|
||||
* If allow_split_realloc is nonzero, an elastic split that cannot reach the
|
||||
* requested size may keep the size actually allocated and leave the remaining
|
||||
* space to following splits.
|
||||
*
|
||||
* If allow_split_realloc is zero, a partial allocation must fail instead of
|
||||
* moving a logical split boundary. This is required by callers that cannot
|
||||
* persist a changed split layout.
|
||||
* An elastic split that cannot reach the requested size may keep the size
|
||||
* actually allocated and leave the remaining space to following splits.
|
||||
* The resulting split layout is returned in parity.
|
||||
*/
|
||||
int parity_chsize(struct snapraid_parity_handle* handle, struct snapraid_parity* parity, int* is_modified, data_off_t size, uint32_t block_size, int skip_fallocate, int skip_space_holder, int allow_split_realloc);
|
||||
int parity_chsize(struct snapraid_parity_handle* handle, struct snapraid_parity* parity, int* is_modified, data_off_t size, uint32_t block_size, int skip_fallocate, int skip_space_holder);
|
||||
|
||||
/**
|
||||
* Restore the parity split layout already stored in the handle, limited to size.
|
||||
*
|
||||
* Persisted split boundaries are preserved. If size ends inside the persisted
|
||||
* layout, only its logical tail is removed: the containing split is shortened
|
||||
* and all following splits are truncated to zero. If a split is physically
|
||||
* non-empty, all preceding splits are restored to their exact logical size.
|
||||
* The remaining physical tail is grown lazily by parity_write().
|
||||
*/
|
||||
int parity_restore(struct snapraid_parity_handle* handle, data_off_t size, uint32_t block_size, int skip_fallocate);
|
||||
|
||||
/**
|
||||
* Get the size of the parity.
|
||||
|
|
@ -117,21 +99,16 @@ int parity_chsize(struct snapraid_parity_handle* handle, struct snapraid_parity*
|
|||
void parity_size(struct snapraid_parity_handle* handle, data_off_t* out_size);
|
||||
|
||||
/**
|
||||
* Get the physical reach of the logical parity file across splits.
|
||||
* Get the physical size of the logical parity file across splits.
|
||||
*
|
||||
* This composes the physical_reach_size boundaries of consecutive splits.
|
||||
* If the physical_reach_size of an earlier split is smaller than its logical size,
|
||||
* This composes the physical EOF of consecutive splits within their logical
|
||||
* boundaries. If an earlier split is physically shorter than its logical size,
|
||||
* later splits cannot extend the logical offset past that missing region.
|
||||
*
|
||||
* Parity within this range is not necessarily valid, synchronized, or contiguous.
|
||||
* In particular, fix may leave an unrecoverable stripe unfixed and continue with
|
||||
* later stripes, so holes inside this range are allowed and are not necessarily
|
||||
* represented in the content state.
|
||||
*
|
||||
* For example, with two 100 GiB splits, if the first has physical_reach_size 90 GiB
|
||||
* and the second has physical_reach_size 100 GiB, the result is 90 GiB, not 190 GiB.
|
||||
* Physical presence does not imply parity validity. Parity may be invalid,
|
||||
* stale, unwritten, sparse, or contain holes independently of its physical EOF.
|
||||
*/
|
||||
void parity_physical_reach_size(struct snapraid_parity_handle* handle, data_off_t* out_size);
|
||||
void parity_physical_size(struct snapraid_parity_handle* handle, data_off_t* out_size);
|
||||
|
||||
/**
|
||||
* Open an already existing parity file.
|
||||
|
|
@ -143,11 +120,6 @@ int parity_open(struct snapraid_parity_handle* handle, const struct snapraid_par
|
|||
*/
|
||||
int parity_sync(struct snapraid_parity_handle* handle);
|
||||
|
||||
/**
|
||||
* Truncate parity splits from the logical tail backwards.
|
||||
*/
|
||||
int parity_truncate(struct snapraid_parity_handle* handle);
|
||||
|
||||
/**
|
||||
* Close the parity file.
|
||||
*/
|
||||
|
|
@ -161,7 +133,7 @@ int parity_read(struct snapraid_parity_handle* handle, block_off_t pos, unsigned
|
|||
/**
|
||||
* Write a block in the parity file.
|
||||
*/
|
||||
int parity_write(struct snapraid_parity_handle* handle, block_off_t pos, unsigned char* block_buffer, unsigned block_size);
|
||||
int parity_write(struct snapraid_parity_handle* handle, block_off_t pos, unsigned char* block_buffer, unsigned block_size, int skip_fallocate);
|
||||
|
||||
/**
|
||||
* Complete all pending I/O and sync the parity files.
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ static void test_parity(void)
|
|||
static const struct {
|
||||
unsigned split_mac;
|
||||
data_off_t size[2];
|
||||
data_off_t physical_reach_size[2];
|
||||
data_off_t physical_size[2];
|
||||
data_off_t expected;
|
||||
} test[] = {
|
||||
{ 1, { 100, 0 }, { 100, 0 }, 100 },
|
||||
|
|
@ -326,13 +326,13 @@ static void test_parity(void)
|
|||
handle.split_mac = test[i].split_mac;
|
||||
for (s = 0; s < handle.split_mac; ++s) {
|
||||
handle.split_map[s].size = test[i].size[s];
|
||||
handle.split_map[s].physical_reach_size = test[i].physical_reach_size[s];
|
||||
handle.split_map[s].st.st_size = test[i].physical_size[s];
|
||||
}
|
||||
|
||||
parity_physical_reach_size(&handle, &size);
|
||||
parity_physical_size(&handle, &size);
|
||||
if (size != test[i].expected) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_fatal(EINTERNAL, "Failed parity physical reach size test\n");
|
||||
log_fatal(EINTERNAL, "Failed parity physical size test\n");
|
||||
exit(EXIT_FAILURE);
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -911,7 +911,7 @@ static void sync_parity_writer(struct snapraid_worker* worker, struct snapraid_t
|
|||
int ret;
|
||||
|
||||
/* write parity */
|
||||
ret = parity_write(parity_handle, blockcur, buffer, state->block_size);
|
||||
ret = parity_write(parity_handle, blockcur, buffer, state->block_size, state->opt.skip_fallocate);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_tag("parity_%s:%" PRIu64 ":%s: Write error. %s.\n", es(errno), blockcur, lev_config_name(level), strerror(errno));
|
||||
|
|
@ -2011,16 +2011,16 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
* discard parity positions that are still referenced by the previously
|
||||
* persisted content file.
|
||||
*
|
||||
* This comparison deliberately does not use physical_reach_size or physical EOF.
|
||||
* Those describe physical I/O extent, not the logical parity layout whose
|
||||
* crash-consistency relationship with the content file must be preserved.
|
||||
* This comparison deliberately does not use physical EOF. It describes
|
||||
* physical I/O extent, not the logical parity layout whose crash-consistency
|
||||
* relationship with the content file must be preserved.
|
||||
*/
|
||||
parity_size(&parity_handle[l], ¤t_size);
|
||||
if (size < current_size)
|
||||
parity_shrink = 1;
|
||||
|
||||
/* number of block in the parity file */
|
||||
parity_physical_reach_size(&parity_handle[l], &out_size);
|
||||
parity_physical_size(&parity_handle[l], &out_size);
|
||||
parityblocks = out_size / state->block_size;
|
||||
|
||||
/* if the file is too small */
|
||||
|
|
@ -2041,9 +2041,9 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
* truncation before parity_tail remains fatal.
|
||||
*
|
||||
* New levels need the same physical coverage of valid blocks, not an exact
|
||||
* match with another level or the allocated extent. After an interrupted
|
||||
* sync, fix leaves pending parity to sync and may truncate its unwritten
|
||||
* tail. Sync will resize and regenerate that tail if it is still allocated.
|
||||
* match with another level or the allocated extent. Physical EOF only records
|
||||
* presence; pending or invalid parity is identified independently by block
|
||||
* state and regenerated by sync when required.
|
||||
*/
|
||||
if (!state->opt.force_full && !(state->opt.force_realloc && state->opt.parity_tail == 0)) {
|
||||
/* if the parities are too small */
|
||||
|
|
@ -2103,9 +2103,9 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
* existing post-resize content write persists the resulting split sizes before
|
||||
* parity synchronization begins.
|
||||
*
|
||||
* This decision uses the logical parity size, not physical_reach_size or physical
|
||||
* EOF. physical_reach_size is only a physical I/O extent and does not represent
|
||||
* parity validity or the persisted logical mapping.
|
||||
* This decision uses the logical parity size, not physical EOF. Physical EOF
|
||||
* is only an I/O extent and does not represent parity validity or the persisted
|
||||
* logical mapping.
|
||||
*/
|
||||
if (state->opt.force_full)
|
||||
state_sync_mark_rebuild(state);
|
||||
|
|
@ -2132,7 +2132,7 @@ int state_sync(struct snapraid_state* state, block_off_t blockstart, block_off_t
|
|||
* Sync may reallocate an elastic split into following splits. Any resulting
|
||||
* logical split mapping is persisted by the sync state-update protocol.
|
||||
*/
|
||||
ret = parity_chsize(&parity_handle[l], &state->parity[l], &is_modified, size, state->block_size, state->opt.skip_fallocate, state->opt.skip_space_holder, 1);
|
||||
ret = parity_chsize(&parity_handle[l], &state->parity[l], &is_modified, size, state->block_size, state->opt.skip_fallocate, state->opt.skip_space_holder);
|
||||
if (ret == -1) {
|
||||
/* LCOV_EXCL_START */
|
||||
data_off_t out_size;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue