blob: 5714fcd2cb5236b3d2fe212827c3eb099e93fd14 [file]
// Copyright 2022 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
//
// http://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 "riegeli/bzip2/bzip2_writer.h"
#include <stddef.h>
#include <limits>
#include <memory>
#include <utility>
#include "absl/base/optimization.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "bzlib.h"
#include "riegeli/base/arithmetic.h"
#include "riegeli/base/assert.h"
#include "riegeli/base/status.h"
#include "riegeli/base/types.h"
#include "riegeli/bytes/buffered_writer.h"
#include "riegeli/bytes/writer.h"
#include "riegeli/bzip2/bzip2_error.h"
namespace riegeli {
// Before C++17 if a constexpr static data member is ODR-used, its definition at
// namespace scope is required. Since C++17 these definitions are deprecated:
// http://en.cppreference.com/w/cpp/language/static
#if !__cpp_inline_variables
constexpr int Bzip2WriterBase::Options::kMinCompressionLevel;
constexpr int Bzip2WriterBase::Options::kMaxCompressionLevel;
constexpr int Bzip2WriterBase::Options::kDefaultCompressionLevel;
#endif
void Bzip2WriterBase::Initialize(Writer* dest, int compression_level) {
RIEGELI_ASSERT(dest != nullptr)
<< "Failed precondition of Bzip2Writer: null Writer pointer";
if (ABSL_PREDICT_FALSE(!dest->ok())) {
FailWithoutAnnotation(AnnotateOverDest(dest->status()));
return;
}
initial_compressed_pos_ = dest->pos();
compressor_.reset(new bz_stream());
const int bzlib_code =
BZ2_bzCompressInit(compressor_.get(), compression_level, 0, 0);
if (ABSL_PREDICT_FALSE(bzlib_code != BZ_OK)) {
delete compressor_.release(); // Skip `BZ2_bzCompressEnd()`.
FailOperation("BZ2_bzCompressInit()", bzlib_code);
}
}
void Bzip2WriterBase::DoneBehindBuffer(absl::string_view src) {
RIEGELI_ASSERT_EQ(start_to_limit(), 0u)
<< "Failed precondition of BufferedWriter::DoneBehindBuffer(): "
"buffer not empty";
if (ABSL_PREDICT_FALSE(!ok())) return;
Writer& dest = *DestWriter();
WriteInternal(src, dest, BZ_FINISH);
}
void Bzip2WriterBase::Done() {
BufferedWriter::Done();
compressor_.reset();
}
inline bool Bzip2WriterBase::FailOperation(absl::string_view operation,
int bzlib_code) {
RIEGELI_ASSERT(bzlib_code != BZ_OK && bzlib_code != BZ_RUN_OK &&
bzlib_code != BZ_FLUSH_OK && bzlib_code != BZ_FINISH_OK)
<< "Failed precondition of Bzip2WriterBase::FailOperation(): "
"bzlib error code not failed";
RIEGELI_ASSERT(is_open())
<< "Failed precondition of Bzip2WriterBase::FailOperation(): "
"Object closed";
return Fail(bzip2_internal::Bzip2ErrorToStatus(operation, bzlib_code));
}
absl::Status Bzip2WriterBase::AnnotateStatusImpl(absl::Status status) {
if (is_open()) {
Writer& dest = *DestWriter();
status = dest.AnnotateStatus(std::move(status));
}
// The status might have been annotated by `dest` with the compressed
// position. Clarify that the current position is the uncompressed position
// instead of delegating to `BufferedWriter::AnnotateStatusImpl()`.
return AnnotateOverDest(std::move(status));
}
absl::Status Bzip2WriterBase::AnnotateOverDest(absl::Status status) {
if (is_open()) {
return Annotate(status, absl::StrCat("at uncompressed byte ", pos()));
}
return status;
}
bool Bzip2WriterBase::WriteInternal(absl::string_view src) {
RIEGELI_ASSERT(!src.empty())
<< "Failed precondition of BufferedWriter::WriteInternal(): "
"nothing to write";
RIEGELI_ASSERT(ok())
<< "Failed precondition of BufferedWriter::WriteInternal(): " << status();
Writer& dest = *DestWriter();
return WriteInternal(src, dest, BZ_RUN);
}
inline bool Bzip2WriterBase::WriteInternal(absl::string_view src, Writer& dest,
int flush) {
RIEGELI_ASSERT(ok())
<< "Failed precondition of Bzip2WriterBase::WriteInternal(): "
<< status();
if (ABSL_PREDICT_FALSE(src.size() >
std::numeric_limits<Position>::max() - start_pos())) {
return FailOverflow();
}
compressor_->next_in = const_cast<char*>(src.data());
for (;;) {
// If no progress was made, e.g. `compressor_->avail_out == 0` but
// `BZ2_bzCompress()` wants to output compressed data, then
// `BZ2_bzCompress()` returns `BZ_PARAM_ERROR`, so `dest.Push()` first.
if (ABSL_PREDICT_FALSE(!dest.Push())) {
return FailWithoutAnnotation(AnnotateOverDest(dest.status()));
}
size_t avail_in =
PtrDistance(compressor_->next_in, src.data() + src.size());
int action = flush;
if (ABSL_PREDICT_FALSE(avail_in >
std::numeric_limits<unsigned int>::max())) {
avail_in = size_t{std::numeric_limits<unsigned int>::max()};
action = BZ_RUN;
}
compressor_->avail_in = IntCast<unsigned int>(avail_in);
compressor_->next_out = dest.cursor();
compressor_->avail_out = SaturatingIntCast<unsigned int>(dest.available());
const int bzlib_code = BZ2_bzCompress(compressor_.get(), action);
dest.set_cursor(compressor_->next_out);
const size_t length_written = PtrDistance(src.data(), compressor_->next_in);
switch (bzlib_code) {
case BZ_RUN_OK:
if (length_written < src.size()) {
RIEGELI_ASSERT(compressor_->avail_in == 0 ||
compressor_->avail_out == 0)
<< "BZ2_bzCompress() returned but there are still input data "
"and output space";
continue;
}
break;
case BZ_FLUSH_OK:
case BZ_FINISH_OK:
RIEGELI_ASSERT_EQ(compressor_->avail_out, 0u)
<< "BZ2_bzCompress() is "
<< (bzlib_code == BZ_FLUSH_OK ? "flushing" : "finishing")
<< " but there is still output space";
continue;
case BZ_STREAM_END:
break;
default:
return FailOperation("BZ2_bzCompress()", bzlib_code);
}
RIEGELI_ASSERT_EQ(length_written, src.size())
<< "BZ2_bzCompress() returned but there are still input data";
move_start_pos(length_written);
return true;
}
}
bool Bzip2WriterBase::FlushBehindBuffer(absl::string_view src,
FlushType flush_type) {
RIEGELI_ASSERT_EQ(start_to_limit(), 0u)
<< "Failed precondition of BufferedWriter::FlushBehindBuffer(): "
"buffer not empty";
if (ABSL_PREDICT_FALSE(!ok())) return false;
Writer& dest = *DestWriter();
return WriteInternal(src, dest, BZ_FLUSH);
}
} // namespace riegeli