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