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()};
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  boost::split(args_vector, args_content, boost::is_any_of(" "));
171 
172  std::vector<const char *> kargs_strv_vector;
173  kargs_strv_vector.reserve(args_vector.size() + 1);
174 
175  for (auto it = args_vector.begin(); it != args_vector.end(); ++it) {
176  kargs_strv_vector.push_back((*it).c_str());
177  }
178  kargs_strv_vector[args_vector.size()] = nullptr;
179  auto kargs_strv = const_cast<char **>(&kargs_strv_vector[0]);
180 
181  OstreeDeployment *new_deployment_raw = nullptr;
182  if (ostree_sysroot_deploy_tree(sysroot.get(), opt_osname, revision, origin.get(), merge_deployment.get(), kargs_strv,
183  &new_deployment_raw, cancellable, &error) == 0) {
184  LOG_ERROR << "ostree_sysroot_deploy_tree: " << error->message;
186  g_error_free(error);
187  return install_res;
188  }
189  GObjectUniquePtr<OstreeDeployment> new_deployment = GObjectUniquePtr<OstreeDeployment>(new_deployment_raw);
190 
191  if (ostree_sysroot_simple_write_deployment(sysroot.get(), nullptr, new_deployment.get(), merge_deployment.get(),
192  OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NONE, cancellable,
193  &error) == 0) {
194  LOG_ERROR << "ostree_sysroot_simple_write_deployment:" << error->message;
196  g_error_free(error);
197  return install_res;
198  }
199 
200  // set reboot flag to be notified later
201  if (bootloader_ != nullptr) {
202  bootloader_->rebootFlagSet();
203  }
204 
205  LOG_INFO << "Performing sync()";
206  sync();
207  return data::InstallationResult(data::ResultCode::Numeric::kNeedCompletion, "Application successful, need reboot");
208 }
209 
210 void OstreeManager::completeInstall() const {
211  LOG_INFO << "About to reboot the system in order to apply pending updates...";
212  bootloader_->reboot();
213 }
214 
215 data::InstallationResult OstreeManager::finalizeInstall(const Uptane::Target &target) {
216  if (!bootloader_->rebootDetected()) {
217  return data::InstallationResult(data::ResultCode::Numeric::kNeedCompletion,
218  "Reboot is required for the pending update application");
219  }
220 
221  LOG_INFO << "Checking installation of new OSTree sysroot";
222  const std::string current_hash = getCurrentHash();
223 
224  data::InstallationResult install_result =
225  data::InstallationResult(data::ResultCode::Numeric::kOk, "Successfully booted on new version");
226 
227  if (current_hash != target.sha256Hash()) {
228  LOG_ERROR << "Expected to boot " << target.sha256Hash() << " but found " << current_hash
229  << ". The system may have been rolled back.";
230  install_result = data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, "Wrong version booted");
231  }
232 
233  bootloader_->rebootFlagClear();
234  return install_result;
235 }
236 
237 OstreeManager::OstreeManager(const PackageConfig &pconfig, const BootloaderConfig &bconfig,
238  const std::shared_ptr<INvStorage> &storage, const std::shared_ptr<HttpInterface> &http)
239  : PackageManagerInterface(pconfig, bconfig, storage, http), bootloader_{new Bootloader(bconfig, *storage)} {
240  GObjectUniquePtr<OstreeSysroot> sysroot_smart = OstreeManager::LoadSysroot(config.sysroot);
241  if (sysroot_smart == nullptr) {
242  throw std::runtime_error("Could not find OSTree sysroot at: " + config.sysroot.string());
243  }
244 
245  // consider boot successful as soon as we started, missing internet connection or connection to secondaries are not
246  // proper reasons to roll back
247  if (imageUpdated()) {
248  bootloader_->setBootOK();
249  }
250 }
251 
252 bool OstreeManager::fetchTarget(const Uptane::Target &target, Uptane::Fetcher &fetcher, const KeyManager &keys,
253  FetcherProgressCb progress_cb, const api::FlowControlToken *token) {
254  if (!target.IsOstree()) {
255  // The case when the ostree package manager is set as a package manager for aktualizr
256  // while the target is aimed for a secondary ECU that is configured with another/non-ostree package manager
257  return PackageManagerInterface::fetchTarget(target, fetcher, keys, progress_cb, token);
258  }
259  return OstreeManager::pull(config.sysroot, config.ostree_server, keys, target, token, progress_cb).success;
260 }
261 
262 TargetStatus OstreeManager::verifyTarget(const Uptane::Target &target) const {
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::verifyTarget(target);
267  }
268  return verifyTargetInternal(target);
269 }
270 
271 TargetStatus OstreeManager::verifyTargetInternal(const Uptane::Target &target) const {
272  const std::string refhash = target.sha256Hash();
273  GError *error = nullptr;
274 
275  GObjectUniquePtr<OstreeSysroot> sysroot = OstreeManager::LoadSysroot(config.sysroot);
276  GObjectUniquePtr<OstreeRepo> repo = LoadRepo(sysroot.get(), &error);
277  if (error != nullptr) {
278  LOG_ERROR << "Could not get OSTree repo";
279  g_error_free(error);
280  return TargetStatus::kNotFound;
281  }
282 
283  GHashTable *ref_list = nullptr;
284  if (ostree_repo_list_commit_objects_starting_with(repo.get(), refhash.c_str(), &ref_list, nullptr, &error) != 0) {
285  guint length = g_hash_table_size(ref_list);
286  g_hash_table_destroy(ref_list); // OSTree creates the table with destroy notifiers, so no memory leaks expected
287  // should never be greater than 1, but use >= for robustness
288  if (length >= 1) {
289  return TargetStatus::kGood;
290  }
291  }
292  if (error != nullptr) {
293  g_error_free(error);
294  error = nullptr;
295  }
296 
297  LOG_ERROR << "Could not find OSTree commit";
298  return TargetStatus::kNotFound;
299 }
300 
301 Json::Value OstreeManager::getInstalledPackages() const {
302  std::string packages_str = Utils::readFile(config.packages_file);
303  std::vector<std::string> package_lines;
304  boost::split(package_lines, packages_str, boost::is_any_of("\n"));
305  Json::Value packages(Json::arrayValue);
306  for (auto it = package_lines.begin(); it != package_lines.end(); ++it) {
307  if (it->empty()) {
308  continue;
309  }
310  size_t pos = it->find(" ");
311  if (pos == std::string::npos) {
312  throw std::runtime_error("Wrong packages file format");
313  }
314  Json::Value package;
315  package["name"] = it->substr(0, pos);
316  package["version"] = it->substr(pos + 1);
317  packages.append(package);
318  }
319  return packages;
320 }
321 
322 std::string OstreeManager::getCurrentHash() const {
323  GObjectUniquePtr<OstreeSysroot> sysroot_smart = OstreeManager::LoadSysroot(config.sysroot);
324  OstreeDeployment *booted_deployment = ostree_sysroot_get_booted_deployment(sysroot_smart.get());
325  if (booted_deployment == nullptr) {
326  throw std::runtime_error("Could not get booted deployment in " + config.sysroot.string());
327  }
328  return ostree_deployment_get_csum(booted_deployment);
329 }
330 
331 Uptane::Target OstreeManager::getCurrent() const {
332  const std::string current_hash = getCurrentHash();
333  boost::optional<Uptane::Target> current_version;
334  // This may appear Primary-specific, but since Secondaries only know about
335  // themselves, this actually works just fine for them, too.
336  storage_->loadPrimaryInstalledVersions(&current_version, nullptr);
337 
338  if (!!current_version && current_version->sha256Hash() == current_hash) {
339  return *current_version;
340  }
341 
342  LOG_ERROR << "Current versions in storage and reported by ostree do not match";
343 
344  // Look into installation log to find a possible candidate. Again, despite the
345  // name, this will work for Secondaries as well.
346  std::vector<Uptane::Target> installed_versions;
347  storage_->loadPrimaryInstallationLog(&installed_versions, false);
348 
349  // Version should be in installed versions. It's possible that multiple
350  // targets could have the same sha256Hash. In this case the safest assumption
351  // is that the most recent (the reverse of the vector) target is what we
352  // should return.
353  std::vector<Uptane::Target>::reverse_iterator it;
354  for (it = installed_versions.rbegin(); it != installed_versions.rend(); it++) {
355  if (it->sha256Hash() == current_hash) {
356  return *it;
357  }
358  }
359 
360  return Uptane::Target::Unknown();
361 }
362 
363 // used for bootloader rollback
364 bool OstreeManager::imageUpdated() {
365  GObjectUniquePtr<OstreeSysroot> sysroot_smart = OstreeManager::LoadSysroot(config.sysroot);
366 
367  // image updated if no pending deployment in the list of deployments
368  GPtrArray *deployments = ostree_sysroot_get_deployments(sysroot_smart.get());
369 
370  OstreeDeployment *pending_deployment = nullptr;
371  ostree_sysroot_query_deployments_for(sysroot_smart.get(), nullptr, &pending_deployment, nullptr);
372 
373  bool pending_found = false;
374  for (guint i = 0; i < deployments->len; i++) {
375  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
376  if (deployments->pdata[i] == pending_deployment) {
377  pending_found = true;
378  break;
379  }
380  }
381 
382  g_ptr_array_unref(deployments);
383  return !pending_found;
384 }
385 
386 GObjectUniquePtr<OstreeDeployment> OstreeManager::getStagedDeployment() const {
387  GObjectUniquePtr<OstreeSysroot> sysroot_smart = OstreeManager::LoadSysroot(config.sysroot);
388 
389  GPtrArray *deployments = nullptr;
390  OstreeDeployment *res = nullptr;
391 
392  deployments = ostree_sysroot_get_deployments(sysroot_smart.get());
393 
394  if (deployments->len > 0) {
395  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
396  auto *d = static_cast<OstreeDeployment *>(deployments->pdata[0]);
397  auto *d2 = static_cast<OstreeDeployment *>(g_object_ref(d));
398  res = d2;
399  }
400 
401  g_ptr_array_unref(deployments);
402  return GObjectUniquePtr<OstreeDeployment>(res);
403 }
404 
405 GObjectUniquePtr<OstreeSysroot> OstreeManager::LoadSysroot(const boost::filesystem::path &path) {
406  GObjectUniquePtr<OstreeSysroot> sysroot = nullptr;
407 
408  if (!path.empty()) {
409  GFile *fl = g_file_new_for_path(path.c_str());
410  sysroot.reset(ostree_sysroot_new(fl));
411  g_object_unref(fl);
412  } else {
413  sysroot.reset(ostree_sysroot_new_default());
414  }
415  GError *error = nullptr;
416  if (ostree_sysroot_load(sysroot.get(), nullptr, &error) == 0) {
417  if (error != nullptr) {
418  g_error_free(error);
419  }
420  throw std::runtime_error("could not load sysroot");
421  }
422  return sysroot;
423 }
424 
425 GObjectUniquePtr<OstreeRepo> OstreeManager::LoadRepo(OstreeSysroot *sysroot, GError **error) {
426  OstreeRepo *repo = nullptr;
427 
428  if (ostree_sysroot_get_repo(sysroot, &repo, nullptr, error) == 0) {
429  return nullptr;
430  }
431 
432  return GObjectUniquePtr<OstreeRepo>(repo);
433 }
434 
435 bool OstreeManager::addRemote(OstreeRepo *repo, const std::string &url, const KeyManager &keys) {
436  GCancellable *cancellable = nullptr;
437  GError *error = nullptr;
438  GVariantBuilder b;
439  GVariant *options;
440 
441  g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}"));
442  g_variant_builder_add(&b, "{s@v}", "gpg-verify", g_variant_new_variant(g_variant_new_boolean(FALSE)));
443 
444  std::string cert_file = keys.getCertFile();
445  std::string pkey_file = keys.getPkeyFile();
446  std::string ca_file = keys.getCaFile();
447  if (!cert_file.empty() && !pkey_file.empty() && !ca_file.empty()) {
448  g_variant_builder_add(&b, "{s@v}", "tls-client-cert-path",
449  g_variant_new_variant(g_variant_new_string(cert_file.c_str())));
450  g_variant_builder_add(&b, "{s@v}", "tls-client-key-path",
451  g_variant_new_variant(g_variant_new_string(pkey_file.c_str())));
452  g_variant_builder_add(&b, "{s@v}", "tls-ca-path", g_variant_new_variant(g_variant_new_string(ca_file.c_str())));
453  }
454  options = g_variant_builder_end(&b);
455 
456  if (ostree_repo_remote_change(repo, nullptr, OSTREE_REPO_REMOTE_CHANGE_DELETE_IF_EXISTS, remote, url.c_str(), options,
457  cancellable, &error) == 0) {
458  LOG_ERROR << "Error of adding remote: " << error->message;
459  g_error_free(error);
460  g_variant_unref(options);
461  return false;
462  }
463  if (ostree_repo_remote_change(repo, nullptr, OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS, remote, url.c_str(),
464  options, cancellable, &error) == 0) {
465  LOG_ERROR << "Error of adding remote: " << error->message;
466  g_error_free(error);
467  g_variant_unref(options);
468  return false;
469  }
470  g_variant_unref(options);
471  return true;
472 }
Uptane::Fetcher
Definition: fetcher.h:33
PackageManagerInterface
Definition: packagemanagerinterface.h:36
KeyManager
Definition: keymanager.h:13
data::ResultCode::Numeric::kAlreadyProcessed
Operation has already been processed.
data::InstallationResult
Definition: types.h:182
Bootloader
Definition: bootloader.h:8
data
General data structures.
Definition: types.cc:54
OstreeManager
Definition: ostreemanager.h:40
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:220
PackageConfig
Definition: packagemanagerconfig.h:24
Uptane::Target
Definition: tuf.h:238
BootloaderConfig
Definition: bootloader_config.h:11
PullMetaStruct
Definition: ostreemanager.h:25
data::ResultCode::Numeric::kInstallFailed
Package installation failed.