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 constexpr char TYPE_FLAG_DIR = '5';
52
53 template <size_t Size>
ParseBase256(const char (& ptr)[Size])54 base::StatusOr<uint64_t> ParseBase256(const char (&ptr)[Size]) {
55 if ((ptr[0] & 0x40) != 0) {
56 return base::ErrStatus(
57 "Negative size in base-256 encoding is not supported.");
58 }
59
60 // Skip leading null bytes after the first byte (base-256 indicator)
61 size_t start = 1;
62 while (start < Size && ptr[start] == 0) {
63 ++start;
64 }
65
66 // Calculate the effective size of the remaining significant bytes
67 size_t effective_size = Size - start;
68 if (effective_size > sizeof(uint64_t)) {
69 return base::ErrStatus("Base-256 value exceeds uint64_t range.");
70 }
71
72 // Accumulate the value directly from the remaining bytes
73 uint64_t value = 0;
74 for (size_t i = start; i < Size; ++i) {
75 value = (value << 8) | static_cast<uint8_t>(ptr[i]);
76 }
77
78 return value;
79 }
80
81 template <size_t Size>
ParseOctal(const char (& ptr)[Size])82 base::StatusOr<uint64_t> ParseOctal(const char (&ptr)[Size]) {
83 uint64_t value = 0;
84 for (size_t i = 0; i < Size && ptr[i] != 0; ++i) {
85 if (ptr[i] > '7' || ptr[i] < '0') {
86 return base::ErrStatus("Invalid octal digit in size field.");
87 }
88 value = (value << 3) + static_cast<uint64_t>(ptr[i] - '0');
89 }
90
91 return value;
92 }
93
94 template <size_t Size>
ExtractUint64(const char (& ptr)[Size])95 base::StatusOr<uint64_t> ExtractUint64(const char (&ptr)[Size]) {
96 static_assert(Size <= 64 / 3);
97
98 if (*ptr == 0) {
99 return base::ErrStatus("Size field is empty or zero.");
100 }
101
102 // Detect and handle base-256 encoding
103 if ((ptr[0] & 0x80) != 0) {
104 return ParseBase256(ptr);
105 }
106
107 // Handle standard octal parsing
108 return ParseOctal(ptr);
109 }
110
111 enum class TarType { kUnknown, kUstar, kGnu };
112
113 struct alignas(1) Header {
114 char name[100];
115 char mode[8];
116 char uid[8];
117 char gid[8];
118 char size[12];
119 char mtime[12];
120 char checksum[8];
121 char type_flag[1];
122 char link_name[100];
123 union {
124 struct UstarMagic {
125 char magic[6];
126 char version[2];
127 } ustar;
128 char gnu[8];
129 } magic;
130 char user_name[32];
131 char group_name[32];
132 char dev_major[8];
133 char dev_minor[8];
134 char prefix[155];
135 char padding[12];
136
GetTarFileTypeperfetto::trace_processor::__anonc05c48130111::Header137 TarType GetTarFileType() const {
138 if (memcmp(magic.gnu, kGnuMagic, sizeof(kGnuMagic)) == 0) {
139 return TarType::kGnu;
140 }
141 if (memcmp(magic.ustar.magic, kUstarMagic, sizeof(kUstarMagic)) == 0) {
142 return TarType::kUstar;
143 }
144 return TarType::kUnknown;
145 }
146 };
147
148 constexpr size_t kHeaderSize = 512;
149 static_assert(sizeof(Header) == kHeaderSize);
150
IsAllZeros(const TraceBlobView & data)151 bool IsAllZeros(const TraceBlobView& data) {
152 const uint8_t* start = data.data();
153 const uint8_t* end = data.data() + data.size();
154 return std::find_if(start, end, [](uint8_t v) { return v != 0; }) == end;
155 }
156
157 template <size_t Size>
ExtractString(const char (& start)[Size])158 std::string ExtractString(const char (&start)[Size]) {
159 const char* end = start + Size;
160 end = std::find(start, end, 0);
161 return std::string(start, end);
162 }
163
164 } // namespace
165
TarTraceReader(TraceProcessorContext * context)166 TarTraceReader::TarTraceReader(TraceProcessorContext* context)
167 : context_(context) {}
168
169 TarTraceReader::~TarTraceReader() = default;
170
Parse(TraceBlobView blob)171 base::Status TarTraceReader::Parse(TraceBlobView blob) {
172 ParseResult result = ParseResult::kOk;
173 buffer_.PushBack(std::move(blob));
174 while (!buffer_.empty() && result == ParseResult::kOk) {
175 switch (state_) {
176 case State::kMetadata:
177 case State::kZeroMetadata: {
178 ASSIGN_OR_RETURN(result, ParseMetadata());
179 break;
180 }
181 case State::kContent: {
182 ASSIGN_OR_RETURN(result, ParseContent());
183 break;
184 }
185 case State::kDone:
186 // We are done, ignore any more data
187 buffer_.PopFrontUntil(buffer_.end_offset());
188 }
189 }
190 return base::OkStatus();
191 }
192
NotifyEndOfFile()193 base::Status TarTraceReader::NotifyEndOfFile() {
194 if (state_ != State::kDone) {
195 return base::ErrStatus("Premature end of TAR file");
196 }
197
198 for (auto& file : ordered_files_) {
199 auto chunk_reader =
200 std::make_unique<ForwardingTraceParser>(context_, file.second.id);
201 auto& parser = *chunk_reader;
202 context_->chunk_readers.push_back(std::move(chunk_reader));
203
204 for (auto& data : file.second.data) {
205 RETURN_IF_ERROR(parser.Parse(std::move(data)));
206 }
207 RETURN_IF_ERROR(parser.NotifyEndOfFile());
208 // Make sure the ForwardingTraceParser determined the same trace type as we
209 // did.
210 PERFETTO_CHECK(parser.trace_type() == file.first.trace_type);
211 }
212
213 return base::OkStatus();
214 }
215
ParseMetadata()216 base::StatusOr<TarTraceReader::ParseResult> TarTraceReader::ParseMetadata() {
217 PERFETTO_CHECK(!metadata_.has_value());
218 auto blob = buffer_.SliceOff(buffer_.start_offset(), kHeaderSize);
219 if (!blob) {
220 return ParseResult::kNeedsMoreData;
221 }
222 buffer_.PopFrontBytes(kHeaderSize);
223 const Header& header = *reinterpret_cast<const Header*>(blob->data());
224
225 TarType type = header.GetTarFileType();
226
227 if (type == TarType::kUnknown) {
228 if (!IsAllZeros(*blob)) {
229 return base::ErrStatus("Invalid magic value");
230 }
231 // EOF is signaled by two consecutive zero headers.
232 if (state_ == State::kMetadata) {
233 // Fist time we see all zeros. NExt parser loop will enter ParseMetadata
234 // again and decide whether it is the real end or maybe a ral header
235 // comes.
236 state_ = State::kZeroMetadata;
237 } else {
238 // Previous header was zeros, thus we are done.
239 PERFETTO_CHECK(state_ == State::kZeroMetadata);
240 state_ = State::kDone;
241 }
242 return ParseResult::kOk;
243 }
244
245 if (*header.type_flag == TYPE_FLAG_DIR) {
246 return ParseResult::kOk;
247 }
248
249 if (type == TarType::kUstar && (header.magic.ustar.version[0] != '0' ||
250 header.magic.ustar.version[1] != '0')) {
251 return base::ErrStatus("Invalid version: %c%c",
252 header.magic.ustar.version[0],
253 header.magic.ustar.version[1]);
254 }
255
256 auto size = ExtractUint64(header.size);
257 if (!size.ok()) {
258 return base::ErrStatus("Failed to parse size field: %s",
259 size.status().message().c_str());
260 }
261
262 metadata_.emplace();
263 metadata_->size = size.value();
264 metadata_->type_flag = *header.type_flag;
265
266 if (long_name_) {
267 metadata_->name = std::move(*long_name_);
268 long_name_.reset();
269 } else {
270 metadata_->name =
271 ExtractString(header.prefix) + "/" + ExtractString(header.name);
272 }
273
274 switch (metadata_->type_flag) {
275 case TYPE_FLAG_REGULAR:
276 case TYPE_FLAG_AREGULAR:
277 case TYPE_FLAG_GNU_LONG_NAME:
278 state_ = State::kContent;
279 break;
280
281 default:
282 if (metadata_->size != 0) {
283 return base::ErrStatus("Unsupported file type: 0x%02x",
284 metadata_->type_flag);
285 }
286 state_ = State::kMetadata;
287 break;
288 }
289
290 return ParseResult::kOk;
291 }
292
ParseContent()293 base::StatusOr<TarTraceReader::ParseResult> TarTraceReader::ParseContent() {
294 PERFETTO_CHECK(metadata_.has_value());
295
296 size_t data_and_padding_size = base::AlignUp(metadata_->size, kHeaderSize);
297 if (buffer_.avail() < data_and_padding_size) {
298 return ParseResult::kNeedsMoreData;
299 }
300
301 if (metadata_->type_flag == TYPE_FLAG_GNU_LONG_NAME) {
302 TraceBlobView data =
303 *buffer_.SliceOff(buffer_.start_offset(), metadata_->size);
304 long_name_ = std::string(reinterpret_cast<const char*>(data.data()),
305 metadata_->size);
306 } else {
307 AddFile(*metadata_,
308 *buffer_.SliceOff(
309 buffer_.start_offset(),
310 std::min(static_cast<uint64_t>(512), metadata_->size)),
311 buffer_.MultiSliceOff(buffer_.start_offset(), metadata_->size));
312 }
313
314 buffer_.PopFrontBytes(data_and_padding_size);
315
316 metadata_.reset();
317 state_ = State::kMetadata;
318 return ParseResult::kOk;
319 }
320
AddFile(const Metadata & metadata,TraceBlobView header,std::vector<TraceBlobView> data)321 void TarTraceReader::AddFile(const Metadata& metadata,
322 TraceBlobView header,
323 std::vector<TraceBlobView> data) {
324 auto file_id = context_->trace_file_tracker->AddFile(metadata.name);
325 context_->trace_file_tracker->SetSize(file_id, metadata.size);
326 ordered_files_.emplace(
327 ArchiveEntry{metadata.name, ordered_files_.size(),
328 GuessTraceType(header.data(), header.size())},
329 File{file_id, std::move(data)});
330 }
331
332 } // namespace perfetto::trace_processor
333