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 repo.");
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:
OSTreeObjectMissing
Thrown by GetObject when the object requested is not present in the repository.
Definition: ostree_repo.h:45
RunMode::kPushTree
Walk the entire tree and upload any missing objects.
RunMode::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
Default operation.
RunMode
RunMode
Execution mode to run garage tools in.
Definition: garage_common.h:6