Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
ostree_object.cc
1 #include "ostree_object.h"
2 
3 #include <sys/stat.h>
4 #include <cassert>
5 #include <cstring>
6 #include <iostream>
7 
8 #include <glib.h>
9 
10 #include "logging/logging.h"
11 #include "ostree_repo.h"
12 #include "request_pool.h"
13 #include "utilities/utils.h"
14 
15 using std::string;
16 
17 OSTreeObject::OSTreeObject(const OSTreeRepo &repo, const std::string &object_name)
18  : file_path_(repo.root() / "/objects/" / object_name),
19  object_name_(object_name),
20  repo_(repo),
21  refcount_(0),
22  is_on_server_(PresenceOnServer::kObjectStateUnknown),
23  curl_handle_(nullptr),
24  fd_(nullptr) {
25  if (!boost::filesystem::is_regular_file(file_path_)) {
26  throw std::runtime_error(file_path_.native() + " is not a valid OSTree object.");
27  }
28 }
29 
30 OSTreeObject::~OSTreeObject() {
31  if (curl_handle_ != nullptr) {
32  curl_easy_cleanup(curl_handle_);
33  curl_handle_ = nullptr;
34  }
35 }
36 
37 void OSTreeObject::AddParent(OSTreeObject *parent, std::list<OSTreeObject::ptr>::iterator parent_it) {
38  parentref par;
39 
40  par.first = parent;
41  par.second = parent_it;
42  parents_.push_back(par);
43 }
44 
45 void OSTreeObject::ChildNotify(std::list<OSTreeObject::ptr>::iterator child_it) {
46  assert((*child_it)->is_on_server() == PresenceOnServer::kObjectPresent);
47  children_.erase(child_it);
48 }
49 
50 void OSTreeObject::NotifyParents(RequestPool &pool) {
51  assert(is_on_server_ == PresenceOnServer::kObjectPresent);
52 
53  for (parentref parent : parents_) {
54  parent.first->ChildNotify(parent.second);
55  if (parent.first->children_ready()) {
56  pool.AddUpload(parent.first);
57  }
58  }
59 }
60 
61 void OSTreeObject::AppendChild(const OSTreeObject::ptr &child) {
62  // the child could be already queried/uploaded by another parent
63  if (child->is_on_server() == PresenceOnServer::kObjectPresent) {
64  return;
65  }
66 
67  children_.push_back(child);
68  auto last = children_.end();
69  last--;
70  child->AddParent(this, last);
71 }
72 
73 // Can throw OSTreeObjectMissing if the repo is corrupt
74 void OSTreeObject::PopulateChildren() {
75  const boost::filesystem::path ext = file_path_.extension();
76  const GVariantType *content_type;
77  bool is_commit;
78 
79  // variant types are borrowed from libostree/ostree-core.h,
80  // but we don't want to create dependency on it
81  if (ext.compare(".commit") == 0) {
82  content_type = G_VARIANT_TYPE("(a{sv}aya(say)sstayay)");
83  is_commit = true;
84  } else if (ext.compare(".dirtree") == 0) {
85  content_type = G_VARIANT_TYPE("(a(say)a(sayay))");
86  is_commit = false;
87  } else {
88  return;
89  }
90 
91  GError *gerror = nullptr;
92  GMappedFile *mfile = g_mapped_file_new(file_path_.c_str(), FALSE, &gerror);
93 
94  if (mfile == nullptr) {
95  throw std::runtime_error("Failed to map metadata file " + file_path_.native());
96  }
97 
98  GVariant *contents =
99  g_variant_new_from_data(content_type, g_mapped_file_get_contents(mfile), g_mapped_file_get_length(mfile), TRUE,
100  reinterpret_cast<GDestroyNotify>(g_mapped_file_unref), mfile);
101  g_variant_ref_sink(contents);
102 
103  if (is_commit) {
104  // * - ay - Root tree contents
105  GVariant *content_csum_variant = nullptr;
106  g_variant_get_child(contents, 6, "@ay", &content_csum_variant);
107 
108  gsize n_elts;
109  const auto *csum = static_cast<const uint8_t *>(g_variant_get_fixed_array(content_csum_variant, &n_elts, 1));
110  assert(n_elts == 32);
111  AppendChild(repo_.GetObject(csum, OstreeObjectType::OSTREE_OBJECT_TYPE_DIR_TREE));
112 
113  // * - ay - Root tree metadata
114  GVariant *meta_csum_variant = nullptr;
115  g_variant_get_child(contents, 7, "@ay", &meta_csum_variant);
116  csum = static_cast<const uint8_t *>(g_variant_get_fixed_array(meta_csum_variant, &n_elts, 1));
117  assert(n_elts == 32);
118  AppendChild(repo_.GetObject(csum, OstreeObjectType::OSTREE_OBJECT_TYPE_DIR_META));
119 
120  g_variant_unref(meta_csum_variant);
121  g_variant_unref(content_csum_variant);
122  } else {
123  GVariant *files_variant = nullptr;
124  GVariant *dirs_variant = nullptr;
125 
126  files_variant = g_variant_get_child_value(contents, 0);
127  dirs_variant = g_variant_get_child_value(contents, 1);
128 
129  gsize nfiles = g_variant_n_children(files_variant);
130  gsize ndirs = g_variant_n_children(dirs_variant);
131 
132  // * - a(say) - array of (filename, checksum) for files
133  for (gsize i = 0; i < nfiles; i++) {
134  GVariant *csum_variant = nullptr;
135  const char *fname = nullptr;
136 
137  g_variant_get_child(files_variant, i, "(&s@ay)", &fname, &csum_variant);
138  gsize n_elts;
139  const auto *csum = static_cast<const uint8_t *>(g_variant_get_fixed_array(csum_variant, &n_elts, 1));
140  assert(n_elts == 32);
141  AppendChild(repo_.GetObject(csum, OstreeObjectType::OSTREE_OBJECT_TYPE_FILE));
142 
143  g_variant_unref(csum_variant);
144  }
145 
146  // * - a(sayay) - array of (dirname, tree_checksum, meta_checksum) for directories
147  for (gsize i = 0; i < ndirs; i++) {
148  GVariant *content_csum_variant = nullptr;
149  GVariant *meta_csum_variant = nullptr;
150  const char *fname = nullptr;
151  g_variant_get_child(dirs_variant, i, "(&s@ay@ay)", &fname, &content_csum_variant, &meta_csum_variant);
152  gsize n_elts;
153  // First the .dirtree:
154  const auto *csum = static_cast<const uint8_t *>(g_variant_get_fixed_array(content_csum_variant, &n_elts, 1));
155  assert(n_elts == 32);
156  AppendChild(repo_.GetObject(csum, OstreeObjectType::OSTREE_OBJECT_TYPE_DIR_TREE));
157 
158  // Then the .dirmeta:
159  csum = static_cast<const uint8_t *>(g_variant_get_fixed_array(meta_csum_variant, &n_elts, 1));
160  assert(n_elts == 32);
161  AppendChild(repo_.GetObject(csum, OstreeObjectType::OSTREE_OBJECT_TYPE_DIR_META));
162 
163  g_variant_unref(meta_csum_variant);
164  g_variant_unref(content_csum_variant);
165  }
166 
167  g_variant_unref(dirs_variant);
168  g_variant_unref(files_variant);
169  }
170  g_variant_unref(contents);
171 }
172 
173 void OSTreeObject::QueryChildren(RequestPool &pool) {
174  for (const OSTreeObject::ptr &child : children_) {
175  if (child->is_on_server() == PresenceOnServer::kObjectStateUnknown) {
176  pool.AddQuery(child);
177  }
178  }
179 }
180 
181 string OSTreeObject::Url() const { return "objects/" + object_name_; }
182 
183 void OSTreeObject::MakeTestRequest(const TreehubServer &push_target, CURLM *curl_multi_handle) {
184  assert(!curl_handle_);
185  curl_handle_ = curl_easy_init();
186  if (curl_handle_ == nullptr) {
187  throw std::runtime_error("Could not initialize curl handle");
188  }
189  curlEasySetoptWrapper(curl_handle_, CURLOPT_VERBOSE, get_curlopt_verbose());
190  current_operation_ = CurrentOp::kOstreeObjectPresenceCheck;
191 
192  push_target.InjectIntoCurl(Url(), curl_handle_);
193  curlEasySetoptWrapper(curl_handle_, CURLOPT_NOBODY, 1L); // HEAD
194 
195  curlEasySetoptWrapper(curl_handle_, CURLOPT_USERAGENT, Utils::getUserAgent());
196  curlEasySetoptWrapper(curl_handle_, CURLOPT_WRITEFUNCTION, &OSTreeObject::curl_handle_write);
197  curlEasySetoptWrapper(curl_handle_, CURLOPT_WRITEDATA, this);
198  curlEasySetoptWrapper(curl_handle_, CURLOPT_PRIVATE, this); // Used by ostree_object_from_curl
199  http_response_.str(""); // Empty the response buffer
200 
201  const CURLMcode err = curl_multi_add_handle(curl_multi_handle, curl_handle_);
202  if (err != 0) {
203  LOG_ERROR << "err:" << curl_multi_strerror(err);
204  }
205  refcount_++; // Because curl now has a reference to us
206  request_start_time_ = std::chrono::steady_clock::now();
207 }
208 
209 void OSTreeObject::Upload(TreehubServer &push_target, CURLM *curl_multi_handle, const RunMode mode) {
210  if (mode == RunMode::kDefault || mode == RunMode::kPushTree) {
211  LOG_INFO << "Uploading " << object_name_;
212  } else {
213  LOG_INFO << "Would upload " << object_name_;
214  is_on_server_ = PresenceOnServer::kObjectPresent;
215  return;
216  }
217  assert(!curl_handle_);
218 
219  curl_handle_ = curl_easy_init();
220  if (curl_handle_ == nullptr) {
221  throw std::runtime_error("Could not initialize curl handle");
222  }
223  curlEasySetoptWrapper(curl_handle_, CURLOPT_VERBOSE, get_curlopt_verbose());
224  current_operation_ = CurrentOp::kOstreeObjectUploading;
225  push_target.SetContentType("Content-Type: application/octet-stream");
226  push_target.InjectIntoCurl(Url(), curl_handle_);
227  curlEasySetoptWrapper(curl_handle_, CURLOPT_USERAGENT, Utils::getUserAgent());
228  curlEasySetoptWrapper(curl_handle_, CURLOPT_WRITEFUNCTION, &OSTreeObject::curl_handle_write);
229  curlEasySetoptWrapper(curl_handle_, CURLOPT_WRITEDATA, this);
230  http_response_.str(""); // Empty the response buffer
231 
232  struct stat file_info {};
233  fd_ = fopen(file_path_.c_str(), "rb");
234  if (fd_ == nullptr) {
235  throw std::runtime_error("could not open file to be uploaded");
236  } else {
237  if (stat(file_path_.c_str(), &file_info) < 0) {
238  throw std::runtime_error("Could not get file information");
239  }
240  }
241  curlEasySetoptWrapper(curl_handle_, CURLOPT_READDATA, fd_);
242  curlEasySetoptWrapper(curl_handle_, CURLOPT_POSTFIELDSIZE, file_info.st_size);
243  curlEasySetoptWrapper(curl_handle_, CURLOPT_POST, 1);
244 
245  curlEasySetoptWrapper(curl_handle_, CURLOPT_PRIVATE, this); // Used by ostree_object_from_curl
246  const CURLMcode err = curl_multi_add_handle(curl_multi_handle, curl_handle_);
247  if (err != 0) {
248  LOG_ERROR << "curl_multi_add_handle error:" << curl_multi_strerror(err);
249  return;
250  }
251  refcount_++; // Because curl now has a reference to us
252  request_start_time_ = std::chrono::steady_clock::now();
253 }
254 
255 void OSTreeObject::CheckChildren(RequestPool &pool, const long rescode) { // NOLINT(google-runtime-int)
256  try {
257  PopulateChildren();
258  LOG_TRACE << "Children of " << object_name_ << ": " << children_.size();
259  if (children_ready()) {
260  if (rescode != 200) {
261  pool.AddUpload(this);
262  }
263  } else {
264  QueryChildren(pool);
265  }
266  } catch (const OSTreeObjectMissing &error) {
267  LOG_ERROR << "Source OSTree repo does not contain object " << error.missing_object();
268  pool.Abort();
269  }
270 }
271 
272 void OSTreeObject::PresenceError(RequestPool &pool, const int64_t rescode) {
273  is_on_server_ = PresenceOnServer::kObjectStateUnknown;
274  LOG_WARNING << "OSTree query reported an error code: " << rescode << " retrying...";
275  LOG_DEBUG << "Http response code:" << rescode;
276  LOG_DEBUG << http_response_.str();
277  last_operation_result_ = ServerResponse::kTemporaryFailure;
278  pool.AddQuery(this);
279 }
280 
281 void OSTreeObject::UploadError(RequestPool &pool, const int64_t rescode) {
282  LOG_WARNING << "OSTree upload reported an error code:" << rescode << " retrying...";
283  LOG_DEBUG << "Http response code:" << rescode;
284  LOG_DEBUG << http_response_.str();
285  is_on_server_ = PresenceOnServer::kObjectMissing;
286  last_operation_result_ = ServerResponse::kTemporaryFailure;
287  pool.AddUpload(this);
288 }
289 
290 void OSTreeObject::CurlDone(CURLM *curl_multi_handle, RequestPool &pool) {
291  refcount_--; // Because curl now doesn't have a reference to us
292  assert(refcount_ > 0); // At least our parent should have a reference to us
293 
294  char *url = nullptr;
295  curl_easy_getinfo(curl_handle_, CURLINFO_EFFECTIVE_URL, &url);
296  long rescode = 0; // NOLINT(google-runtime-int)
297  curl_easy_getinfo(curl_handle_, CURLINFO_RESPONSE_CODE, &rescode);
298  if (current_operation_ == CurrentOp::kOstreeObjectPresenceCheck) {
299  // Sanity-check the handle's URL to make sure it contains the expected
300  // object hash.
301  // NOLINTNEXTLINE(bugprone-branch-clone)
302  if (url == nullptr || strstr(url, object_name_.c_str()) == nullptr) {
303  PresenceError(pool, rescode);
304  } else if (rescode == 200) {
305  LOG_INFO << "Already present: " << object_name_;
306  is_on_server_ = PresenceOnServer::kObjectPresent;
307  last_operation_result_ = ServerResponse::kOk;
308  if (pool.run_mode() == RunMode::kWalkTree || pool.run_mode() == RunMode::kPushTree) {
309  CheckChildren(pool, rescode);
310  } else {
311  NotifyParents(pool);
312  }
313  } else if (rescode == 404) {
314  is_on_server_ = PresenceOnServer::kObjectMissing;
315  last_operation_result_ = ServerResponse::kOk;
316  CheckChildren(pool, rescode);
317  } else {
318  PresenceError(pool, rescode);
319  }
320 
321  } else if (current_operation_ == CurrentOp::kOstreeObjectUploading) {
322  // Sanity-check the handle's URL to make sure it contains the expected
323  // object hash.
324  // NOLINTNEXTLINE(bugprone-branch-clone)
325  if (url == nullptr || strstr(url, object_name_.c_str()) == nullptr) {
326  UploadError(pool, rescode);
327  } else if (rescode == 204) {
328  LOG_TRACE << "OSTree upload successful";
329  is_on_server_ = PresenceOnServer::kObjectPresent;
330  last_operation_result_ = ServerResponse::kOk;
331  NotifyParents(pool);
332  } else if (rescode == 409) {
333  LOG_DEBUG << "OSTree upload reported a 409 Conflict, possibly due to concurrent uploads";
334  is_on_server_ = PresenceOnServer::kObjectPresent;
335  last_operation_result_ = ServerResponse::kOk;
336  NotifyParents(pool);
337  } else {
338  UploadError(pool, rescode);
339  }
340  fclose(fd_);
341  } else {
342  LOG_ERROR << "Unknown operation: " << static_cast<int>(current_operation_);
343  assert(0);
344  }
345  curl_multi_remove_handle(curl_multi_handle, curl_handle_);
346  curl_easy_cleanup(curl_handle_);
347  curl_handle_ = nullptr;
348 }
349 
350 size_t OSTreeObject::curl_handle_write(void *buffer, size_t size, size_t nmemb, void *userp) {
351  auto *that = static_cast<OSTreeObject *>(userp);
352  that->http_response_.write(static_cast<const char *>(buffer), static_cast<std::streamsize>(size * nmemb));
353  return size * nmemb;
354 }
355 
356 OSTreeObject::ptr ostree_object_from_curl(CURL *curlhandle) {
357  void *p;
358  curl_easy_getinfo(curlhandle, CURLINFO_PRIVATE, &p);
359  assert(p);
360  auto *h = static_cast<OSTreeObject *>(p);
361  return boost::intrusive_ptr<OSTreeObject>(h);
362 }
363 
364 void intrusive_ptr_add_ref(OSTreeObject *h) { h->refcount_++; }
365 
366 void intrusive_ptr_release(OSTreeObject *h) {
367  if (--h->refcount_ == 0) {
368  delete h;
369  }
370 }
371 
372 std::ostream &operator<<(std::ostream &stream, const OSTreeObject &o) {
373  stream << o.object_name_;
374  return stream;
375 }
376 
377 std::ostream &operator<<(std::ostream &stream, const OSTreeObject::ptr &o) {
378  stream << *o;
379  return stream;
380 }
381 
382 // vim: set tabstop=2 shiftwidth=2 expandtab:
OSTreeObjectMissing
Thrown by GetObject when the object requested is not present in the repository.
Definition: ostree_repo.h:46
RunMode::kPushTree
@ kPushTree
Walk the entire tree and upload any missing objects.
RunMode::kWalkTree
@ kWalkTree
Walk the entire tree (without uploading).
OSTreeObject
Definition: ostree_object.h:30
TreehubServer
Definition: treehub_server.h:11
OSTreeRepo
A source repository to read OSTree objects from.
Definition: ostree_repo.h:19
RequestPool
Definition: request_pool.h:12
RunMode::kDefault
@ kDefault
Default operation.
RunMode
RunMode
Execution mode to run garage tools in.
Definition: garage_common.h:6