blob: 6dc72d3899114674b0ba5f0a272e1149befe309c [file] [log] [blame]
// Copyright 2021 Google LLC
//
// 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 <getopt.h>
#include <flasher/device.hpp>
#include <flashupdate/args.hpp>
#include <flashupdate/config.hpp>
#include <flashupdate/flash.hpp>
#include <flashupdate/info.hpp>
#include <flashupdate/validator.hpp>
#include <flashupdate/validator/cr51.hpp>
#include <stdplus/print.hpp>
#include <charconv>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
namespace
{
std::optional<uint8_t> getSecondaryIndex(std::string_view arg)
{
if (arg == "primary")
{
return std::nullopt;
}
if (!arg.starts_with("secondary/"))
{
throw std::runtime_error(
"FLASH_TYPE must be primary or secondary/<partition number>");
}
std::string_view indexStr =
arg.substr(std::string_view("secondary/").size());
uint32_t index;
auto [ptr, ec]{std::from_chars(indexStr.begin(), indexStr.end(), index)};
if (ec != std::errc())
{
throw std::runtime_error(
std::format("failed to convert string `{}` to uint32_t: {}",
indexStr, std::make_error_code(ec).message()));
}
if (ptr != indexStr.end())
{
throw std::runtime_error(
std::format("converted invalid characters: {}", indexStr));
}
return index;
}
} // namespace
namespace flashupdate
{
const std::unordered_map<std::string_view, Args::Op> stringToOp = {
{"hash_descriptor", Args::Op::HashDescriptor},
{"info", Args::Op::Info},
{"invalidate", Args::Op::Invalidate},
{"read", Args::Op::Read},
{"update_state", Args::Op::UpdateState},
{"update_version", Args::Op::UpdateVersion},
{"validate_config", Args::Op::ValidateConfig},
{"write", Args::Op::Write},
{"verify_staging", Args::Op::VerifyStaging},
{"fetch_version", Args::Op::FetchVersion},
{"copy_partition", Args::Op::CopyPartition},
{"erase", Args::Op::Erase},
};
void Args::setValidatorHelper(validator::Validator* validatorHelper)
{
this->validatorHelper = validatorHelper;
}
void Args::setFlashHelper(flash::Flash* flashHelper)
{
this->flashHelper = flashHelper;
}
Args::Args()
{
validatorHelperPtr = std::make_unique<validator::cr51::Cr51>();
validatorHelper = validatorHelperPtr.get();
flashHelperPtr = std::make_unique<flash::Flash>();
flashHelper = flashHelperPtr.get();
}
Args::Args(int argc, char* argv[])
{
static const char opts[] = ":j:i:acIfkSsvxz";
static const struct option longopts[] = {
{"active_version", no_argument, nullptr, 'a'},
{"clean_output", no_argument, nullptr, 'c'},
{"config", required_argument, nullptr, 'j'},
{"skip_staging_index_update", no_argument, nullptr, 'x'},
{"skip_validation", no_argument, nullptr, 'z'},
{"stage_state", no_argument, nullptr, 'S'},
{"staged_version", no_argument, nullptr, 's'},
{"staged_index", no_argument, nullptr, 'I'},
{"keep_mux", no_argument, nullptr, 'k'},
{"verbose", no_argument, nullptr, 'v'},
{nullptr, 0, nullptr, 0},
};
int c;
optind = 0;
while ((c = getopt_long(argc, argv, opts, longopts, nullptr)) > 0)
{
switch (c)
{
case 'a':
checkActiveVersion = true;
break;
case 's':
checkStagedVersion = true;
break;
case 'S':
checkStageState = true;
break;
case 'v':
verbose++;
break;
case 'c':
cleanOutput = true;
break;
case 'k':
keepMux = true;
break;
case 'I':
checkStagedIndex = true;
break;
case 'j':
configFile.emplace(optarg);
break;
case 'x':
updateStagingIndex = false;
break;
case 'z':
skipValidation = true;
break;
case ':':
throw std::runtime_error(
std::format("Missing argument for `{}`", argv[optind - 1]));
break;
default:
throw std::runtime_error(std::format(
"Invalid command line argument `{}`", argv[optind - 1]));
}
}
// Enabled all UpdateInfo output when using `Info` command
// If no specific targeted information, print out all avaliable information
// for general debugging purpose.
if (!checkActiveVersion && !checkStagedVersion && !checkStageState &&
!checkStagedIndex)
{
checkActiveVersion = true;
checkStagedVersion = true;
checkStageState = true;
checkStagedIndex = true;
otherInfo = true;
}
if (optind == argc)
{
throw std::runtime_error("Missing flashupdate operation");
}
auto it = stringToOp.find(argv[optind]);
if (it == stringToOp.end())
{
throw std::runtime_error(
std::format("Invalid operation: {}", argv[optind]));
}
optind++;
op = it->second;
std::optional<uint8_t> secondaryIndex = std::nullopt;
switch (op)
{
case Args::Op::ValidateConfig:
break;
case Args::Op::HashDescriptor:
printHelp = printHashDescriptorHelp;
if (optind + 1 != argc)
{
throw std::runtime_error("Must specify FILE");
}
file.emplace(argv[optind]);
break;
case Args::Op::Read:
printHelp = printReadHelp;
if (optind + 2 != argc)
{
throw std::runtime_error("Must specify FLASH_TYPE and FILE");
}
secondaryIndex = getSecondaryIndex(argv[optind]);
primary = !secondaryIndex.has_value();
stagingIndex = secondaryIndex.value_or(0);
file.emplace(argv[optind + 1]);
break;
case Args::Op::Write:
printHelp = printWriteHelp;
if (optind + 2 != argc)
{
throw std::runtime_error("Must specify FILE and FLASH_TYPE");
}
file.emplace(argv[optind]);
secondaryIndex = getSecondaryIndex(argv[optind + 1]);
primary = !secondaryIndex.has_value();
stagingIndex = secondaryIndex.value_or(0);
break;
case Args::Op::UpdateState:
printHelp = printUpdateStateHelp;
if (optind + 1 != argc)
{
throw std::runtime_error("Must specify STATE");
}
state = argv[optind];
break;
case Args::Op::UpdateVersion:
printHelp = printUpdateVersionHelp;
if (optind + 2 != argc)
{
throw std::runtime_error("Must specify VERSION and FLASH_TYPE");
}
version = argv[optind];
secondaryIndex = getSecondaryIndex(argv[optind + 1]);
primary = !secondaryIndex.has_value();
stagingIndex = secondaryIndex.value_or(0);
break;
case Args::Op::Info:
printHelp = printInfoHelp;
break;
case Args::Op::Invalidate:
printHelp = [this](const char* arg0) { printInvalidateHelp(arg0); };
if (optind + 1 != argc)
{
throw std::runtime_error("Must specify FLASH_TYPE");
}
secondaryIndex = getSecondaryIndex(argv[optind]);
primary = !secondaryIndex.has_value();
stagingIndex = secondaryIndex.value_or(0);
break;
case Args::Op::VerifyStaging:
printHelp = printVerifyStagingHelp;
break;
case Args::Op::FetchVersion:
printHelp = printFetchVersionHelp;
if (optind + 1 != argc)
{
throw std::runtime_error("Must specify FLASH_TYPE");
}
secondaryIndex = getSecondaryIndex(argv[optind]);
primary = !secondaryIndex.has_value();
stagingIndex = secondaryIndex.value_or(0);
break;
case Args::Op::CopyPartition:
printHelp = printCopyPartitionHelp;
if (optind + 2 != argc)
{
throw std::runtime_error("Must specify two FLASH_TYPEs");
}
fromPartition = getSecondaryIndex(argv[optind]);
toPartition = getSecondaryIndex(argv[optind + 1]);
if (fromPartition == toPartition)
{
throw std::runtime_error("From and to partition are the same.");
}
if (toPartition.has_value())
{
// Anything to Secondary
primary = false;
stagingIndex = toPartition.value();
}
else
{
// Secondary to Primary
primary = true;
stagingIndex = fromPartition.value();
}
break;
case Args::Op::Erase:
printHelp = printEraseHelp;
if (optind + 1 != argc)
{
throw std::runtime_error("Must specify the FLASH_TYPEs");
}
auto partition = getSecondaryIndex(argv[optind]);
if (!partition.has_value())
{
throw std::runtime_error(
"Erase command is not supported on primary partition.");
}
primary = false;
stagingIndex = *partition;
break;
};
config = createConfig(configFile, stagingIndex);
// The validatorHelper will be set to Cr51 for now.
if (config.cr51.has_value())
{
validatorHelperPtr =
std::make_unique<validator::cr51::Cr51>(*config.cr51);
validatorHelper = validatorHelperPtr.get();
}
// The flashHelper needs the config to be valid before setting it up.
flashHelperPtr = std::make_unique<flash::Flash>(config, keepMux);
flashHelper = flashHelperPtr.get();
}
void Args::printHashDescriptorHelp(const char* arg0)
{
stdplus::println(stderr, "Usage: {} [OPTION]... hash_descriptor FILE",
arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} hash_descriptor image.bin", arg0);
stdplus::println(stderr, "");
}
void Args::printUpdateStateHelp(const char* arg0)
{
stdplus::println(stderr, "Usage: {} [OPTION]... update_state STATE", arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "STATE options");
stdplus::println(stderr, "{}", info::listStates());
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} update_state STAGED", arg0);
stdplus::println(stderr, "");
}
void Args::printUpdateVersionHelp(const char* arg0)
{
stdplus::println(stderr,
"Usage: {} [OPTION]... update_version VERSION FLASH_TYPE",
arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "VERSION format");
stdplus::println(stderr, " `x.y.z.w`");
stdplus::println(stderr, "");
stdplus::println(stderr, "FLASH_TYPE options");
stdplus::println(stderr, " `primary`");
stdplus::println(stderr, " `secondary/<partition number>`");
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} update_version 10.10.10.0 secondary/0",
arg0);
stdplus::println(stderr, "");
}
void Args::printWriteHelp(const char* arg0)
{
stdplus::println(stderr, "Usage: {} [OPTION]... write FILE FLASH_TYPE",
arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "FLASH_TYPE options");
stdplus::println(stderr, " `primary`");
stdplus::println(stderr, " `secondary/<partition number>`");
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} write image.bin primary", arg0);
stdplus::println(stderr, "");
}
void Args::printReadHelp(const char* arg0)
{
stdplus::println(stderr, "Usage: {} [OPTION]... read FLASH_TYPE FILE",
arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "FLASH_TYPE options");
stdplus::println(stderr, " `primary`");
stdplus::println(stderr, " `secondary/<partition number>`");
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} read primary image.bin", arg0);
stdplus::println(stderr, "");
}
void Args::printVerifyStagingHelp(const char* arg0)
{
stdplus::println(stderr, "Usage: {} verify_staging", arg0);
stdplus::println(stderr, "");
stdplus::println(stderr,
"Verify the staging partition to make sure it matches the "
"version in metadata. ");
stdplus::println(stderr, "Ex: {} verify_staging", arg0);
stdplus::println(stderr, "");
}
void Args::printFetchVersionHelp(const char* arg0)
{
stdplus::println(stderr, "Usage: {} fetch_version", arg0);
stdplus::println(stderr, "");
stdplus::println(stderr,
"Read the CR51 descriptor of the partition and return "
"the validated version ");
stdplus::println(stderr, "FLASH_TYPE options");
stdplus::println(stderr, " `primary`");
stdplus::println(stderr, " `secondary/<partition number>`");
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} fetch_version", arg0);
stdplus::println(stderr, "");
}
void Args::printCopyPartitionHelp(const char* arg0)
{
stdplus::println(stderr, "Usage: {} copy_partition FLASH_TYPE", arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "Copy the firmware from one partition to another");
stdplus::println(stderr, "FLASH_TYPE options");
stdplus::println(stderr, " `primary`");
stdplus::println(stderr, " `secondary/<partition number>`");
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} copy_partition secondary/1 primary", arg0);
stdplus::println(stderr, "");
}
void Args::printEraseHelp(const char* arg0)
{
stdplus::println(stderr, "Usage: {} erase FLASH_TYPE", arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "Erase the target partition (staging only)");
stdplus::println(stderr, "FLASH_TYPE options");
stdplus::println(stderr, " `secondary/<partition number>`");
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} erase secondary/1", arg0);
stdplus::println(stderr, "");
}
void Args::printInfoHelp(const char* arg0)
{
stdplus::println(stderr, "Usage: {} [OPTION]... info", arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "Optional Arguments for `info` command:");
stdplus::println(stderr,
" -a, --active_version Print the active version with "
"the `info command");
stdplus::println(stderr,
" -s, --staged_version Print the stage version with "
"the `info` command");
stdplus::println(stderr, " -S, --stage_state Print the Stage "
"stage of the BIOS image.");
stdplus::println(stderr,
" -I, --staged_index Print the index of the lasted "
"staged secondary partition");
stdplus::println(stderr, " -c, --clean_output Print the `info` "
"message with no prefixes");
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} info -avS", arg0);
stdplus::println(stderr, "");
}
void Args::printInvalidateHelp(const char* arg0)
{
stdplus::println(stderr, "Usage: {} [OPTION]... invalidate FLASH_TYPE",
arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "FLASH_TYPE options");
stdplus::println(stderr, " `primary`");
stdplus::println(stderr, " `secondary`");
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} invalidate primary", arg0);
stdplus::println(stderr, "");
}
std::function<void(const char* arg0)> Args::printHelp = [](const char* arg0) {
stdplus::println(stderr, "Usage: {} [OPTION]... validate_config", arg0);
stdplus::println(stderr, " or: {} [OPTION]... read FLASH_TYPE FILE",
arg0);
stdplus::println(stderr, " or: {} [OPTION]... write FILE FLASH_TYPE",
arg0);
stdplus::println(stderr, " or: {} [OPTION]... update_state STATE", arg0);
stdplus::println(stderr,
" or: {} [OPTION]... update_version VERSION FLASH_TYPE",
arg0);
stdplus::println(stderr, " or: {} [OPTION]... info", arg0);
stdplus::println(stderr, " or: {} [OPTION]... hash_descriptor", arg0);
stdplus::println(stderr, " or: {} [OPTION]... verify_staging", arg0);
stdplus::println(stderr, " or: {} [OPTION]... invalidate FLASH_TYPE",
arg0);
stdplus::println(stderr, " or: {} [OPTION]... fetch_version FLASH_TYPE",
arg0);
stdplus::println(
stderr, " or: {} [OPTION]... copy_partition FLASH_TYPE FLASH_TYPE",
arg0);
stdplus::println(stderr, " or: {} [OPTION]... erase FLASH_TYPE", arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "Command Specifc Arguments:");
stdplus::println(stderr, " Run `{} $CMD` to see the command arguments",
arg0);
stdplus::println(stderr, "");
stdplus::println(stderr, "General Optional Arguments:");
stdplus::println(stderr, " -v, --verbose Increases the verbosity "
"level of error message output");
stdplus::println(stderr, " -j, --config[=JSON] Path for json config. "
"For example, `--config bios` will look for "
"/usr/share/flashupdate/bios.json. You can also "
"target specific config file with full path");
stdplus::println(stderr,
" -k, --keep_mux Keep the mux state to select "
"the current flash.");
stdplus::println(stderr, "");
stdplus::println(stderr, "Ex: {} validate_config", arg0);
stdplus::println(stderr, "Ex: {} read primary image.bin", arg0);
stdplus::println(stderr, "Ex: {} write image.bin primary", arg0);
stdplus::println(stderr, "Ex: {} update_state STAGED", arg0);
stdplus::println(stderr, "Ex: {} update_version 10.0.0.0 secondary/0",
arg0);
stdplus::println(stderr, "Ex: {} info", arg0);
stdplus::println(stderr, "Ex: {} hash_descriptor image.bin", arg0);
stdplus::println(stderr, "Ex: {} verify_staging", arg0);
stdplus::println(stderr, "Ex: {} invalidate secondary/0", arg0);
stdplus::println(stderr, "Ex: {} fetch_version secondary/1", arg0);
stdplus::println(stderr, "Ex: {} copy_partition secondary/1 secondary/0",
arg0);
stdplus::println(stderr, "Ex: {} erase secondary/1", arg0);
stdplus::println(stderr, "");
};
Args Args::argsOrHelp(int argc, char* argv[])
{
try
{
return Args(argc, argv);
}
catch (...)
{
printHelp(argv[0]);
throw;
}
}
} // namespace flashupdate