1 // Copyright (C) 2017 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "protobuf_io.h"
16 
17 #include "common/trace.h"
18 #include "serialize/arena_ptr.h"
19 
20 #include <android-base/chrono_utils.h>
21 #include <android-base/logging.h>
22 #include <android-base/unique_fd.h>
23 #include <fcntl.h>
24 #include <sys/mman.h>
25 #include <sys/stat.h>
26 #include <sys/types.h>
27 #include <unistd.h>
28 #include <utils/Trace.h>
29 
30 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
31 #include "system/iorap/src/serialize/TraceFile.pb.h"
32 
33 namespace iorap {
34 namespace serialize {
35 
Open(std::string file_path)36 ArenaPtr<proto::TraceFile> ProtobufIO::Open(std::string file_path) {
37   // TODO: file a bug about this.
38   // Note: can't use {} here, clang think it's narrowing from long->int.
39   android::base::unique_fd fd(TEMP_FAILURE_RETRY(::open(file_path.c_str(), O_RDONLY)));
40   if (fd.get() < 0) {
41     PLOG(DEBUG) << "ProtobufIO: open failed: " << file_path;
42     return nullptr;
43   }
44 
45   return Open(fd.get(), file_path.c_str());
46 }
47 
Open(int fd,const char * file_path)48 ArenaPtr<proto::TraceFile> ProtobufIO::Open(int fd, const char* file_path) {
49 
50   ScopedFormatTrace atrace_protobuf_io_open(ATRACE_TAG_ACTIVITY_MANAGER,
51                                             "ProtobufIO::Open %s",
52                                             file_path);
53   android::base::Timer timer{};
54 
55   struct stat buf;
56   if (fstat(fd, /*out*/&buf) < 0) {
57     PLOG(ERROR) << "ProtobufIO: open error, fstat failed: " << file_path;
58     return nullptr;
59   }
60   // XX: off64_t for stat::st_size ?
61 
62   // Using the mmap appears to be the only way to do zero-copy with protobuf lite.
63   void* data = mmap(/*addr*/nullptr,
64                     buf.st_size,
65                     PROT_READ, MAP_SHARED | MAP_POPULATE,
66                     fd,
67                     /*offset*/0);
68   if (data == nullptr) {
69     PLOG(ERROR) << "ProtobufIO: open error, mmap failed: " << file_path;
70     return nullptr;
71   }
72 
73   ArenaPtr<proto::TraceFile> protobuf_trace_file = ArenaPtr<proto::TraceFile>::Make();
74   if (protobuf_trace_file == nullptr) {
75     LOG(ERROR) << "ProtobufIO: open error, failed to create arena: " << file_path;
76     return nullptr;
77   }
78 
79   google::protobuf::io::ArrayInputStream protobuf_input_stream{data, static_cast<int>(buf.st_size)};
80   if (!protobuf_trace_file->ParseFromZeroCopyStream(/*in*/&protobuf_input_stream)) {
81     // XX: Does protobuf on android already have the right LogHandler ?
82     LOG(ERROR) << "ProtobufIO: open error, protobuf parsing failed: " << file_path;
83     return nullptr;
84   }
85 
86   if (munmap(data, buf.st_size) < 0) {
87     PLOG(WARNING) << "ProtobufIO: open problem, munmap failed, possibly memory leak? "
88                   << file_path;
89   }
90 
91   LOG(VERBOSE) << "ProtobufIO: open succeeded: " << file_path << ", duration: " << timer;
92   return protobuf_trace_file;
93 }
94 
WriteFully(const::google::protobuf::MessageLite & message,std::string_view file_path)95 iorap::expected<size_t /*bytes written*/, int /*errno*/> ProtobufIO::WriteFully(
96     const ::google::protobuf::MessageLite& message,
97     std::string_view file_path) {
98 
99   std::string str{file_path};
100   android::base::unique_fd fd(TEMP_FAILURE_RETRY(
101       ::open(str.c_str(),
102              O_CREAT | O_TRUNC | O_RDWR,
103              S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)));  // ugo: rw-rw----
104   if (fd.get() < 0) {
105     int err = errno;
106     PLOG(ERROR) << "ProtobufIO: open failed: " << file_path;
107     return unexpected{err};
108   }
109 
110   return WriteFully(message, fd.get(), file_path);
111 }
112 
WriteFully(const::google::protobuf::MessageLite & message,int fd,std::string_view file_path)113 iorap::expected<size_t /*bytes written*/, int /*errno*/> ProtobufIO::WriteFully(
114     const ::google::protobuf::MessageLite& message,
115     int fd,
116     std::string_view file_path) {
117 
118   int byte_size = message.ByteSize();
119   if (byte_size < 0) {
120     DCHECK(false) << "Invalid protobuf size: " << byte_size;
121     LOG(ERROR) << "ProtobufIO: Invalid protobuf size: " << byte_size;
122     return unexpected{EDOM};
123   }
124   size_t serialized_size = static_cast<size_t>(byte_size);
125 
126   // Change the file to be exactly the length of the protobuf.
127   if (ftruncate(fd, static_cast<off_t>(serialized_size)) < 0) {
128     int err = errno;
129     PLOG(ERROR) << "ProtobufIO: ftruncate (size=" << serialized_size << ") failed";
130     return unexpected{err};
131   }
132 
133   // Using the mmap appears to be the only way to do zero-copy with protobuf lite.
134   void* data = mmap(/*addr*/nullptr,
135                     serialized_size,
136                     PROT_WRITE,
137                     MAP_SHARED,
138                     fd,
139                     /*offset*/0);
140   if (data == nullptr) {
141     int err = errno;
142     PLOG(ERROR) << "ProtobufIO: mmap failed: " << file_path;
143     return unexpected{err};
144   }
145 
146   // Zero-copy write from protobuf to file via memory-map.
147   ::google::protobuf::io::ArrayOutputStream output_stream{data, byte_size};
148   if (!message.SerializeToZeroCopyStream(/*inout*/&output_stream)) {
149     // This should never happen since we pre-allocated the file and memory map to be large
150     // enough to store the full protobuf.
151     DCHECK(false) << "ProtobufIO:: SerializeToZeroCopyStream failed despite precalculating size";
152     LOG(ERROR) << "ProtobufIO: SerializeToZeroCopyStream failed";
153     return unexpected{EXFULL};
154   }
155 
156   // Guarantee that changes are written back prior to munmap.
157   if (msync(data, static_cast<size_t>(serialized_size), MS_SYNC) < 0) {
158     int err = errno;
159     PLOG(ERROR) << "ProtobufIO: msync failed";
160     return unexpected{err};
161   }
162 
163   if (munmap(data, serialized_size) < 0) {
164     PLOG(WARNING) << "ProtobufIO: munmap failed, possibly memory leak? "
165                   << file_path;
166   }
167 
168   return serialized_size;
169 }
170 
171 }  // namespace serialize
172 }  // namespace iorap
173 
174