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