| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extract-metadata.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/zx/time.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <cstdint> |
| #include <filesystem> |
| #include <iomanip> |
| #include <memory> |
| #include <optional> |
| #include <sstream> |
| #include <string> |
| |
| #include <fbl/unique_fd.h> |
| |
| #include "src/storage/extractor/c/extractor.h" |
| #include "src/storage/extractor/cpp/extractor.h" |
| #include "src/storage/extractor/cpp/hex_dump_generator.h" |
| #include "src/storage/minfs/format.h" |
| |
| namespace devmgr { |
| namespace { |
| |
| void Dump(fbl::unique_fd image_fd, DumpMetadataOptions& dump_options) { |
| lseek(image_fd.get(), 0, SEEK_SET); |
| extractor::HexDumpGeneratorOptions options = { |
| .tag = dump_options.tag, |
| .bytes_per_line = dump_options.bytes_per_line, |
| .dump_offset = true, |
| .dump_checksum = true, |
| }; |
| auto generator_or = extractor::HexDumpGenerator::Create(std::move(image_fd), options); |
| |
| if (generator_or.is_error()) { |
| FX_LOGS(ERROR) << "Failed to create hex dump generator: " |
| << zx_status_get_string(generator_or.error_value()); |
| return; |
| } |
| |
| auto generator = std::move(generator_or.value()); |
| |
| // Force start dump on a new line. FX_LOGS(ERROR) prints component name, file path and line number |
| // along with the message. This might wrap the log around and make it difficult to grep. So we add |
| // a new line here. |
| std::string buffer = "\n"; |
| while (!generator->Done()) { |
| auto line_or = generator->GetNextLine(); |
| if (line_or.is_error()) { |
| // Dump whatever we read so far and return. |
| FX_LOGS(ERROR) << buffer; |
| FX_LOGS(ERROR) << "Failed to get hex dump line" |
| << zx_status_get_string(line_or.error_value()); |
| return; |
| } |
| buffer.append(line_or.value()); |
| if (buffer.length() > dump_options.stream_buffer_size) { |
| FX_LOGS(ERROR) << buffer; |
| // Force start dump on a new line. FX_LOGS(ERROR) prints component name, file path and line |
| // number along with the message. This might wrap the log around and make it difficult to |
| // grep. So we add a new line here. |
| buffer = "\n"; |
| } |
| } |
| |
| if (buffer.length() > 1) { |
| FX_LOGS(ERROR) << buffer; |
| } |
| } |
| |
| } // namespace |
| |
| bool ExtractMetadataEnabled() { return true; } |
| |
| void MaybeDumpMetadata(fbl::unique_fd device_fd, DumpMetadataOptions options) { |
| // At the moment, extraction is supported only for minfs. |
| ZX_ASSERT(options.disk_format == DISK_FORMAT_MINFS); |
| if (options.bytes_per_line <= 0) { |
| FX_LOGS(ERROR) << "Invalid bytes_per_line:" << options.bytes_per_line << std::endl; |
| return; |
| } |
| |
| if (options.stream_buffer_size <= 0) { |
| FX_LOGS(ERROR) << "Invalid stream_buffer_size:" << options.stream_buffer_size << std::endl; |
| return; |
| } |
| |
| if (!device_fd) { |
| FX_LOGS(ERROR) << "Invalid device for extractor" << std::endl; |
| return; |
| } |
| |
| std::string tmp_path = "/fs/tmp/extracted_image_XXXXXX"; |
| fbl::unique_fd output_stream(mkstemp(tmp_path.data())); |
| if (!output_stream) { |
| FX_LOGS(ERROR) << "Failed to create image file: " << tmp_path; |
| return; |
| } |
| |
| ExtractorOptions extractor_options{ |
| .force_dump_pii = false, |
| .add_checksum = false, |
| .alignment = minfs::kMinfsBlockSize, |
| // TODO(fxbug.dev/67782): Enable compression. |
| .compress = false, |
| }; |
| |
| auto extractor_or = extractor::Extractor::Create(device_fd.duplicate(), extractor_options, |
| output_stream.duplicate()); |
| |
| if (extractor_or.is_error()) { |
| FX_LOGS(ERROR) << "Failed to create extractor: " |
| << zx_status_get_string(extractor_or.error_value()); |
| return; |
| } |
| |
| if (auto status = extractor::MinfsExtract(std::move(device_fd), *extractor_or.value()); |
| status.is_error()) { |
| FX_LOGS(ERROR) << "Failed to extract: " << zx_status_get_string(status.error_value()); |
| return; |
| } |
| if (auto status = extractor_or.value()->Write(); status.is_error()) { |
| FX_LOGS(ERROR) << "Failed to write to the extracted file: " |
| << zx_status_get_string(status.error_value()); |
| return; |
| } |
| |
| // Wait for all other component to stop writing to logs. This is not fool proof but helps to |
| // cluster logs together and decreases the chances of dropping logs. |
| zx::nanosleep(zx::deadline_after(options.log_settle_time)); |
| |
| FX_LOGS(ERROR) << std::endl |
| << options.tag << ": Extracting minfs to serial." << std::endl |
| << options.tag << ": Following lines that start with \"" << options.tag |
| << "\" are from extractor." << std::endl |
| << options.tag << ": Successful extraction ends with \"" << options.tag |
| << ": Done extracting minfs to serial.\"" << std::endl |
| << options.tag << ": Compression:" << (extractor_options.compress ? "on" : "off") |
| << " Checksum:on Offset:on bytes_per_line:" << options.bytes_per_line << std::endl; |
| |
| Dump(std::move(output_stream), options); |
| |
| FX_LOGS(ERROR) << std::endl << options.tag << ": Done extracting minfs to serial" << std::endl; |
| // Wait for all the logs we have written to get flushed. This is not fool proof but helps to |
| // cluster logs together and decreases the chances of hex-dump logs interleaving with other logs. |
| zx::nanosleep(zx::deadline_after(options.log_settle_time)); |
| } |
| |
| } // namespace devmgr |