| /* | 
 |  * Copyright (C) 2024 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | #include "src/trace_redaction/trace_redactor.h" | 
 |  | 
 | #include <cstddef> | 
 | #include <string> | 
 | #include <string_view> | 
 | #include <vector> | 
 |  | 
 | #include "perfetto/base/status.h" | 
 | #include "perfetto/ext/base/file_utils.h" | 
 | #include "perfetto/ext/base/scoped_file.h" | 
 | #include "perfetto/ext/base/scoped_mmap.h" | 
 | #include "perfetto/protozero/scattered_heap_buffer.h" | 
 | #include "perfetto/trace_processor/trace_blob.h" | 
 | #include "perfetto/trace_processor/trace_blob_view.h" | 
 | #include "src/trace_redaction/trace_redaction_framework.h" | 
 |  | 
 | #include "protos/perfetto/trace/trace.pbzero.h" | 
 |  | 
 | namespace perfetto::trace_redaction { | 
 |  | 
 | using Trace = protos::pbzero::Trace; | 
 | using TracePacket = protos::pbzero::TracePacket; | 
 |  | 
 | TraceRedactor::TraceRedactor() = default; | 
 |  | 
 | TraceRedactor::~TraceRedactor() = default; | 
 |  | 
 | base::Status TraceRedactor::Redact(std::string_view source_filename, | 
 |                                    std::string_view dest_filename, | 
 |                                    Context* context) const { | 
 |   const std::string source_filename_str(source_filename); | 
 |   base::ScopedMmap mapped = | 
 |       base::ReadMmapWholeFile(source_filename_str.c_str()); | 
 |   if (!mapped.IsValid()) { | 
 |     return base::ErrStatus("Failed to map pages for trace (%s)", | 
 |                            source_filename_str.c_str()); | 
 |   } | 
 |  | 
 |   trace_processor::TraceBlobView whole_view( | 
 |       trace_processor::TraceBlob::FromMmap(std::move(mapped))); | 
 |  | 
 |   if (auto status = Collect(context, whole_view); !status.ok()) { | 
 |     return status; | 
 |   } | 
 |  | 
 |   if (auto status = Build(context); !status.ok()) { | 
 |     return status; | 
 |   } | 
 |  | 
 |   if (auto status = Transform(*context, whole_view, std::string(dest_filename)); | 
 |       !status.ok()) { | 
 |     return status; | 
 |   } | 
 |  | 
 |   return base::OkStatus(); | 
 | } | 
 |  | 
 | base::Status TraceRedactor::Collect( | 
 |     Context* context, | 
 |     const trace_processor::TraceBlobView& view) const { | 
 |   // Mask, marking which collectors should be ran. When a collector no longer | 
 |   // needs to run, the value will be null. | 
 |   std::vector<const CollectPrimitive*> collectors; | 
 |   collectors.reserve(collectors_.size()); | 
 |  | 
 |   for (const auto& collector : collectors_) { | 
 |     collectors.push_back(collector.get()); | 
 |   } | 
 |  | 
 |   const Trace::Decoder trace_decoder(view.data(), view.length()); | 
 |  | 
 |   for (auto packet_it = trace_decoder.packet(); packet_it; ++packet_it) { | 
 |     const TracePacket::Decoder packet(packet_it->as_bytes()); | 
 |  | 
 |     for (auto cit = collectors.begin(); cit != collectors.end();) { | 
 |       auto status = (*cit)->Collect(packet, context); | 
 |  | 
 |       if (!status.ok()) { | 
 |         return status.status(); | 
 |       } | 
 |  | 
 |       // If this collector has returned `kStop`, it means that it (and it alone) | 
 |       // no longer needs to run. The driver (TraceRedactor) should not invoke it | 
 |       // on any future packets. | 
 |       if (status.value() == CollectPrimitive::ContinueCollection::kRetire) { | 
 |         cit = collectors.erase(cit); | 
 |       } else { | 
 |         ++cit; | 
 |       } | 
 |     } | 
 |  | 
 |     // If all the collectors have found what they were looking for, then there | 
 |     // is no reason to continue through the trace. | 
 |     if (collectors.empty()) { | 
 |       break; | 
 |     } | 
 |   } | 
 |  | 
 |   return base::OkStatus(); | 
 | } | 
 |  | 
 | base::Status TraceRedactor::Build(Context* context) const { | 
 |   for (const auto& builder : builders_) { | 
 |     if (auto status = builder->Build(context); !status.ok()) { | 
 |       return status; | 
 |     } | 
 |   } | 
 |  | 
 |   return base::OkStatus(); | 
 | } | 
 |  | 
 | base::Status TraceRedactor::Transform( | 
 |     const Context& context, | 
 |     const trace_processor::TraceBlobView& view, | 
 |     const std::string& dest_file) const { | 
 |   std::ignore = context; | 
 |   const auto dest_fd = base::OpenFile(dest_file, O_RDWR | O_CREAT, 0666); | 
 |  | 
 |   if (dest_fd.get() == -1) { | 
 |     return base::ErrStatus( | 
 |         "Failed to open destination file; can't write redacted trace."); | 
 |   } | 
 |  | 
 |   const Trace::Decoder trace_decoder(view.data(), view.length()); | 
 |   for (auto packet_it = trace_decoder.packet(); packet_it; ++packet_it) { | 
 |     auto packet = packet_it->as_std_string(); | 
 |  | 
 |     for (const auto& transformer : transformers_) { | 
 |       // If the packet has been cleared, it means a tranformation has removed it | 
 |       // from the trace. Stop processing it. This saves transforms from having | 
 |       // to check and handle empty packets. | 
 |       if (packet.empty()) { | 
 |         break; | 
 |       } | 
 |  | 
 |       if (auto status = transformer->Transform(context, &packet); | 
 |           !status.ok()) { | 
 |         return status; | 
 |       } | 
 |     } | 
 |  | 
 |     // The packet has been removed from the trace. Don't write an empty packet | 
 |     // to disk. | 
 |     if (packet.empty()) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     protozero::HeapBuffered<protos::pbzero::Trace> serializer; | 
 |     serializer->add_packet()->AppendRawProtoBytes(packet.data(), packet.size()); | 
 |     packet.assign(serializer.SerializeAsString()); | 
 |  | 
 |     if (const auto exported_data = | 
 |             base::WriteAll(dest_fd.get(), packet.data(), packet.size()); | 
 |         exported_data <= 0) { | 
 |       return base::ErrStatus("Failed to write redacted trace to disk"); | 
 |     } | 
 |   } | 
 |  | 
 |   return base::OkStatus(); | 
 | } | 
 |  | 
 | }  // namespace perfetto::trace_redaction |