blob: 5823a511256f633003c441f073c62b36f01b504c [file] [log] [blame]
// 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();
}