blob: c04b707c45412d5398aaeef10f4e7d7b4db418d1 [file]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "utils/byte_instruction.h"
#include <algorithm>
#include <cstdint>
#include <string>
#include "absl/base/nullability.h"
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "riegeli/base/chain.h"
#include "riegeli/bytes/chain_reader.h"
#include "riegeli/bytes/copy_all.h"
#include "riegeli/bytes/reader.h"
#include "riegeli/bytes/writer.h"
namespace credentio {
namespace {
constexpr uint64_t kBufferChunkSize = 1024 * 1024; // 1 MiB
absl::Status ReadDataToBuffer(riegeli::Writer& writer, int64_t length,
riegeli::Chain& buffer) {
// Flush the writer to make sure the data is available for reading.
if (!writer.Flush()) {
return writer.status();
}
// Create a reader at the current position of the writer.
riegeli::Reader* reader = writer.ReadMode(writer.pos());
if (reader == nullptr) {
return writer.status();
}
// Read data from the reader to the buffer.
if (!reader->ReadAndAppend(length, buffer)) {
return reader->StatusOrAnnotate(
absl::DataLossError("Failed to read data from buffer"));
}
// Close the reader.
if (!reader->Close()) {
return reader->StatusOrAnnotate(
absl::DataLossError("Failed to close reader"));
}
return absl::OkStatus();
}
absl::Status WriteDataFromBuffer(riegeli::Chain& buffer, int64_t length,
riegeli::Writer& writer) {
// Create a reader for the buffer.
riegeli::ChainReader cr(&buffer);
// Read data from the buffer.
absl::string_view data;
if (!cr.Read(length, data)) {
return cr.StatusOrAnnotate(
absl::DataLossError("Failed to read data from buffer"));
}
// Write the data to the writer.
if (!writer.Write(data)) {
return writer.status();
}
// Close the reader.
if (!cr.Close()) {
return cr.StatusOrAnnotate(absl::DataLossError("Failed to close reader"));
}
// Remove data from the buffer, done after writing as the absl::string_view
// data is a ref into the buffer.
buffer.RemovePrefix(length);
return absl::OkStatus();
}
absl::Status MoveDataThroughBuffer(riegeli::Chain& buffer, int64_t length,
riegeli::Writer& writer) {
ABSL_RETURN_IF_ERROR(ReadDataToBuffer(writer, length, buffer));
return WriteDataFromBuffer(buffer, length, writer);
}
absl::Status MoveDataThroughBuffer(riegeli::Chain& buffer,
absl::string_view data,
riegeli::Writer& writer) {
buffer.Append(data);
return WriteDataFromBuffer(buffer, data.size(), writer);
}
absl::Status PushData(riegeli::Writer& writer, riegeli::Chain& buffer,
int64_t end_position) {
if (buffer.empty()) {
// We didn't insert anything yet, so just seek
if (!writer.Seek(end_position)) {
return writer.StatusOrAnnotate(
absl::DataLossError("Failed to seek writer"));
}
return absl::OkStatus();
}
uint64_t remaining_byte_count = end_position - writer.pos();
while (remaining_byte_count > 0) {
uint64_t count_to_move = std::min(kBufferChunkSize, remaining_byte_count);
ABSL_RETURN_IF_ERROR(MoveDataThroughBuffer(buffer, count_to_move, writer));
remaining_byte_count -= count_to_move;
}
return absl::OkStatus();
}
} // namespace
absl::Status ApplyByteInstructions(
riegeli::Reader* absl_nonnull source,
absl::Span<const ByteInstruction> instructions,
riegeli::Writer* absl_nonnull destination) {
if (!source->Seek(0) || !destination->Seek(0)) {
return absl::DataLossError("failed to seek to start of file");
}
if (instructions.empty()) {
// No instructions, just copy the source to the destination.
return riegeli::CopyAll(*source, *destination);
}
if (!source->SupportsSize() || !source->Size().has_value()) {
return absl::InvalidArgumentError("Source does not have a size.");
}
uint64_t file_size = source->Size().value_or(0);
uint64_t instruction_index = 0;
while (source->pos() < file_size && instruction_index < instructions.size()) {
const ByteInstruction& ins = instructions[instruction_index];
if (ins.offset < source->pos()) {
return absl::InvalidArgumentError(
"Byte instruction offsets must be in ascending order");
}
if (ins.offset >= file_size) {
// Next instruction is at or past the end of the file.
break;
}
// Copy enough data from the source to reach the instruction offset.
if (source->pos() < ins.offset) {
if (!source->Copy(ins.offset - source->pos(), *destination)) {
ABSL_RETURN_IF_ERROR(source->status());
ABSL_RETURN_IF_ERROR(destination->status());
return absl::DataLossError("Unexpected EOF while copying source data");
}
}
// Write the data
if (!destination->Write(ins.bytes)) {
return destination->status();
}
if (ins.operation == ByteInstruction::Operation::kReplace) {
uint64_t bytes_to_skip =
std::min<uint64_t>(ins.bytes.size(), file_size - source->pos());
if (!source->Skip(bytes_to_skip)) {
ABSL_RETURN_IF_ERROR(source->status());
return absl::DataLossError("Unexpected EOF while skipping source data");
}
}
++instruction_index;
}
// Copy any remaining data from the source to the destination.
if (source->pos() < file_size) {
ABSL_RETURN_IF_ERROR(riegeli::CopyAll(*source, *destination));
}
// Write any remaining instructions.
while (instruction_index < instructions.size()) {
const ByteInstruction& ins = instructions[instruction_index];
if (!destination->Write(ins.bytes)) {
return destination->status();
}
++instruction_index;
}
return absl::OkStatus();
}
absl::Status ApplyByteInstructions(
absl::Span<const credentio::ByteInstruction> instructions,
riegeli::Writer* absl_nonnull writer) {
if (!writer->Seek(0)) {
return absl::DataLossError("failed to seek to start of file");
}
if (instructions.empty()) {
return absl::OkStatus();
}
if (!writer->Size().has_value()) {
return absl::InvalidArgumentError("Writer does not have a size.");
}
uint64_t file_size = writer->Size().value();
riegeli::Chain buffer;
uint64_t instruction_index = 0;
while (writer->pos() < file_size && instruction_index < instructions.size()) {
const ByteInstruction& ins = instructions[instruction_index];
if (ins.offset < writer->pos()) {
return absl::InvalidArgumentError(
"Byte instruction offsets must be in ascending order");
}
if (ins.offset >= file_size) {
// Next instruction is at or past the end of the file.
break;
}
// Process enough data to reach the instruction offset.
ABSL_RETURN_IF_ERROR(PushData(*writer, buffer, ins.offset));
if (ins.operation == credentio::ByteInstruction::Operation::kInsert) {
// Insert operations do not impact the current offset, just add to the
// buffer.
buffer.Append(ins.bytes);
} else if (ins.operation ==
credentio::ByteInstruction::Operation::kReplace) {
if (buffer.empty()) {
// If the buffer is empty, we can write the data directly to the writer.
if (!writer->Write(ins.bytes)) {
return writer->status();
}
} else {
// If the buffer is not empty, we need to move the data through the
// buffer.
ABSL_RETURN_IF_ERROR(MoveDataThroughBuffer(buffer, ins.bytes, *writer));
}
}
++instruction_index;
}
// Process all remaining data up to the end of the file.
if (writer->pos() < file_size) {
ABSL_RETURN_IF_ERROR(PushData(*writer, buffer, file_size));
}
if (!buffer.empty()) {
// Write any remaining data in the buffer.
if (!writer->Write(buffer)) {
return writer->status();
}
}
// Write any remaining instructions.
while (instruction_index < instructions.size()) {
if (!writer->Write(instructions[instruction_index].bytes)) {
return writer->status();
}
++instruction_index;
}
return absl::OkStatus();
}
} // namespace credentio