Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
main.cc
1 #include <unistd.h>
2 #include <iostream>
3 
4 #include <boost/filesystem.hpp>
5 #include <boost/program_options.hpp>
6 
7 #include "config/config.h"
8 #include "helpers.h"
9 #include "utilities/aktualizr_version.h"
10 
11 namespace bpo = boost::program_options;
12 
13 static void log_info_target(const std::string &prefix, const Config &config, const Uptane::Target &t) {
14  auto name = t.filename();
15  if (t.custom_version().length() > 0) {
16  name = t.custom_version();
17  }
18  LOG_INFO << prefix + name << "\tsha256:" << t.sha256Hash();
19  if (config.pacman.type == PACKAGE_MANAGER_OSTREEDOCKERAPP) {
20  bool shown = false;
21  auto apps = t.custom_data()["docker_apps"];
22  for (Json::ValueIterator i = apps.begin(); i != apps.end(); ++i) {
23  if (!shown) {
24  shown = true;
25  LOG_INFO << "\tDocker Apps:";
26  }
27  if ((*i).isObject() && (*i).isMember("filename")) {
28  LOG_INFO << "\t\t" << i.key().asString() << " -> " << (*i)["filename"].asString();
29  }
30  if ((*i).isObject() && (*i).isMember("uri")) {
31  LOG_INFO << "\t\t" << i.key().asString() << " -> " << (*i)["uri"].asString();
32  }
33  }
34  }
35 }
36 
37 static int status_main(LiteClient &client, const bpo::variables_map &unused) {
38  (void)unused;
39  auto target = client.primary->getCurrent();
40 
41  if (target.MatchTarget(Uptane::Target::Unknown())) {
42  LOG_INFO << "No active deployment found";
43  } else {
44  auto name = target.filename();
45  if (target.custom_version().length() > 0) {
46  name = target.custom_version();
47  }
48  log_info_target("Active image is: ", client.config, target);
49  }
50  return 0;
51 }
52 
53 static int list_main(LiteClient &client, const bpo::variables_map &unused) {
54  (void)unused;
55  Uptane::HardwareIdentifier hwid(client.config.provision.primary_ecu_hardware_id);
56 
57  LOG_INFO << "Refreshing Targets metadata";
58  try {
59  client.primary->updateImageMeta();
60  } catch (const std::exception &e) {
61  LOG_WARNING << "Unable to update latest metadata, using local copy";
62  try {
63  client.primary->checkImageMetaOffline();
64  } catch (const std::exception &e2) {
65  LOG_ERROR << "Unable to use local copy of TUF data";
66  return 1;
67  }
68  }
69 
70  LOG_INFO << "Updates available to " << hwid << ":";
71  for (auto &t : client.primary->allTargets()) {
72  for (auto const &it : t.hardwareIds()) {
73  if (it == hwid) {
74  log_info_target("", client.config, t);
75  break;
76  }
77  }
78  }
79  return 0;
80 }
81 
82 static std::unique_ptr<Uptane::Target> find_target(const std::shared_ptr<SotaUptaneClient> &client,
83  Uptane::HardwareIdentifier &hwid, const std::string &version) {
84  std::unique_ptr<Uptane::Target> rv;
85  try {
86  client->updateImageMeta();
87  } catch (const std::exception &e) {
88  LOG_WARNING << "Unable to update latest metadata, using local copy";
89  try {
90  client->checkImageMetaOffline();
91  } catch (const std::exception &e2) {
92  throw std::runtime_error("Unable to find update");
93  }
94  }
95 
96  bool find_latest = (version == "latest");
97  std::unique_ptr<Uptane::Target> latest = nullptr;
98  for (auto &t : client->allTargets()) {
99  for (auto const &it : t.hardwareIds()) {
100  if (it == hwid) {
101  if (find_latest) {
102  if (latest == nullptr || Version(latest->custom_version()) < Version(t.custom_version())) {
103  latest = std_::make_unique<Uptane::Target>(t);
104  }
105  } else if (version == t.filename() || version == t.custom_version()) {
106  return std_::make_unique<Uptane::Target>(t);
107  }
108  }
109  }
110  }
111  if (find_latest && latest != nullptr) {
112  return latest;
113  }
114  throw std::runtime_error("Unable to find update");
115 }
116 
117 static int do_update(LiteClient &client, Uptane::Target &target) {
118  target.InsertEcu(client.primary_ecu);
119  if (!client.primary->downloadImage(target).first) {
120  return 1;
121  }
122 
123  if (client.primary->VerifyTarget(target) != TargetStatus::kGood) {
124  LOG_ERROR << "Downloaded target is invalid";
125  return 1;
126  }
127 
128  auto iresult = client.primary->PackageInstall(target);
129  if (iresult.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
130  LOG_INFO << "Update complete. Please reboot the device to activate";
131  client.storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kPending);
132  } else if (iresult.result_code.num_code == data::ResultCode::Numeric::kOk) {
133  client.storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kCurrent);
134  } else {
135  LOG_ERROR << "Unable to install update: " << iresult.description;
136  return 1;
137  }
138  LOG_INFO << iresult.description;
139  return 0;
140 }
141 
142 static int update_main(LiteClient &client, const bpo::variables_map &variables_map) {
143  Uptane::HardwareIdentifier hwid(client.config.provision.primary_ecu_hardware_id);
144 
145  std::string version("latest");
146  if (variables_map.count("update-name") > 0) {
147  version = variables_map["update-name"].as<std::string>();
148  }
149  LOG_INFO << "Finding " << version << " to update to...";
150  auto target = find_target(client.primary, hwid, version);
151  LOG_INFO << "Updating to: " << *target;
152  return do_update(client, *target);
153 }
154 
155 struct SubCommand {
156  const char *name;
157  int (*main)(LiteClient &, const bpo::variables_map &);
158 };
159 static std::array<SubCommand, 3> commands{{{"status", status_main}, {"list", list_main}, {"update", update_main}}};
160 
161 void check_info_options(const bpo::options_description &description, const bpo::variables_map &vm) {
162  if (vm.count("help") != 0 || (vm.count("command") == 0 && vm.count("version") == 0)) {
163  std::cout << description << '\n';
164  exit(EXIT_SUCCESS);
165  }
166  if (vm.count("version") != 0) {
167  std::cout << "Current aktualizr version is: " << aktualizr_version() << "\n";
168  exit(EXIT_SUCCESS);
169  }
170 }
171 
172 bpo::variables_map parse_options(int argc, char **argv) {
173  std::string subs("Command to execute: ");
174  for (size_t i = 0; i < commands.size(); i++) {
175  if (i != 0) {
176  subs += ", ";
177  }
178  subs += commands[i].name;
179  }
180  bpo::options_description description("aktualizr-lite command line options");
181  // clang-format off
182  // Try to keep these options in the same order as Config::updateFromCommandLine().
183  // The first three are commandline only.
184  description.add_options()
185  ("help,h", "print usage")
186  ("version,v", "Current aktualizr version")
187  ("config,c", bpo::value<std::vector<boost::filesystem::path> >()->composing(), "configuration file or directory")
188  ("loglevel", bpo::value<int>(), "set log level 0-5 (trace, debug, info, warning, error, fatal)")
189  ("repo-server", bpo::value<std::string>(), "URL of the Uptane Image repository")
190  ("primary-ecu-hardware-id", bpo::value<std::string>(), "hardware ID of Primary ecu")
191  ("update-name", bpo::value<std::string>(), "optional name of the update when running \"update\". default=latest")
192  ("command", bpo::value<std::string>(), subs.c_str());
193  // clang-format on
194 
195  // consider the first positional argument as the aktualizr run mode
196  bpo::positional_options_description pos;
197  pos.add("command", 1);
198 
199  bpo::variables_map vm;
200  std::vector<std::string> unregistered_options;
201  try {
202  bpo::basic_parsed_options<char> parsed_options =
203  bpo::command_line_parser(argc, argv).options(description).positional(pos).allow_unregistered().run();
204  bpo::store(parsed_options, vm);
205  check_info_options(description, vm);
206  bpo::notify(vm);
207  unregistered_options = bpo::collect_unrecognized(parsed_options.options, bpo::exclude_positional);
208  if (vm.count("help") == 0 && !unregistered_options.empty()) {
209  std::cout << description << "\n";
210  exit(EXIT_FAILURE);
211  }
212  } catch (const bpo::required_option &ex) {
213  // print the error and append the default commandline option description
214  std::cout << ex.what() << std::endl << description;
215  exit(EXIT_FAILURE);
216  } catch (const bpo::error &ex) {
217  check_info_options(description, vm);
218 
219  // log boost error
220  LOG_ERROR << "boost command line option error: " << ex.what();
221 
222  // print the error message to the standard output too, as the user provided
223  // a non-supported commandline option
224  std::cout << ex.what() << '\n';
225 
226  // set the returnValue, thereby ctest will recognize
227  // that something went wrong
228  exit(EXIT_FAILURE);
229  }
230 
231  return vm;
232 }
233 
234 int main(int argc, char *argv[]) {
235  logger_init(isatty(1) == 1);
236  logger_set_threshold(boost::log::trivial::info);
237 
238  bpo::variables_map commandline_map = parse_options(argc, argv);
239 
240  int r = EXIT_FAILURE;
241  try {
242  if (geteuid() != 0) {
243  LOG_WARNING << "\033[31mRunning as non-root and may not work as expected!\033[0m\n";
244  }
245 
246  Config config(commandline_map);
247  config.storage.uptane_metadata_path = BasedPath(config.storage.path / "metadata");
248  LOG_DEBUG << "Current directory: " << boost::filesystem::current_path().string();
249 
250  std::string cmd = commandline_map["command"].as<std::string>();
251  for (size_t i = 0; i < commands.size(); i++) {
252  if (cmd == commands[i].name) {
253  LiteClient client(config);
254  return commands[i].main(client, commandline_map);
255  }
256  }
257  throw bpo::invalid_option_value(cmd);
258  r = EXIT_SUCCESS;
259  } catch (const std::exception &ex) {
260  LOG_ERROR << ex.what();
261  }
262  return r;
263 }
Configuration object for an aktualizr instance running on a Primary ECU.
Definition: config.h:74