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