1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "src/trace_processor/importers/archive/tar_trace_reader.h"
18
19 #include <algorithm>
20 #include <array>
21 #include <cstddef>
22 #include <cstdint>
23 #include <cstring>
24 #include <memory>
25 #include <optional>
26 #include <string>
27 #include <utility>
28 #include <vector>
29
30 #include "perfetto/base/logging.h"
31 #include "perfetto/base/status.h"
32 #include "perfetto/ext/base/status_or.h"
33 #include "perfetto/ext/base/string_view.h"
34 #include "perfetto/trace_processor/trace_blob_view.h"
35 #include "src/trace_processor/forwarding_trace_parser.h"
36 #include "src/trace_processor/importers/archive/archive_entry.h"
37 #include "src/trace_processor/importers/common/trace_file_tracker.h"
38 #include "src/trace_processor/types/trace_processor_context.h"
39 #include "src/trace_processor/util/status_macros.h"
40 #include "src/trace_processor/util/trace_type.h"
41
42 namespace perfetto::trace_processor {
43 namespace {
44
45 constexpr char kUstarMagic[] = {'u', 's', 't', 'a', 'r', '\0'};
46 constexpr char kGnuMagic[] = {'u', 's', 't', 'a', 'r', ' ', ' ', '\0'};
47
48 constexpr char TYPE_FLAG_REGULAR = '0';
49 constexpr char TYPE_FLAG_AREGULAR = '\0';
50 constexpr char TYPE_FLAG_GNU_LONG_NAME = 'L';
51
52 template <size_t Size>
ExtractUint64(const char (& ptr)[Size])53 std::optional<uint64_t> ExtractUint64(const char (&ptr)[Size]) {
54 static_assert(Size <= 64 / 3);
55 if (*ptr == 0) {
56 return std::nullopt;
57 }
58 uint64_t value = 0;
59 for (size_t i = 0; i < Size && ptr[i] != 0; ++i) {
60 if (ptr[i] > '7' || ptr[i] < '0') {
61 return std::nullopt;
62 }
63 value <<= 3;
64 value += static_cast<uint64_t>(ptr[i] - '0');
65 }
66 return value;
67 }
68
69 enum class TarType { kUnknown, kUstar, kGnu };
70
71 struct alignas(1) Header {
72 char name[100];
73 char mode[8];
74 char uid[8];
75 char gid[8];
76 char size[12];
77 char mtime[12];
78 char checksum[8];
79 char type_flag[1];
80 char link_name[100];
81 union {
82 struct UstarMagic {
83 char magic[6];
84 char version[2];
85 } ustar;
86 char gnu[8];
87 } magic;
88 char user_name[32];
89 char group_name[32];
90 char dev_major[8];
91 char dev_minor[8];
92 char prefix[155];
93 char padding[12];
94
GetTarFileTypeperfetto::trace_processor::__anonbf72bd930111::Header95 TarType GetTarFileType() const {
96 if (memcmp(magic.gnu, kGnuMagic, sizeof(kGnuMagic)) == 0) {
97 return TarType::kGnu;
98 }
99 if (memcmp(magic.ustar.magic, kUstarMagic, sizeof(kUstarMagic)) == 0) {
100 return TarType::kUstar;
101 }
102 return TarType::kUnknown;
103 }
104 };
105
106 constexpr size_t kHeaderSize = 512;
107 static_assert(sizeof(Header) == kHeaderSize);
108
IsAllZeros(const TraceBlobView & data)109 bool IsAllZeros(const TraceBlobView& data) {
110 const uint8_t* start = data.data();
111 const uint8_t* end = data.data() + data.size();
112 return std::find_if(start, end, [](uint8_t v) { return v != 0; }) == end;
113 }
114
115 template <size_t Size>
ExtractString(const char (& start)[Size])116 std::string ExtractString(const char (&start)[Size]) {
117 const char* end = start + Size;
118 end = std::find(start, end, 0);
119 return std::string(start, end);
120 }
121
122 } // namespace
123
TarTraceReader(TraceProcessorContext * context)124 TarTraceReader::TarTraceReader(TraceProcessorContext* context)
125 : context_(context) {}
126
127 TarTraceReader::~TarTraceReader() = default;
128
Parse(TraceBlobView blob)129 util::Status TarTraceReader::Parse(TraceBlobView blob) {
130 ParseResult result = ParseResult::kOk;
131 buffer_.PushBack(std::move(blob));
132 while (!buffer_.empty() && result == ParseResult::kOk) {
133 switch (state_) {
134 case State::kMetadata:
135 case State::kZeroMetadata: {
136 ASSIGN_OR_RETURN(result, ParseMetadata());
137 break;
138 }
139 case State::kContent: {
140 ASSIGN_OR_RETURN(result, ParseContent());
141 break;
142 }
143 case State::kDone:
144 // We are done, ignore any more data
145 buffer_.PopFrontUntil(buffer_.end_offset());
146 }
147 }
148 return base::OkStatus();
149 }
150
NotifyEndOfFile()151 base::Status TarTraceReader::NotifyEndOfFile() {
152 if (state_ != State::kDone) {
153 return base::ErrStatus("Premature end of TAR file");
154 }
155
156 for (auto& file : ordered_files_) {
157 auto chunk_reader =
158 std::make_unique<ForwardingTraceParser>(context_, file.second.id);
159 auto& parser = *chunk_reader;
160 context_->chunk_readers.push_back(std::move(chunk_reader));
161
162 for (auto& data : file.second.data) {
163 RETURN_IF_ERROR(parser.Parse(std::move(data)));
164 }
165 RETURN_IF_ERROR(parser.NotifyEndOfFile());
166 // Make sure the ForwardingTraceParser determined the same trace type as we
167 // did.
168 PERFETTO_CHECK(parser.trace_type() == file.first.trace_type);
169 }
170
171 return base::OkStatus();
172 }
173
ParseMetadata()174 base::StatusOr<TarTraceReader::ParseResult> TarTraceReader::ParseMetadata() {
175 PERFETTO_CHECK(!metadata_.has_value());
176 auto blob = buffer_.SliceOff(buffer_.start_offset(), kHeaderSize);
177 if (!blob) {
178 return ParseResult::kNeedsMoreData;
179 }
180 buffer_.PopFrontBytes(kHeaderSize);
181 const Header& header = *reinterpret_cast<const Header*>(blob->data());
182
183 TarType type = header.GetTarFileType();
184
185 if (type == TarType::kUnknown) {
186 if (!IsAllZeros(*blob)) {
187 return base::ErrStatus("Invalid magic value");
188 }
189 // EOF is signaled by two consecutive zero headers.
190 if (state_ == State::kMetadata) {
191 // Fist time we see all zeros. NExt parser loop will enter ParseMetadata
192 // again and decide whether it is the real end or maybe a ral header
193 // comes.
194 state_ = State::kZeroMetadata;
195 } else {
196 // Previous header was zeros, thus we are done.
197 PERFETTO_CHECK(state_ == State::kZeroMetadata);
198 state_ = State::kDone;
199 }
200 return ParseResult::kOk;
201 }
202
203 if (type == TarType::kUstar && (header.magic.ustar.version[0] != '0' ||
204 header.magic.ustar.version[1] != '0')) {
205 return base::ErrStatus("Invalid version: %c%c",
206 header.magic.ustar.version[0],
207 header.magic.ustar.version[1]);
208 }
209
210 std::optional<uint64_t> size = ExtractUint64(header.size);
211 if (!size.has_value()) {
212 return base::ErrStatus("Failed to parse octal size field.");
213 }
214
215 metadata_.emplace();
216 metadata_->size = *size;
217 metadata_->type_flag = *header.type_flag;
218
219 if (long_name_) {
220 metadata_->name = std::move(*long_name_);
221 long_name_.reset();
222 } else {
223 metadata_->name =
224 ExtractString(header.prefix) + "/" + ExtractString(header.name);
225 }
226
227 switch (metadata_->type_flag) {
228 case TYPE_FLAG_REGULAR:
229 case TYPE_FLAG_AREGULAR:
230 case TYPE_FLAG_GNU_LONG_NAME:
231 state_ = State::kContent;
232 break;
233
234 default:
235 if (metadata_->size != 0) {
236 return base::ErrStatus("Unsupported file type: 0x%02x",
237 metadata_->type_flag);
238 }
239 state_ = State::kMetadata;
240 break;
241 }
242
243 return ParseResult::kOk;
244 }
245
ParseContent()246 base::StatusOr<TarTraceReader::ParseResult> TarTraceReader::ParseContent() {
247 PERFETTO_CHECK(metadata_.has_value());
248
249 size_t data_and_padding_size = base::AlignUp(metadata_->size, kHeaderSize);
250 if (buffer_.avail() < data_and_padding_size) {
251 return ParseResult::kNeedsMoreData;
252 }
253
254 if (metadata_->type_flag == TYPE_FLAG_GNU_LONG_NAME) {
255 TraceBlobView data =
256 *buffer_.SliceOff(buffer_.start_offset(), metadata_->size);
257 long_name_ = std::string(reinterpret_cast<const char*>(data.data()),
258 metadata_->size);
259 } else {
260 AddFile(*metadata_,
261 *buffer_.SliceOff(
262 buffer_.start_offset(),
263 std::min(static_cast<uint64_t>(512), metadata_->size)),
264 buffer_.MultiSliceOff(buffer_.start_offset(), metadata_->size));
265 }
266
267 buffer_.PopFrontBytes(data_and_padding_size);
268
269 metadata_.reset();
270 state_ = State::kMetadata;
271 return ParseResult::kOk;
272 }
273
AddFile(const Metadata & metadata,TraceBlobView header,std::vector<TraceBlobView> data)274 void TarTraceReader::AddFile(const Metadata& metadata,
275 TraceBlobView header,
276 std::vector<TraceBlobView> data) {
277 auto file_id = context_->trace_file_tracker->AddFile(metadata.name);
278 context_->trace_file_tracker->SetSize(file_id, metadata.size);
279 ordered_files_.emplace(
280 ArchiveEntry{metadata.name, ordered_files_.size(),
281 GuessTraceType(header.data(), header.size())},
282 File{file_id, std::move(data)});
283 }
284
285 } // namespace perfetto::trace_processor
286