/*
**
** Copyright 2015, The Android Open Source Project
**
** 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 "perfprofd_io.h"
#include <fcntl.h>
#include <unistd.h>
#include <memory>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/stringprintf.h>
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <zlib.h>
#include "perfprofd_record.pb.h"
namespace android {
namespace perfprofd {
using android::base::StringPrintf;
using android::base::unique_fd;
using android::base::WriteFully;
namespace {
// Protobuf's file implementation is not available in protobuf-lite. :-(
class FileCopyingOutputStream : public ::google::protobuf::io::CopyingOutputStream {
public:
explicit FileCopyingOutputStream(android::base::unique_fd&& fd_in) : fd_(std::move(fd_in)) {
};
bool Write(const void * buffer, int size) override {
return WriteFully(fd_.get(), buffer, size);
}
private:
android::base::unique_fd fd_;
};
using google::protobuf::io::ZeroCopyOutputStream;
// Protobuf's Gzip implementation is not available in protobuf-lite. :-(
class GzipOutputStream : public ZeroCopyOutputStream {
public:
~GzipOutputStream();
static std::unique_ptr<GzipOutputStream> Create(ZeroCopyOutputStream* next,
std::string* error_msg);
bool Next(void** data, int* size) override;
void BackUp(int count) override;
google::protobuf::int64 ByteCount() const override;
bool WriteAliasedRaw(const void* data, int size) override;
bool AllowsAliasing() const override;
bool Flush();
bool Close();
private:
GzipOutputStream(ZeroCopyOutputStream* next, z_stream* stream);
int Write(int flush_flags);
bool NextBuffer();
ZeroCopyOutputStream* next_;
void* next_data_;
int next_size_;
z_stream* stream_;
std::unique_ptr<uint8_t[]> stream_buffer_;
bool had_error_;
};
constexpr size_t kStreamBufferSize = 16u * 1024u;
GzipOutputStream::GzipOutputStream(ZeroCopyOutputStream* next, z_stream* stream)
: next_(next),
next_data_(nullptr),
next_size_(0),
stream_(stream),
stream_buffer_(nullptr),
had_error_(false) {
}
GzipOutputStream::~GzipOutputStream() {
if (stream_ != nullptr) {
deflateEnd(stream_);
delete stream_;
stream_ = nullptr;
}
}
bool GzipOutputStream::WriteAliasedRaw(const void* data ATTRIBUTE_UNUSED,
int size ATTRIBUTE_UNUSED) {
LOG(FATAL) << "Not supported";
__builtin_unreachable();
}
bool GzipOutputStream::AllowsAliasing() const {
return false;
}
google::protobuf::int64 GzipOutputStream::ByteCount() const {
return stream_->total_in + stream_->avail_in;
}
std::unique_ptr<GzipOutputStream> GzipOutputStream::Create(ZeroCopyOutputStream* next,
std::string* error_msg) {
std::unique_ptr<z_stream> stream(new z_stream);
stream->zalloc = Z_NULL;
stream->zfree = Z_NULL;
stream->opaque = Z_NULL;
stream->msg = nullptr;
stream->avail_in = 0;
stream->total_in = 0;
stream->next_in = nullptr;
stream->total_out = 0;
{
constexpr int kWindowBits = 15;
constexpr int kGzipEncoding = 16;
constexpr int kMemLevel = 8; // Default.
int init_result = deflateInit2(stream.get(),
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
kWindowBits | kGzipEncoding,
kMemLevel,
Z_DEFAULT_STRATEGY);
if (init_result != Z_OK) {
*error_msg = StringPrintf("Could not initialize compression: %d (%s)",
init_result,
stream->msg != nullptr ? stream->msg : "no message");
return nullptr;
}
}
return std::unique_ptr<GzipOutputStream>(new GzipOutputStream(next, stream.release()));
}
bool GzipOutputStream::NextBuffer() {
for (;;) {
if (!next_->Next(&next_data_, &next_size_)) {
next_data_ = nullptr;
next_size_ = 0;
return false;
}
if (next_size_ == 0) {
continue;
}
stream_->next_out = static_cast<Bytef*>(next_data_);
stream_->avail_out = next_size_;
return true;
}
}
int GzipOutputStream::Write(int flush_flags) {
CHECK(flush_flags == Z_NO_FLUSH || flush_flags == Z_FULL_FLUSH || flush_flags == Z_FINISH);
int res;
do {
if ((next_data_ == nullptr || stream_->avail_out == 0) && !NextBuffer()) {
return Z_BUF_ERROR;
}
res = deflate(stream_, flush_flags);
} while (res == Z_OK && stream_->avail_out == 0);
if (flush_flags == Z_FULL_FLUSH || flush_flags == Z_FINISH) {
next_->BackUp(stream_->avail_out);
next_data_ = nullptr;
next_size_ = 0;
}
return res;
}
bool GzipOutputStream::Next(void** data, int* size) {
if (had_error_) {
return false;
}
// Write all pending data.
if (stream_->avail_in > 0) {
int write_error = Write(Z_NO_FLUSH);
if (write_error != Z_OK) {
had_error_ = true;
return false;
}
CHECK_EQ(stream_->avail_in, 0);
}
if (stream_buffer_ == nullptr) {
stream_buffer_.reset(new uint8_t[kStreamBufferSize]);
}
stream_->next_in = static_cast<Bytef*>(stream_buffer_.get());
stream_->avail_in = kStreamBufferSize;
*data = stream_buffer_.get();
*size = kStreamBufferSize;
return true;
}
void GzipOutputStream::BackUp(int count) {
CHECK_GE(stream_->avail_in, count);
stream_->avail_in -= count;
}
bool GzipOutputStream::Flush() {
if (had_error_) {
return false;
}
int res = Write(Z_FULL_FLUSH);
had_error_ |= (res != Z_OK)
&& !(res == Z_BUF_ERROR && stream_->avail_in == 0 && stream_->avail_out > 0);
return !had_error_;
}
bool GzipOutputStream::Close() {
if (had_error_) {
return false;
}
{
int res;
do {
res = Write(Z_FINISH);
} while (res == Z_OK);
}
int res = deflateEnd(stream_);
delete stream_;
stream_ = nullptr;
had_error_ = true; // Pretend an error so no other operations succeed.
return res == Z_OK;
}
} // namespace
bool SerializeProtobuf(android::perfprofd::PerfprofdRecord* encodedProfile,
android::base::unique_fd&& fd,
bool compress) {
FileCopyingOutputStream fcos(std::move(fd));
google::protobuf::io::CopyingOutputStreamAdaptor cosa(&fcos);
ZeroCopyOutputStream* out;
std::unique_ptr<GzipOutputStream> gzip;
if (compress) {
std::string error_msg;
gzip = GzipOutputStream::Create(&cosa, &error_msg);
if (gzip == nullptr) {
LOG(ERROR) << error_msg;
return false;
}
out = gzip.get();
} else {
out = &cosa;
}
bool serialized = encodedProfile->SerializeToZeroCopyStream(out);
if (!serialized) {
LOG(WARNING) << "SerializeToZeroCopyStream failed";
return false;
}
bool zip_ok = true;
if (gzip != nullptr) {
zip_ok = gzip->Flush();
zip_ok = gzip->Close() && zip_ok;
}
cosa.Flush();
return zip_ok;
}
bool SerializeProtobuf(PerfprofdRecord* encodedProfile,
const char* encoded_file_path,
bool compress) {
unlink(encoded_file_path); // Attempt to unlink for a clean slate.
constexpr int kFlags = O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW | O_CLOEXEC;
unique_fd fd(open(encoded_file_path, kFlags, 0664));
if (fd.get() == -1) {
PLOG(WARNING) << "Could not open " << encoded_file_path << " for serialization";
return false;
}
return SerializeProtobuf(encodedProfile, std::move(fd), compress);
}
} // namespace perfprofd
} // namespace android