common/recipes-core/fw-util/files/fw-util.cpp (388 lines of code) (raw):
/*
* fw-util.cpp
*
* Copyright 2017-present Facebook. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <map>
#include <tuple>
#include <signal.h>
#include <syslog.h>
#include <atomic>
#include <openbmc/pal.h>
#include <openbmc/misc-utils.h>
#ifdef __TEST__
#include <gtest/gtest.h>
#endif
#include "fw-util.h"
#include "scheduler.h"
using namespace std;
std::atomic<bool> quit_process(false);
string exec_name = "Unknown";
map<string, map<string, Component *, partialLexCompare>, partialLexCompare> * Component::fru_list = NULL;
Component::Component(string fru, string component)
: _fru(fru), _component(component), _sys(), update_initiated(false)
{
fru_list_setup();
(*fru_list)[fru][component] = this;
}
Component::~Component()
{
(*fru_list)[_fru].erase(_component);
if ((*fru_list)[_fru].size() == 0) {
fru_list->erase(_fru);
}
// Clear the update-ongoing flag if we were the ones
// setting it.
if (update_initiated)
set_update_ongoing(0);
}
Component *Component::find_component(string fru, string comp)
{
if (!fru_list) {
return NULL;
}
if (fru_list->find(fru) == fru_list->end()) {
return NULL;
}
if ((*fru_list)[fru].find(comp) == (*fru_list)[fru].end()) {
return NULL;
}
return (*fru_list)[fru][comp];
}
AliasComponent::AliasComponent(string fru, string comp, string t_fru, string t_comp) :
Component(fru, comp), _target_fru(t_fru), _target_comp_name(t_comp), _target_comp(NULL)
{
// This might not be successful this early.
_target_comp = find_component(t_fru, t_comp);
}
bool AliasComponent::setup()
{
if (!_target_comp) {
_target_comp = find_component(_target_fru, _target_comp_name);
if (!_target_comp) {
return false;
}
}
return true;
}
int AliasComponent::update(string image)
{
if (!setup())
return FW_STATUS_NOT_SUPPORTED;
return _target_comp->update(image);
}
int AliasComponent::fupdate(string image)
{
if (!setup())
return FW_STATUS_NOT_SUPPORTED;
return _target_comp->fupdate(image);
}
int AliasComponent::dump(string image)
{
if (!setup())
return FW_STATUS_NOT_SUPPORTED;
return _target_comp->dump(image);
}
int AliasComponent::print_version()
{
if (!setup())
return FW_STATUS_NOT_SUPPORTED;
return _target_comp->print_version();
}
void AliasComponent::set_update_ongoing(int timeout)
{
if (setup())
_target_comp->set_update_ongoing(timeout);
}
bool AliasComponent::is_update_ongoing()
{
if (!setup())
return FW_STATUS_NOT_SUPPORTED;
return _target_comp->is_update_ongoing();
}
void fw_util_sig_handler(int signo)
{
quit_process.store(true);
cout << "Terminated requested. Waiting for earlier operation complete.\n";
syslog(LOG_DEBUG, "fw_util_sig_handler signo=%d requesting exit", signo);
}
void usage()
{
cout << "USAGE: " << exec_name << " all|FRU --version [all|COMPONENT]" << endl;
cout << " " << exec_name << " FRU --update [--]COMPONENT IMAGE_PATH" << endl;
cout << " " << exec_name << " FRU --force --update [--]COMPONENT IMAGE_PATH" << endl;
cout << " " << exec_name << " FRU --dump [--]COMPONENT IMAGE_PATH" << endl;
cout << " " << exec_name << " FRU --update COMPONENT IMAGE_PATH --schedule now" << endl;
cout << " " << exec_name << " all --show-schedule" << endl;
cout << " " << exec_name << " all --delete-schedule TASK_ID" << endl;
cout << endl;
cout << left << setw(10) << "FRU" << " : Components" << endl;
cout << "---------- : ----------" << endl;
for (auto fkv : *Component::fru_list) {
string fru_name = fkv.first;
cout << left << setw(10) << fru_name << " : ";
for (auto ckv : fkv.second) {
string comp_name = ckv.first;
Component *c = ckv.second;
if (comp_name.find(" ") == comp_name.npos) {
// No spaces in the component name, print as-is.
cout << comp_name;
} else {
// Add quotes to ensure there is no confusion from usage.
cout << "\"" << comp_name << "\"";
}
if (c->is_alias()) {
AliasComponent *a = (AliasComponent *)c;
cout << "(" << a->alias_fru() << ":" << a->alias_component() << ")";
}
cout << " ";
}
cout << endl;
}
}
int main(int argc, char *argv[])
{
int ret = 0;
int find_comp = 0;
int lfd;
struct sigaction sa;
System system;
Component::fru_list_setup();
#ifdef __TEST__
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
#endif
exec_name = argv[0];
if (argc < 3) {
usage();
return -1;
}
string fru(argv[1]);
string action(argv[2]);
string component("all");
string image("");
string sub_action("");
string time("");
string task_id("");
json json_array(nullptr);
bool add_task = false;
Scheduler tasker;
if (action == "--force") {
if (argc < 4) {
usage();
return -1;
}
string action_ext(argv[3]);
if (action_ext != "--update") {
usage();
return -1;
}
if (argc >= 5) {
component.assign(argv[4]);
if (component.compare(0, 2, "--") == 0) {
cerr << "WARNING: Passing component as " << component << " is deprecated" << endl;
component = component.substr(2);
cerr << " Use: --version|--update " << component << " instead" << endl;
}
}
} else {
if (argc >= 4) {
component.assign(argv[3]);
if (component.compare(0, 2, "--") == 0) {
cerr << "WARNING: Passing component as " << component << " is deprecated" << endl;
component = component.substr(2);
cerr << " Use: --version|--update " << component << " instead" << endl;
}
if ( argc == 7 ) {
sub_action.assign(argv[5]);
time.assign(argv[6]);
if (time != "now") { // only can set time to "now"
cerr << "Only can schedule update " << component << " now" << endl;
usage();
return -1;
}
} else {
task_id.assign(argv[3]);
}
}
}
if ((action == "--update") || (action == "--dump")) {
if (argc != 5 && argc != 7 ) {
usage();
return -1;
}
image.assign(argv[4]);
if (action == "--update" && image != "-") {
ifstream f(image);
if (!f.good()) {
cerr << "Cannot access: " << image << endl;
return -1;
}
}
if (component == "all") {
cerr << "Upgrading all components not supported" << endl;
return -1;
}
if ( sub_action.empty() == false ) {
if ( sub_action == "--schedule" ) {
add_task = true;
} else {
cerr << "Invalid action: " << sub_action << endl;
return -1;
}
}
} else if (action == "--force") {
if (argc != 6) {
usage();
return -1;
}
image.assign(argv[5]);
if (image != "-") {
ifstream f(image);
if (!f.good()) {
cerr << "Cannot access: " << image << endl;
return -1;
}
}
if (component == "all") {
cerr << "Upgrading all components not supported" << endl;
return -1;
}
} else if (action == "--version") {
if(argc > 4) {
usage();
return -1;
}
} else if (action == "--version-json" ) {
json_array = json::array();
} else if ( action == "--show-schedule" ) {
if (fru != "all") {
cerr << "Invalid fru: " << fru <<" for showing schedule" << endl;
usage();
return -1;
}
return tasker.show_task();
} else if ( action == "--delete-schedule" ) {
if (fru != "all") {
cerr << "Invalid fru: " << fru <<" for deleting schedule" << endl;
usage();
return -1;
}
return tasker.del_task(task_id);
} else {
cerr << "Invalid action: " << action << endl;
usage();
return -1;
}
sa.sa_handler = fw_util_sig_handler;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL); // for ssh terminate
//print the fw version or do the fw update when the fru and the comp are found
for (auto fkv : *Component::fru_list) {
if (fru == "all" || fru == fkv.first) {
for (auto ckv : fkv.second) {
string comp_name = ckv.first;
if (component == "all" || component == comp_name) {
find_comp = 1;
Component *c = ckv.second;
// Ignore aliases if their target is going to be
// considered in one of the loops.
if (c->is_alias()) {
if (component == "all" && (fru == "all" || fru == c->alias_fru()))
continue;
if (fru == "all" && component == c->alias_component())
continue;
}
// We are going to add a task but print fw version
// or do fw update.
if ( add_task == true ) {
return tasker.add_task(fru, component, image, time);
}
if (c->is_sled_cycle_initiated()) {
cerr << "Upgrade aborted due to fw update preparing" << endl;
return -1;
}
lfd = single_instance_lock_blocked(string("fw-util_"+c->fru()).c_str());
if (lfd < 0) {
syslog(LOG_WARNING, "Error getting single_instance_lock");
}
if (c->is_update_ongoing()) {
cerr << "Upgrade aborted due to ongoing upgrade on FRU: " << c->fru() << endl;
single_instance_unlock(lfd);
return -1;
}
if (action.rfind("--version", 0) == string::npos && fru != "all") {
// update or dump
c->set_update_ongoing(60 * 10);
}
single_instance_unlock(lfd);
if (action == "--version") {
ret = c->print_version();
if (ret != FW_STATUS_SUCCESS && ret != FW_STATUS_NOT_SUPPORTED) {
cerr << "Error getting version of " << c->component()
<< " on fru: " << c->fru() << endl;
}
} else if ( action == "--version-json" ) {
json j_object = {{"FRU", c->fru()}, {"COMPONENT", c->component()}};
c->get_version(j_object);
json_array.push_back(j_object);
} else { // update or dump
if (fru == "all") {
usage();
return -1;
}
string str_act("");
// ensure the shutdown (reboot) will not be execute during update
if (system.wait_shutdown_non_executable(2)) {
// the permission of shutdown command not changed
// add warning message to syslog here, and continue the update process
syslog(LOG_WARNING, "fw-util: shutdown command can still be executed after 2 seconds waiting");
}
if (system.is_reboot_ongoing()) {
cout << "Aborted action due to reboot ongoing" << endl;
c->set_update_ongoing(0);
return -1;
}
if (action == "--update") {
if (image != "-") {
ret = c->update(image);
} else {
ret = c->update(0, false /* force */);
}
str_act.assign("Upgrade");
} else if (action == "--force") {
if (image != "-") {
ret = c->fupdate(image);
} else {
ret = c->update(0 /* fd */, true /* force */);
}
str_act.assign("Force upgrade");
} else {
ret = c->dump(image);
str_act.assign("Dump");
}
c->set_update_ongoing(0);
if (ret == 0) {
cout << str_act << " of " << c->fru() << " : " << component << " succeeded" << endl;
c->update_finish();
} else {
cerr << str_act << " of " << c->fru() << " : " << component;
if (ret == FW_STATUS_NOT_SUPPORTED) {
cerr << " not supported" << endl;
} else {
cerr << " failed" << endl;
}
return -1;
}
}
if (quit_process.load()) {
syslog(LOG_DEBUG, "fw-util: Terminate request handled");
cout << "Aborted action due to signal\n";
return -1;
}
}
}
}
}
if (!find_comp) {
usage();
return -1;
}
if ( action == "--version-json" ) {
cout << json_array.dump(4) << endl;
}
return 0;
}