• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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