| Andreas Gampe | d0aac25 | 2018-03-22 08:20:08 -0700 | [diff] [blame] | 1 | /* |
| 2 | ** |
| 3 | ** Copyright 2015, The Android Open Source Project |
| 4 | ** |
| 5 | ** Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | ** you may not use this file except in compliance with the License. |
| 7 | ** You may obtain a copy of the License at |
| 8 | ** |
| 9 | ** http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | ** |
| 11 | ** Unless required by applicable law or agreed to in writing, software |
| 12 | ** distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | ** See the License for the specific language governing permissions and |
| 15 | ** limitations under the License. |
| 16 | */ |
| 17 | |
| 18 | #include "perfprofd_io.h" |
| 19 | |
| 20 | #include <fcntl.h> |
| 21 | #include <unistd.h> |
| 22 | |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 23 | #include <memory> |
| 24 | |
| Andreas Gampe | d0aac25 | 2018-03-22 08:20:08 -0700 | [diff] [blame] | 25 | #include <android-base/file.h> |
| 26 | #include <android-base/logging.h> |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 27 | #include <android-base/macros.h> |
| 28 | #include <android-base/stringprintf.h> |
| Andreas Gampe | c9a2ab4 | 2018-03-22 19:42:30 -0700 | [diff] [blame] | 29 | #include <google/protobuf/io/zero_copy_stream_impl_lite.h> |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 30 | #include <zlib.h> |
| Andreas Gampe | d0aac25 | 2018-03-22 08:20:08 -0700 | [diff] [blame] | 31 | |
| 32 | #include "perfprofd_record.pb.h" |
| 33 | |
| 34 | namespace android { |
| 35 | namespace perfprofd { |
| 36 | |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 37 | using android::base::StringPrintf; |
| Andreas Gampe | c9a2ab4 | 2018-03-22 19:42:30 -0700 | [diff] [blame] | 38 | using android::base::unique_fd; |
| 39 | using android::base::WriteFully; |
| Andreas Gampe | d0aac25 | 2018-03-22 08:20:08 -0700 | [diff] [blame] | 40 | |
| Andreas Gampe | c9a2ab4 | 2018-03-22 19:42:30 -0700 | [diff] [blame] | 41 | namespace { |
| 42 | |
| 43 | // Protobuf's file implementation is not available in protobuf-lite. :-( |
| 44 | class FileCopyingOutputStream : public ::google::protobuf::io::CopyingOutputStream { |
| 45 | public: |
| 46 | explicit FileCopyingOutputStream(android::base::unique_fd&& fd_in) : fd_(std::move(fd_in)) { |
| 47 | }; |
| 48 | bool Write(const void * buffer, int size) override { |
| 49 | return WriteFully(fd_.get(), buffer, size); |
| 50 | } |
| 51 | |
| 52 | private: |
| 53 | android::base::unique_fd fd_; |
| 54 | }; |
| 55 | |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 56 | using google::protobuf::io::ZeroCopyOutputStream; |
| 57 | |
| 58 | // Protobuf's Gzip implementation is not available in protobuf-lite. :-( |
| 59 | class GzipOutputStream : public ZeroCopyOutputStream { |
| 60 | public: |
| 61 | ~GzipOutputStream(); |
| 62 | |
| 63 | static std::unique_ptr<GzipOutputStream> Create(ZeroCopyOutputStream* next, |
| 64 | std::string* error_msg); |
| 65 | |
| 66 | bool Next(void** data, int* size) override; |
| 67 | |
| 68 | void BackUp(int count) override; |
| 69 | |
| 70 | google::protobuf::int64 ByteCount() const override; |
| 71 | |
| 72 | bool WriteAliasedRaw(const void* data, int size) override; |
| 73 | bool AllowsAliasing() const override; |
| 74 | |
| 75 | bool Flush(); |
| 76 | bool Close(); |
| 77 | |
| 78 | private: |
| 79 | GzipOutputStream(ZeroCopyOutputStream* next, z_stream* stream); |
| 80 | |
| 81 | int Write(int flush_flags); |
| 82 | bool NextBuffer(); |
| 83 | |
| 84 | ZeroCopyOutputStream* next_; |
| 85 | void* next_data_; |
| 86 | int next_size_; |
| 87 | |
| 88 | z_stream* stream_; |
| 89 | std::unique_ptr<uint8_t[]> stream_buffer_; |
| 90 | bool had_error_; |
| 91 | }; |
| 92 | |
| 93 | constexpr size_t kStreamBufferSize = 16u * 1024u; |
| 94 | |
| 95 | GzipOutputStream::GzipOutputStream(ZeroCopyOutputStream* next, z_stream* stream) |
| 96 | : next_(next), |
| 97 | next_data_(nullptr), |
| 98 | next_size_(0), |
| 99 | stream_(stream), |
| 100 | stream_buffer_(nullptr), |
| 101 | had_error_(false) { |
| 102 | } |
| 103 | |
| 104 | GzipOutputStream::~GzipOutputStream() { |
| 105 | if (stream_ != nullptr) { |
| 106 | deflateEnd(stream_); |
| 107 | delete stream_; |
| 108 | stream_ = nullptr; |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | bool GzipOutputStream::WriteAliasedRaw(const void* data ATTRIBUTE_UNUSED, |
| 113 | int size ATTRIBUTE_UNUSED) { |
| 114 | LOG(FATAL) << "Not supported"; |
| 115 | __builtin_unreachable(); |
| 116 | } |
| 117 | bool GzipOutputStream::AllowsAliasing() const { |
| 118 | return false; |
| 119 | } |
| 120 | |
| 121 | google::protobuf::int64 GzipOutputStream::ByteCount() const { |
| 122 | return stream_->total_in + stream_->avail_in; |
| 123 | } |
| 124 | |
| 125 | std::unique_ptr<GzipOutputStream> GzipOutputStream::Create(ZeroCopyOutputStream* next, |
| 126 | std::string* error_msg) { |
| 127 | std::unique_ptr<z_stream> stream(new z_stream); |
| 128 | |
| 129 | stream->zalloc = Z_NULL; |
| 130 | stream->zfree = Z_NULL; |
| 131 | stream->opaque = Z_NULL; |
| 132 | stream->msg = nullptr; |
| 133 | stream->avail_in = 0; |
| 134 | stream->total_in = 0; |
| 135 | stream->next_in = nullptr; |
| 136 | stream->total_out = 0; |
| 137 | |
| 138 | { |
| 139 | constexpr int kWindowBits = 15; |
| 140 | constexpr int kGzipEncoding = 16; |
| 141 | constexpr int kMemLevel = 8; // Default. |
| 142 | int init_result = deflateInit2(stream.get(), |
| 143 | Z_DEFAULT_COMPRESSION, |
| 144 | Z_DEFLATED, |
| 145 | kWindowBits | kGzipEncoding, |
| 146 | kMemLevel, |
| 147 | Z_DEFAULT_STRATEGY); |
| 148 | if (init_result != Z_OK) { |
| 149 | *error_msg = StringPrintf("Could not initialize compression: %d (%s)", |
| 150 | init_result, |
| 151 | stream->msg != nullptr ? stream->msg : "no message"); |
| 152 | return nullptr; |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | return std::unique_ptr<GzipOutputStream>(new GzipOutputStream(next, stream.release())); |
| 157 | } |
| 158 | |
| 159 | bool GzipOutputStream::NextBuffer() { |
| 160 | for (;;) { |
| 161 | if (!next_->Next(&next_data_, &next_size_)) { |
| 162 | next_data_ = nullptr; |
| 163 | next_size_ = 0; |
| 164 | return false; |
| 165 | } |
| 166 | if (next_size_ == 0) { |
| 167 | continue; |
| 168 | } |
| 169 | stream_->next_out = static_cast<Bytef*>(next_data_); |
| 170 | stream_->avail_out = next_size_; |
| 171 | return true; |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | int GzipOutputStream::Write(int flush_flags) { |
| 176 | CHECK(flush_flags == Z_NO_FLUSH || flush_flags == Z_FULL_FLUSH || flush_flags == Z_FINISH); |
| 177 | |
| 178 | int res; |
| 179 | do { |
| 180 | if ((next_data_ == nullptr || stream_->avail_out == 0) && !NextBuffer()) { |
| 181 | return Z_BUF_ERROR; |
| 182 | } |
| 183 | res = deflate(stream_, flush_flags); |
| 184 | } while (res == Z_OK && stream_->avail_out == 0); |
| 185 | |
| 186 | if (flush_flags == Z_FULL_FLUSH || flush_flags == Z_FINISH) { |
| 187 | next_->BackUp(stream_->avail_out); |
| 188 | next_data_ = nullptr; |
| 189 | next_size_ = 0; |
| 190 | } |
| 191 | |
| 192 | return res; |
| 193 | } |
| 194 | |
| 195 | bool GzipOutputStream::Next(void** data, int* size) { |
| 196 | if (had_error_) { |
| 197 | return false; |
| 198 | } |
| 199 | |
| 200 | // Write all pending data. |
| 201 | if (stream_->avail_in > 0) { |
| 202 | int write_error = Write(Z_NO_FLUSH); |
| 203 | if (write_error != Z_OK) { |
| 204 | had_error_ = true; |
| 205 | return false; |
| 206 | } |
| 207 | CHECK_EQ(stream_->avail_in, 0); |
| 208 | } |
| 209 | |
| 210 | if (stream_buffer_ == nullptr) { |
| 211 | stream_buffer_.reset(new uint8_t[kStreamBufferSize]); |
| 212 | } |
| 213 | |
| 214 | stream_->next_in = static_cast<Bytef*>(stream_buffer_.get()); |
| 215 | stream_->avail_in = kStreamBufferSize; |
| 216 | *data = stream_buffer_.get(); |
| 217 | *size = kStreamBufferSize; |
| 218 | return true; |
| 219 | } |
| 220 | |
| 221 | void GzipOutputStream::BackUp(int count) { |
| 222 | CHECK_GE(stream_->avail_in, count); |
| 223 | stream_->avail_in -= count; |
| 224 | } |
| 225 | |
| 226 | bool GzipOutputStream::Flush() { |
| 227 | if (had_error_) { |
| 228 | return false; |
| 229 | } |
| 230 | |
| 231 | int res = Write(Z_FULL_FLUSH); |
| 232 | had_error_ |= (res != Z_OK) |
| 233 | && !(res == Z_BUF_ERROR && stream_->avail_in == 0 && stream_->avail_out > 0); |
| 234 | return !had_error_; |
| 235 | } |
| 236 | |
| 237 | bool GzipOutputStream::Close() { |
| 238 | if (had_error_) { |
| 239 | return false; |
| 240 | } |
| 241 | |
| 242 | { |
| 243 | int res; |
| 244 | do { |
| 245 | res = Write(Z_FINISH); |
| 246 | } while (res == Z_OK); |
| 247 | } |
| 248 | |
| 249 | int res = deflateEnd(stream_); |
| 250 | delete stream_; |
| 251 | stream_ = nullptr; |
| 252 | |
| 253 | had_error_ = true; // Pretend an error so no other operations succeed. |
| 254 | |
| 255 | return res == Z_OK; |
| 256 | } |
| 257 | |
| Andreas Gampe | c9a2ab4 | 2018-03-22 19:42:30 -0700 | [diff] [blame] | 258 | } // namespace |
| 259 | |
| 260 | bool SerializeProtobuf(android::perfprofd::PerfprofdRecord* encodedProfile, |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 261 | android::base::unique_fd&& fd, |
| 262 | bool compress) { |
| Andreas Gampe | c9a2ab4 | 2018-03-22 19:42:30 -0700 | [diff] [blame] | 263 | FileCopyingOutputStream fcos(std::move(fd)); |
| 264 | google::protobuf::io::CopyingOutputStreamAdaptor cosa(&fcos); |
| 265 | |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 266 | ZeroCopyOutputStream* out; |
| 267 | |
| 268 | std::unique_ptr<GzipOutputStream> gzip; |
| 269 | if (compress) { |
| 270 | std::string error_msg; |
| 271 | gzip = GzipOutputStream::Create(&cosa, &error_msg); |
| 272 | if (gzip == nullptr) { |
| 273 | LOG(ERROR) << error_msg; |
| 274 | return false; |
| 275 | } |
| 276 | out = gzip.get(); |
| 277 | } else { |
| 278 | out = &cosa; |
| 279 | } |
| 280 | |
| 281 | bool serialized = encodedProfile->SerializeToZeroCopyStream(out); |
| Andreas Gampe | c9a2ab4 | 2018-03-22 19:42:30 -0700 | [diff] [blame] | 282 | if (!serialized) { |
| 283 | LOG(WARNING) << "SerializeToZeroCopyStream failed"; |
| 284 | return false; |
| 285 | } |
| 286 | |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 287 | bool zip_ok = true; |
| 288 | if (gzip != nullptr) { |
| 289 | zip_ok = gzip->Flush(); |
| 290 | zip_ok = gzip->Close() && zip_ok; |
| 291 | } |
| Andreas Gampe | c9a2ab4 | 2018-03-22 19:42:30 -0700 | [diff] [blame] | 292 | cosa.Flush(); |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 293 | return zip_ok; |
| Andreas Gampe | c9a2ab4 | 2018-03-22 19:42:30 -0700 | [diff] [blame] | 294 | } |
| 295 | |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 296 | bool SerializeProtobuf(PerfprofdRecord* encodedProfile, |
| 297 | const char* encoded_file_path, |
| 298 | bool compress) { |
| Andreas Gampe | d0aac25 | 2018-03-22 08:20:08 -0700 | [diff] [blame] | 299 | unlink(encoded_file_path); // Attempt to unlink for a clean slate. |
| 300 | constexpr int kFlags = O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW | O_CLOEXEC; |
| Andreas Gampe | c9a2ab4 | 2018-03-22 19:42:30 -0700 | [diff] [blame] | 301 | unique_fd fd(open(encoded_file_path, kFlags, 0664)); |
| Andreas Gampe | d0aac25 | 2018-03-22 08:20:08 -0700 | [diff] [blame] | 302 | if (fd.get() == -1) { |
| 303 | PLOG(WARNING) << "Could not open " << encoded_file_path << " for serialization"; |
| 304 | return false; |
| 305 | } |
| Andreas Gampe | 894b3f9 | 2018-03-22 19:48:48 -0700 | [diff] [blame] | 306 | return SerializeProtobuf(encodedProfile, std::move(fd), compress); |
| Andreas Gampe | d0aac25 | 2018-03-22 08:20:08 -0700 | [diff] [blame] | 307 | } |
| 308 | |
| 309 | } // namespace perfprofd |
| 310 | } // namespace android |