/*
|
**
|
** 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
|