blob: 45b5e4d89dae8ec00225cae44e18f03def8eaa5f [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/zxdump/zstd-writer.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <cerrno>
#include <thread>
#include <zstd/zstd.h>
namespace zxdump {
using namespace std::literals;
namespace {
auto ErrnoError(std::string_view op, int error = errno) {
return fit::error{FdError{.op_ = op, .error_ = error}};
}
auto ZstdError(size_t result) { return fit::error{FdError{.op_ = ZSTD_getErrorName(result)}}; }
} // namespace
ZstdWriter::ZstdWriter(fbl::unique_fd fd)
: ctx_(ZSTD_createCStream()),
buffer_(new std::byte[ZSTD_CStreamOutSize()]),
fd_(std::move(fd)) {
auto set = [ctx = static_cast<ZSTD_CCtx*>(ctx_)](auto param, auto value) {
ZSTD_CCtx_setParameter(ctx, param, value);
};
set(ZSTD_c_compressionLevel, 11);
set(ZSTD_c_enableLongDistanceMatching, 1);
set(ZSTD_c_nbWorkers, std::thread::hardware_concurrency());
}
ZstdWriter::~ZstdWriter() {
auto ctx = static_cast<ZSTD_CCtx*>(ctx_);
ZSTD_freeCStream(ctx);
}
fit::result<FdError> ZstdWriter::Flush() {
ByteView out{buffer_.get(), buffer_pos_};
buffer_pos_ = 0;
while (!out.empty()) {
ssize_t n = write(fd_.get(), out.data(), out.size());
if (n < 0) {
return ErrnoError("write"sv);
}
if (n == 0) {
return ErrnoError("write returned zero"sv, EAGAIN);
}
out = out.subspan(n);
}
return fit::ok();
}
fit::result<FdError> ZstdWriter::Write(size_t offset, ByteView data) {
ZX_ASSERT(offset >= offset_);
ZX_ASSERT(!data.empty());
ZX_DEBUG_ASSERT(data.data());
// If there are holes we have to feed zero bytes to the compressor.
while (offset > offset_) {
static constexpr std::byte kZero[32] = {};
auto pad = ByteView{kZero, sizeof(kZero)}.subspan(0, offset - offset_);
auto result = Write(offset_, pad);
if (result.is_error()) {
return result.take_error();
}
}
ZSTD_inBuffer in = {data.data(), data.size(), 0};
while (in.pos < in.size) {
ZSTD_outBuffer out = {buffer_.get(), ZSTD_CStreamOutSize(), buffer_pos_};
auto ctx = static_cast<ZSTD_CCtx*>(ctx_);
size_t result = ZSTD_compressStream2(ctx, &out, &in, ZSTD_e_continue);
buffer_pos_ = out.pos;
if (ZSTD_isError(result)) {
return ZstdError(result);
}
if (in.pos < in.size) {
// Not all consumed yet, so flush the buffer.
auto result = Flush();
if (result.is_error()) {
return result.take_error();
}
}
}
offset_ += in.pos;
return fit::ok();
}
fit::result<FdError> ZstdWriter::Finish() {
size_t compress_result;
do {
ZSTD_inBuffer in = {};
ZSTD_outBuffer out = {buffer_.get(), ZSTD_CStreamOutSize(), buffer_pos_};
auto ctx = static_cast<ZSTD_CCtx*>(ctx_);
compress_result = ZSTD_compressStream2(ctx, &out, &in, ZSTD_e_end);
buffer_pos_ = out.pos;
if (ZSTD_isError(compress_result)) {
return ZstdError(compress_result);
}
if (auto result = Flush(); result.is_error()) {
return result.take_error();
}
} while (compress_result != 0);
return fit::ok();
}
} // namespace zxdump