xref: /aosp_15_r20/external/perfetto/src/trace_processor/importers/archive/tar_trace_reader.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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