| // Copyright 2024 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <optional> |
| #include <string> |
| |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/hash/md5.h> |
| #include <base/logging.h> |
| #include <brillo/flag_helper.h> |
| #include <brillo/proto_file_io.h> |
| |
| #include "patchmaker/directory_util.h" |
| #include "patchmaker/file_util.h" |
| #include "patchmaker/managed_directory.h" |
| |
| namespace { |
| |
| bool EncodeDirectory(const base::FilePath& src_path, |
| const base::FilePath& dest_path, |
| const std::string& input_manifest_str, |
| const std::string& immutable_path_str) { |
| ManagedDirectory managed_dir; |
| |
| if (!managed_dir.CreateNew(dest_path, |
| input_manifest_str.empty() |
| ? std::optional<base::FilePath>{} |
| : base::FilePath(input_manifest_str))) { |
| LOG(ERROR) << "Failed to initialize ManagedDirectory"; |
| return false; |
| } |
| |
| std::vector<base::FilePath> immutable_paths; |
| util::ParseDelimitedFilePaths(immutable_path_str, &immutable_paths); |
| for (const auto& path : immutable_paths) { |
| if (!base::PathExists(path)) { |
| LOG(ERROR) << "Path requesting immutability doesn't exist: " << path; |
| return false; |
| } |
| } |
| |
| if (!managed_dir.Encode(src_path, dest_path, immutable_paths)) { |
| LOG(ERROR) << "Failed to encode source path " << src_path; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Target path could be a single file or sub-directory within a managed |
| // directory |
| bool DecodeDirectory(const base::FilePath& target_path, |
| const base::FilePath& dest_path) { |
| ManagedDirectory managed_dir; |
| |
| if (!managed_dir.CreateFromExisting(target_path)) { |
| LOG(ERROR) << "Failed to initialize ManagedDirectory"; |
| return false; |
| } |
| if (!managed_dir.Decode(target_path, dest_path)) { |
| LOG(ERROR) << "Failed to decode target path " << target_path; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool EndToEndTest(const base::FilePath& src_path) { |
| // Take a directory as input, encode it, and then reconstruct it, |
| // ensuring that the reconstructed contents are identical to the |
| // originals. |
| |
| // Step 1 - Create temp directories for encode and decode |
| base::ScopedTempDir tmp_encode, tmp_decode; |
| if (!tmp_encode.CreateUniqueTempDir() || !tmp_decode.CreateUniqueTempDir()) { |
| LOG(ERROR) << "Failed to create temp directories for testing"; |
| return false; |
| } |
| |
| // Step 2 - Call EncodeDirectory from src_path to tmp_encode |
| LOG(INFO) << "Encoding into " << tmp_encode.GetPath(); |
| if (!EncodeDirectory(src_path, tmp_encode.GetPath(), "", "")) { |
| LOG(ERROR) << "Encode step failed"; |
| return false; |
| } |
| |
| // Step 3 - Call DecodeDirectory from tmp_encode to tmp_decode |
| LOG(INFO) << "Decoding into " << tmp_decode.GetPath(); |
| if (!DecodeDirectory(tmp_encode.GetPath(), tmp_decode.GetPath())) { |
| LOG(ERROR) << "Decode step failed"; |
| return false; |
| } |
| |
| // Step 4 - Ensure src_path and tmp_decode paths have identical contents |
| if (!util::DirectoriesAreEqual(src_path, tmp_decode.GetPath())) { |
| LOG(ERROR) << "Failed to validate equality after decode"; |
| return false; |
| } |
| |
| LOG(INFO) << "Src size " << ComputeDirectorySize(src_path) |
| << ", Encoded size " << ComputeDirectorySize(tmp_encode.GetPath()); |
| |
| LOG(INFO) << "All validation checks passed :)"; |
| return true; |
| } |
| |
| } // namespace |
| |
| int main(int argc, char* argv[]) { |
| DEFINE_bool(encode, false, "Generate patches to replace original files"); |
| DEFINE_bool(decode, false, "Reconstruct original files from patches"); |
| DEFINE_bool(end_to_end, false, "Encode, decode, and validate contents"); |
| |
| DEFINE_string(src_path, "", "Source path for operation"); |
| DEFINE_string(dest_path, "", "Destination path for encode operation"); |
| |
| DEFINE_string(input_manifest, "", "Optional: Input manifest for operation"); |
| DEFINE_string(immutable_paths, "", |
| "Optional: Colon (':') separated list of immutable files or " |
| "directories that must be left intact."); |
| |
| brillo::FlagHelper::Init(argc, argv, "Patch utility for binary storage."); |
| |
| int num_operations = FLAGS_encode + FLAGS_decode + FLAGS_end_to_end; |
| if (num_operations != 1) { |
| LOG(ERROR) << "Expected one of --encode / --decode / --end_to_end"; |
| return EXIT_FAILURE; |
| } |
| |
| if (FLAGS_src_path.empty()) { |
| LOG(ERROR) << "--src_path is required"; |
| return EXIT_FAILURE; |
| } |
| |
| if (FLAGS_end_to_end) { |
| return EndToEndTest(base::FilePath(FLAGS_src_path)) ? EXIT_SUCCESS |
| : EXIT_FAILURE; |
| } |
| |
| if (FLAGS_dest_path.empty()) { |
| LOG(ERROR) << "--dest_path is required"; |
| return EXIT_FAILURE; |
| } |
| |
| if (FLAGS_encode) { |
| return EncodeDirectory(base::FilePath(FLAGS_src_path), |
| base::FilePath(FLAGS_dest_path), |
| FLAGS_input_manifest, FLAGS_immutable_paths) |
| ? EXIT_SUCCESS |
| : EXIT_FAILURE; |
| } |
| |
| if (FLAGS_decode) { |
| return DecodeDirectory(base::FilePath(FLAGS_src_path), |
| base::FilePath(FLAGS_dest_path)) |
| ? EXIT_SUCCESS |
| : EXIT_FAILURE; |
| } |
| |
| NOTREACHED(); |
| } |