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