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