Aktualizr
C++ SOTA Client
ostreemanager.cc
1 #include "ostreemanager.h"
2 
3 #include <unistd.h>
4 #include <cstdio>
5 #include <fstream>
6 
7 #include <gio/gio.h>
8 #include <json/json.h>
9 #include <ostree-async-progress.h>
10 #include <boost/algorithm/string.hpp>
11 #include <boost/algorithm/string/classification.hpp>
12 #include <boost/algorithm/string/split.hpp>
13 #include <boost/filesystem.hpp>
14 #include <utility>
15 
16 #include "libaktualizr/packagemanagerfactory.h"
17 
18 #include "bootloader/bootloader.h"
19 #include "logging/logging.h"
20 #include "storage/invstorage.h"
21 #include "utilities/utils.h"
22 
23 AUTO_REGISTER_PACKAGE_MANAGER(PACKAGE_MANAGER_OSTREE, OstreeManager);
24 
25 static void aktualizr_progress_cb(OstreeAsyncProgress *progress, gpointer data) {
26  auto *mt = static_cast<PullMetaStruct *>(data);
27  if (mt->token != nullptr && !mt->token->canContinue()) {
28  g_cancellable_cancel(mt->cancellable.get());
29  }
30 
31  g_autofree char *status = ostree_async_progress_get_status(progress);
32  guint scanning = ostree_async_progress_get_uint(progress, "scanning");
33  guint outstanding_fetches = ostree_async_progress_get_uint(progress, "outstanding-fetches");
34  guint outstanding_metadata_fetches = ostree_async_progress_get_uint(progress, "outstanding-metadata-fetches");
35  guint outstanding_writes = ostree_async_progress_get_uint(progress, "outstanding-writes");
36  guint n_scanned_metadata = ostree_async_progress_get_uint(progress, "scanned-metadata");
37 
38  if (status != nullptr && *status != '\0') {
39  LOG_INFO << "ostree-pull: " << status;
40  } else if (outstanding_fetches != 0) {
41  guint fetched = ostree_async_progress_get_uint(progress, "fetched");
42  guint metadata_fetched = ostree_async_progress_get_uint(progress, "metadata-fetched");
43  guint requested = ostree_async_progress_get_uint(progress, "requested");
44  if (scanning != 0 || outstanding_metadata_fetches != 0) {
45  LOG_INFO << "ostree-pull: Receiving metadata objects: " << metadata_fetched
46  << " outstanding: " << outstanding_metadata_fetches;
47  if (mt->progress_cb) {
48  mt->progress_cb(mt->target, "Receiving metadata objects", 0);
49  }
50  } else {
51  guint calculated = (fetched * 100) / requested;
52  if (calculated != mt->percent_complete) {
53  mt->percent_complete = calculated;
54  LOG_INFO << "ostree-pull: Receiving objects: " << calculated << "% ";
55  if (mt->progress_cb) {
56  mt->progress_cb(mt->target, "Receiving objects", calculated);
57  }
58  }
59  }
60  } else if (outstanding_writes != 0) {
61  LOG_INFO << "ostree-pull: Writing objects: " << outstanding_writes;
62  } else {
63  LOG_INFO << "ostree-pull: Scanning metadata: " << n_scanned_metadata;
64  if (mt->progress_cb) {
65  mt->progress_cb(mt->target, "Scanning metadata", 0);
66  }
67  }
68 }
69 
70 data::InstallationResult OstreeManager::pull(const boost::filesystem::path &sysroot_path,
71  const std::string &ostree_server, const KeyManager &keys,
72  const Uptane::Target &target, const api::FlowControlToken *token,
73  OstreeProgressCb progress_cb) {
74  const std::string refhash = target.sha256Hash();
75  // NOLINTNEXTLINE(modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays, hicpp-avoid-c-arrays)
76  const char *const commit_ids[] = {refhash.c_str()};
77  GError *error = nullptr;
78  GVariantBuilder builder;
79  GVariant *options;
80  GObjectUniquePtr<OstreeAsyncProgress> progress = nullptr;
81 
82  GObjectUniquePtr<OstreeSysroot> sysroot = OstreeManager::LoadSysroot(sysroot_path);
83  GObjectUniquePtr<OstreeRepo> repo = LoadRepo(sysroot.get(), &error);
84  if (error != nullptr) {
85  LOG_ERROR << "Could not get OSTree repo";
86  g_error_free(error);
87  return data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, "Could not get OSTree repo");
88  }
89 
90  GHashTable *ref_list = nullptr;
91  if (ostree_repo_list_commit_objects_starting_with(repo.get(), refhash.c_str(), &ref_list, nullptr, &error) != 0) {
92  guint length = g_hash_table_size(ref_list);
93  g_hash_table_destroy(ref_list); // OSTree creates the table with destroy notifiers, so no memory leaks expected
94  // should never be greater than 1, but use >= for robustness
95  if (length >= 1) {
96  LOG_DEBUG << "refhash already pulled";
97  return data::InstallationResult(true, data::ResultCode::Numeric::kAlreadyProcessed, "Refhash was already pulled");
98  }
99  }
100  if (error != nullptr) {
101  g_error_free(error);
102  error = nullptr;
103  }
104 
105  if (!OstreeManager::addRemote(repo.get(), ostree_server, keys)) {
106  return data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, "Error adding OSTree remote");
107  }
108 
109  g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
110  g_variant_builder_add(&builder, "{s@v}", "flags", g_variant_new_variant(g_variant_new_int32(0)));
111 
112  g_variant_builder_add(&builder, "{s@v}", "refs", g_variant_new_variant(g_variant_new_strv(commit_ids, 1)));
113 
114  options = g_variant_builder_end(&builder);
115 
116  PullMetaStruct mt(target, token, g_cancellable_new(), std::move(progress_cb));
117  progress.reset(ostree_async_progress_new_and_connect(aktualizr_progress_cb, &mt));
118  if (ostree_repo_pull_with_options(repo.get(), remote, options, progress.get(), mt.cancellable.get(), &error) == 0) {
119  LOG_ERROR << "Error while pulling image: " << error->code << " " << error->message;
121  g_error_free(error);
122  g_variant_unref(options);
123  return install_res;
124  }
125  ostree_async_progress_finish(progress.get());
126  g_variant_unref(options);
127  return data::InstallationResult(data::ResultCode::Numeric::kOk, "Pulling OSTree image was successful");
128 }
129 
130 data::InstallationResult OstreeManager::install(const Uptane::Target &target) const {
131  const char *opt_osname = nullptr;
132  GCancellable *cancellable = nullptr;
133  GError *error = nullptr;
134  g_autofree char *revision = nullptr;
135 
136  if (!config.os.empty()) {
137  opt_osname = config.os.c_str();
138  }
139 
140  GObjectUniquePtr<OstreeSysroot> sysroot = OstreeManager::LoadSysroot(config.sysroot);
141  GObjectUniquePtr<OstreeRepo> repo = LoadRepo(sysroot.get(), &error);
142 
143  if (error != nullptr) {
144  LOG_ERROR << "could not get repo";
145  g_error_free(error);
147  }
148 
149  auto origin = StructGuard<GKeyFile>(
150  ostree_sysroot_origin_new_from_refspec(sysroot.get(), target.sha256Hash().c_str()), g_key_file_free);
151  if (ostree_repo_resolve_rev(repo.get(), target.sha256Hash().c_str(), FALSE, &revision, &error) == 0) {
152  LOG_ERROR << error->message;
154  g_error_free(error);
155  return install_res;
156  }
157 
158  GObjectUniquePtr<OstreeDeployment> merge_deployment(ostree_sysroot_get_merge_deployment(sysroot.get(), opt_osname));
159  if (merge_deployment == nullptr) {
160  LOG_ERROR << "No merge deployment";
162  }
163 
164  if (ostree_sysroot_prepare_cleanup(sysroot.get(), cancellable, &error) == 0) {
165  LOG_ERROR << error->message;
167  g_error_free(error);
168  return install_res;
169  }
170 
171  std::string args_content =
172  std::string(ostree_bootconfig_parser_get(ostree_deployment_get_bootconfig(merge_deployment.get()), "options"));
173  std::vector<std::string> args_vector;
174  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
175  boost::split(args_vector, args_content, boost::is_any_of(" "));
176 
177  std::vector<const char *> kargs_strv_vector;
178  kargs_strv_vector.reserve(args_vector.size() + 1);
179 
180  for (auto it = args_vector.begin(); it != args_vector.end(); ++it) {
181  kargs_strv_vector.push_back((*it).c_str());
182  }
183  kargs_strv_vector[args_vector.size()] = nullptr;
184  auto *kargs_strv = const_cast<char **>(&kargs_strv_vector[0]);
185 
186  OstreeDeployment *new_deployment_raw = nullptr;
187  if (ostree_sysroot_deploy_tree(sysroot.get(), opt_osname, revision, origin.get(), merge_deployment.get(), kargs_strv,
188  &new_deployment_raw, cancellable, &error) == 0) {
189  LOG_ERROR << "ostree_sysroot_deploy_tree: " << error->message;
191  g_error_free(error);
192  return install_res;
193  }
194  GObjectUniquePtr<OstreeDeployment> new_deployment = GObjectUniquePtr<OstreeDeployment>(new_deployment_raw);
195 
196  if (ostree_sysroot_simple_write_deployment(sysroot.get(), nullptr, new_deployment.get(), merge_deployment.get(),
197  OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NONE, cancellable,
198  &error) == 0) {
199  LOG_ERROR << "ostree_sysroot_simple_write_deployment:" << error->message;
201  g_error_free(error);
202  return install_res;
203  }
204 
205  // set reboot flag to be notified later
206  if (bootloader_ != nullptr) {
207  bootloader_->rebootFlagSet();
208  }
209 
210  LOG_INFO << "Performing sync()";
211  sync();
212  return data::InstallationResult(data::ResultCode::Numeric::kNeedCompletion, "Application successful, need reboot");
213 }
214 
215 void OstreeManager::completeInstall() const {
216  LOG_INFO << "About to reboot the system in order to apply pending updates...";
217  bootloader_->reboot();
218 }
219 
220 data::InstallationResult OstreeManager::finalizeInstall(const Uptane::Target &target) {
221  if (!bootloader_->rebootDetected()) {
222  return data::InstallationResult(data::ResultCode::Numeric::kNeedCompletion,
223  "Reboot is required for the pending update application");
224  }
225 
226  LOG_INFO << "Checking installation of new OSTree sysroot";
227  const std::string current_hash = getCurrentHash();
228 
229  data::InstallationResult install_result =
230  data::InstallationResult(data::ResultCode::Numeric::kOk, "Successfully booted on new version");
231 
232  if (current_hash != target.sha256Hash()) {
233  LOG_ERROR << "Expected to boot " << target.sha256Hash() << " but found " << current_hash
234  << ". The system may have been rolled back.";
235  install_result = data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, "Wrong version booted");
236  }
237 
238  bootloader_->rebootFlagClear();
239  return install_result;
240 }
241 
242 void OstreeManager::updateNotify() { bootloader_->updateNotify(); }
243 
244 OstreeManager::OstreeManager(const PackageConfig &pconfig, const BootloaderConfig &bconfig,
245  const std::shared_ptr<INvStorage> &storage, const std::shared_ptr<HttpInterface> &http)
246  : PackageManagerInterface(pconfig, bconfig, storage, http), bootloader_{new Bootloader(bconfig, *storage)} {
247  GObjectUniquePtr<OstreeSysroot> sysroot_smart = OstreeManager::LoadSysroot(config.sysroot);
248  if (sysroot_smart == nullptr) {
249  throw std::runtime_error("Could not find OSTree sysroot at: " + config.sysroot.string());
250  }
251 
252  // consider boot successful as soon as we started, missing internet connection or connection to Secondaries are not
253  // proper reasons to roll back
254  if (imageUpdated()) {
255  bootloader_->setBootOK();
256  }
257 }
258 
259 OstreeManager::~OstreeManager() { bootloader_.reset(nullptr); }
260 
261 bool OstreeManager::fetchTarget(const Uptane::Target &target, Uptane::Fetcher &fetcher, const KeyManager &keys,
262  const FetcherProgressCb &progress_cb, const api::FlowControlToken *token) {
263  if (!target.IsOstree()) {
264  // The case when the OSTree package manager is set as a package manager for aktualizr
265  // while the target is aimed for a Secondary ECU that is configured with another/non-OSTree package manager
266  return PackageManagerInterface::fetchTarget(target, fetcher, keys, progress_cb, token);
267  }
268  return OstreeManager::pull(config.sysroot, config.ostree_server, keys, target, token, progress_cb).success;
269 }
270 
271 TargetStatus OstreeManager::verifyTarget(const Uptane::Target &target) const {
272  if (!target.IsOstree()) {
273  // The case when the OSTree package manager is set as a package manager for aktualizr
274  // while the target is aimed for a Secondary ECU that is configured with another/non-OSTree package manager
275  return PackageManagerInterface::verifyTarget(target);
276  }
277  return verifyTargetInternal(target);
278 }
279 
280 TargetStatus OstreeManager::verifyTargetInternal(const Uptane::Target &target) const {
281  const std::string refhash = target.sha256Hash();
282  GError *error = nullptr;
283 
284  GObjectUniquePtr<OstreeSysroot> sysroot = OstreeManager::LoadSysroot(config.sysroot);
285  GObjectUniquePtr<OstreeRepo> repo = LoadRepo(sysroot.get(), &error);
286  if (error != nullptr) {
287  LOG_ERROR << "Could not get OSTree repo";
288  g_error_free(error);
289  return TargetStatus::kNotFound;
290  }
291 
292  GHashTable *ref_list = nullptr;
293  if (ostree_repo_list_commit_objects_starting_with(repo.get(), refhash.c_str(), &ref_list, nullptr, &error) != 0) {
294  guint length = g_hash_table_size(ref_list);
295  g_hash_table_destroy(ref_list); // OSTree creates the table with destroy notifiers, so no memory leaks expected
296  // should never be greater than 1, but use >= for robustness
297  if (length >= 1) {
298  return TargetStatus::kGood;
299  }
300  }
301  if (error != nullptr) {
302  g_error_free(error);
303  error = nullptr;
304  }
305 
306  LOG_ERROR << "Could not find OSTree commit";
307  return TargetStatus::kNotFound;
308 }
309 
310 Json::Value OstreeManager::getInstalledPackages() const {
311  std::string packages_str = Utils::readFile(config.packages_file);
312  std::vector<std::string> package_lines;
313  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
314  boost::split(package_lines, packages_str, boost::is_any_of("\n"));
315  Json::Value packages(Json::arrayValue);
316  for (auto it = package_lines.begin(); it != package_lines.end(); ++it) {
317  if (it->empty()) {
318  continue;
319  }
320  size_t pos = it->find(" ");
321  if (pos == std::string::npos) {
322  throw std::runtime_error("Wrong packages file format");
323  }
324  Json::Value package;
325  package["name"] = it->substr(0, pos);
326  package["version"] = it->substr(pos + 1);
327  packages.append(package);
328  }
329  return packages;
330 }
331 
332 std::string OstreeManager::getCurrentHash() const {
333  GObjectUniquePtr<OstreeSysroot> sysroot_smart = OstreeManager::LoadSysroot(config.sysroot);
334  OstreeDeployment *booted_deployment = ostree_sysroot_get_booted_deployment(sysroot_smart.get());
335  if (booted_deployment == nullptr) {
336  throw std::runtime_error("Could not get booted deployment in " + config.sysroot.string());
337  }
338  return ostree_deployment_get_csum(booted_deployment);
339 }
340 
341 Uptane::Target OstreeManager::getCurrent() const {
342  const std::string current_hash = getCurrentHash();
343  boost::optional<Uptane::Target> current_version;
344  // This may appear Primary-specific, but since Secondaries only know about
345  // themselves, this actually works just fine for them, too.
346  storage_->loadPrimaryInstalledVersions(&current_version, nullptr);
347 
348  if (!!current_version && current_version->sha256Hash() == current_hash) {
349  return *current_version;
350  }
351 
352  LOG_ERROR << "Current versions in storage and reported by OSTree do not match";
353 
354  // Look into installation log to find a possible candidate. Again, despite the
355  // name, this will work for Secondaries as well.
356  std::vector<Uptane::Target> installed_versions;
357  storage_->loadPrimaryInstallationLog(&installed_versions, false);
358 
359  // Version should be in installed versions. It's possible that multiple
360  // targets could have the same sha256Hash. In this case the safest assumption
361  // is that the most recent (the reverse of the vector) target is what we
362  // should return.
363  std::vector<Uptane::Target>::reverse_iterator it;
364  for (it = installed_versions.rbegin(); it != installed_versions.rend(); it++) {
365  if (it->sha256Hash() == current_hash) {
366  return *it;
367  }
368  }
369 
370  return Uptane::Target::Unknown();
371 }
372 
373 // used for bootloader rollback
374 bool OstreeManager::imageUpdated() {
375  GObjectUniquePtr<OstreeSysroot> sysroot_smart = OstreeManager::LoadSysroot(config.sysroot);
376 
377  // image updated if no pending deployment in the list of deployments
378  GPtrArray *deployments = ostree_sysroot_get_deployments(sysroot_smart.get());
379 
380  OstreeDeployment *pending_deployment = nullptr;
381  ostree_sysroot_query_deployments_for(sysroot_smart.get(), nullptr, &pending_deployment, nullptr);
382 
383  bool pending_found = false;
384  for (guint i = 0; i < deployments->len; i++) {
385  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
386  if (deployments->pdata[i] == pending_deployment) {
387  pending_found = true;
388  break;
389  }
390  }
391 
392  g_ptr_array_unref(deployments);
393  return !pending_found;
394 }
395 
396 GObjectUniquePtr<OstreeDeployment> OstreeManager::getStagedDeployment() const {
397  GObjectUniquePtr<OstreeSysroot> sysroot_smart = OstreeManager::LoadSysroot(config.sysroot);
398 
399  GPtrArray *deployments = nullptr;
400  OstreeDeployment *res = nullptr;
401 
402  deployments = ostree_sysroot_get_deployments(sysroot_smart.get());
403 
404  if (deployments->len > 0) {
405  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
406  auto *d = static_cast<OstreeDeployment *>(deployments->pdata[0]);
407  auto *d2 = static_cast<OstreeDeployment *>(g_object_ref(d));
408  res = d2;
409  }
410 
411  g_ptr_array_unref(deployments);
412  return GObjectUniquePtr<OstreeDeployment>(res);
413 }
414 
415 GObjectUniquePtr<OstreeSysroot> OstreeManager::LoadSysroot(const boost::filesystem::path &path) {
416  GObjectUniquePtr<OstreeSysroot> sysroot = nullptr;
417 
418  if (!path.empty()) {
419  GFile *fl = g_file_new_for_path(path.c_str());
420  sysroot.reset(ostree_sysroot_new(fl));
421  g_object_unref(fl);
422  } else {
423  sysroot.reset(ostree_sysroot_new_default());
424  }
425  GError *error = nullptr;
426  if (ostree_sysroot_load(sysroot.get(), nullptr, &error) == 0) {
427  if (error != nullptr) {
428  g_error_free(error);
429  }
430  throw std::runtime_error("could not load sysroot");
431  }
432  return sysroot;
433 }
434 
435 GObjectUniquePtr<OstreeRepo> OstreeManager::LoadRepo(OstreeSysroot *sysroot, GError **error) {
436  OstreeRepo *repo = nullptr;
437 
438  if (ostree_sysroot_get_repo(sysroot, &repo, nullptr, error) == 0) {
439  return nullptr;
440  }
441 
442  return GObjectUniquePtr<OstreeRepo>(repo);
443 }
444 
445 bool OstreeManager::addRemote(OstreeRepo *repo, const std::string &url, const KeyManager &keys) {
446  GCancellable *cancellable = nullptr;
447  GError *error = nullptr;
448  GVariantBuilder b;
449  GVariant *options;
450 
451  g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}"));
452  g_variant_builder_add(&b, "{s@v}", "gpg-verify", g_variant_new_variant(g_variant_new_boolean(FALSE)));
453 
454  std::string cert_file = keys.getCertFile();
455  std::string pkey_file = keys.getPkeyFile();
456  std::string ca_file = keys.getCaFile();
457  if (!cert_file.empty() && !pkey_file.empty() && !ca_file.empty()) {
458  g_variant_builder_add(&b, "{s@v}", "tls-client-cert-path",
459  g_variant_new_variant(g_variant_new_string(cert_file.c_str())));
460  g_variant_builder_add(&b, "{s@v}", "tls-client-key-path",
461  g_variant_new_variant(g_variant_new_string(pkey_file.c_str())));
462  g_variant_builder_add(&b, "{s@v}", "tls-ca-path", g_variant_new_variant(g_variant_new_string(ca_file.c_str())));
463  }
464  options = g_variant_builder_end(&b);
465 
466  if (ostree_repo_remote_change(repo, nullptr, OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS, remote, url.c_str(), options,
467  cancellable, &error) == 0) {
468  LOG_ERROR << "Error of adding remote: " << error->message;
469  g_error_free(error);
470  g_variant_unref(options);
471  return false;
472  }
473  if (ostree_repo_remote_change(repo, nullptr, OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS, remote, url.c_str(),
474  options, cancellable, &error) == 0) {
475  LOG_ERROR << "Error of adding remote: " << error->message;
476  g_error_free(error);
477  g_variant_unref(options);
478  return false;
479  }
480  g_variant_unref(options);
481  return true;
482 }
Uptane::Fetcher
Definition: fetcher.h:33
PackageManagerInterface
Definition: packagemanagerinterface.h:42
KeyManager
Definition: keymanager.h:13
data::ResultCode::Numeric::kAlreadyProcessed
@ kAlreadyProcessed
Operation has already been processed.
data::InstallationResult
Definition: types.h:277
Bootloader
Definition: bootloader.h:8
data
General data structures.
Definition: types.h:217
OstreeManager
Definition: ostreemanager.h:41
api::FlowControlToken
Provides a thread-safe way to pause and terminate task execution.
Definition: apiqueue.h:19
Uptane::Target::IsOstree
bool IsOstree() const
Is this an OSTree target? OSTree targets need special treatment because the hash doesn't represent th...
Definition: tuf.cc:179
PackageConfig
Definition: config.h:91
Uptane::Target
Definition: types.h:379
BootloaderConfig
Definition: config.h:156
PullMetaStruct
Definition: ostreemanager.h:26
data::ResultCode::Numeric::kInstallFailed
@ kInstallFailed
Package installation failed.