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