-
Notifications
You must be signed in to change notification settings - Fork 980
[Store] Prevent file-per-key path collisions #2984
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1421,10 +1421,39 @@ StorageBackendAdaptor::EvictAboveDiskWatermark( | |
|
|
||
| tl::expected<bool, ErrorCode> StorageBackendAdaptor::IsExist( | ||
| const std::string& key) { | ||
| auto path = ResolvePathFromKey(key, file_storage_config_.storage_filepath, | ||
| file_per_key_config_.fsdir); | ||
| namespace fs = std::filesystem; | ||
| return fs::exists(path); | ||
| const auto path = ResolvePathFromKey( | ||
| key, file_storage_config_.storage_filepath, file_per_key_config_.fsdir); | ||
| if (fs::exists(path)) { | ||
| return true; | ||
| } | ||
|
|
||
| const auto legacy_path = ResolveLegacyPathFromKey( | ||
| key, file_storage_config_.storage_filepath, file_per_key_config_.fsdir); | ||
| std::error_code ec; | ||
| const auto size = fs::file_size(legacy_path, ec); | ||
| if (ec) { | ||
| if (ec == std::errc::no_such_file_or_directory) { | ||
| return false; | ||
| } | ||
| return tl::make_unexpected(ErrorCode::FILE_READ_FAIL); | ||
| } | ||
| std::string kv_buf; | ||
| auto load_result = storage_backend_->LoadObject(legacy_path, kv_buf, | ||
| static_cast<int64_t>(size)); | ||
| if (!load_result) { | ||
| return tl::make_unexpected(load_result.error()); | ||
| } | ||
|
|
||
| try { | ||
| KVEntry kv; | ||
| struct_pb::from_pb(kv, kv_buf); | ||
| return kv.key == key; | ||
| } catch (const std::exception& e) { | ||
| LOG(ERROR) << "Failed to decode legacy file for key: " << key | ||
| << ", error: " << e.what(); | ||
| return tl::make_unexpected(ErrorCode::FILE_READ_FAIL); | ||
| } | ||
| } | ||
|
|
||
| tl::expected<void, ErrorCode> StorageBackendAdaptor::BatchLoad( | ||
|
|
@@ -1435,6 +1464,14 @@ tl::expected<void, ErrorCode> StorageBackendAdaptor::BatchLoad( | |
| auto path = | ||
| ResolvePathFromKey(kv.key, file_storage_config_.storage_filepath, | ||
| file_per_key_config_.fsdir); | ||
| if (!std::filesystem::exists(path)) { | ||
| const auto legacy_path = ResolveLegacyPathFromKey( | ||
| kv.key, file_storage_config_.storage_filepath, | ||
| file_per_key_config_.fsdir); | ||
| if (std::filesystem::exists(legacy_path)) { | ||
| path = legacy_path; | ||
| } | ||
| } | ||
|
|
||
| kv.value.resize(slice.size); | ||
|
|
||
|
|
@@ -1447,7 +1484,25 @@ tl::expected<void, ErrorCode> StorageBackendAdaptor::BatchLoad( | |
| return tl::make_unexpected(r.error()); | ||
| } | ||
|
|
||
| struct_pb::from_pb(kv, kv_buf); | ||
| try { | ||
| struct_pb::from_pb(kv, kv_buf); | ||
| } catch (const std::exception& e) { | ||
| LOG(ERROR) << "Failed to decode file for key: " << key | ||
| << ", error: " << e.what(); | ||
| return tl::make_unexpected(ErrorCode::FILE_READ_FAIL); | ||
| } | ||
|
|
||
| if (kv.key != key) { | ||
| LOG(ERROR) << "Stored key does not match requested key"; | ||
| return tl::make_unexpected(ErrorCode::INVALID_KEY); | ||
| } | ||
| if (kv.value.size() != slice.size) { | ||
| LOG(ERROR) | ||
| << "Stored value size does not match destination for key: " | ||
| << key << ", expected: " << slice.size | ||
| << ", got: " << kv.value.size(); | ||
| return tl::make_unexpected(ErrorCode::INVALID_PARAMS); | ||
| } | ||
|
|
||
| if (!kv.value.empty()) { | ||
| std::memcpy(slice.ptr, kv.value.data(), kv.value.size()); | ||
|
|
@@ -1536,6 +1591,15 @@ tl::expected<void, ErrorCode> StorageBackendAdaptor::ScanMeta( | |
|
|
||
| KVEntry kv; | ||
| struct_pb::from_pb(kv, buf); | ||
|
Comment on lines
1592
to
1593
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
KVEntry kv;
try {
struct_pb::from_pb(kv, buf);
} catch (const std::exception& e) {
LOG(ERROR) << "Failed to decode file: " << p.string()
<< ", error: " << e.what();
continue;
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. This pre-existing |
||
|
|
||
| const auto canonical_path = ResolvePathFromKey( | ||
| kv.key, file_storage_config_.storage_filepath, | ||
| file_per_key_config_.fsdir); | ||
| if (p.lexically_normal() != | ||
| fs::path(canonical_path).lexically_normal() && | ||
| fs::exists(canonical_path)) { | ||
| continue; | ||
| } | ||
| storage_backend_->UpdateFileRecordKey(p.string(), kv.key); | ||
|
|
||
| total_keys++; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
fs::file_sizefails because the file was concurrently deleted or does not exist (e.g.,std::errc::no_such_file_or_directory),IsExistshould returnfalseinstead of failing withErrorCode::FILE_READ_FAIL. This makes the existence check more robust against concurrent evictions.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handled in b63b817:
IsExist()now treats a concurrently removed legacy file as absent, with regression coverage.