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