Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
packagemanagerinterface.cc
1 #include "packagemanagerinterface.h"
2 
3 #include "http/httpclient.h"
4 #include "logging/logging.h"
5 
7  DownloadMetaStruct(Uptane::Target target_in, FetcherProgressCb progress_cb_in, const api::FlowControlToken* token_in)
8  : hash_type{target_in.hashes()[0].type()},
9  target{std::move(target_in)},
10  token{token_in},
11  progress_cb{std::move(progress_cb_in)} {}
12  uintmax_t downloaded_length{0};
13  unsigned int last_progress{0};
14  std::unique_ptr<StorageTargetWHandle> fhandle;
15  const Uptane::Hash::Type hash_type;
16  MultiPartHasher& hasher() {
17  switch (hash_type) {
18  case Uptane::Hash::Type::kSha256:
19  return sha256_hasher;
20  case Uptane::Hash::Type::kSha512:
21  return sha512_hasher;
22  default:
23  throw std::runtime_error("Unknown hash algorithm");
24  }
25  }
26  Uptane::Target target;
27  const api::FlowControlToken* token;
28  FetcherProgressCb progress_cb;
29 
30  private:
31  MultiPartSHA256Hasher sha256_hasher;
32  MultiPartSHA512Hasher sha512_hasher;
33 };
34 
35 static size_t DownloadHandler(char* contents, size_t size, size_t nmemb, void* userp) {
36  assert(userp);
37  auto* ds = static_cast<DownloadMetaStruct*>(userp);
38  uint64_t downloaded = size * nmemb;
39  uint64_t expected = ds->target.length();
40  if ((ds->downloaded_length + downloaded) > expected) {
41  return downloaded + 1; // curl will abort if return unexpected size;
42  }
43 
44  // incomplete writes will stop the download (written_size != nmemb*size)
45  size_t written_size = ds->fhandle->wfeed(reinterpret_cast<uint8_t*>(contents), downloaded);
46  ds->hasher().update(reinterpret_cast<const unsigned char*>(contents), written_size);
47 
48  ds->downloaded_length += downloaded;
49  return written_size;
50 }
51 
52 static int ProgressHandler(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
53  (void)dltotal;
54  (void)dlnow;
55  (void)ultotal;
56  (void)ulnow;
57  auto* ds = static_cast<DownloadMetaStruct*>(clientp);
58 
59  uint64_t expected = ds->target.length();
60  auto progress = static_cast<unsigned int>((ds->downloaded_length * 100) / expected);
61  if (ds->progress_cb && progress > ds->last_progress) {
62  ds->last_progress = progress;
63  ds->progress_cb(ds->target, "Downloading", progress);
64  }
65  if (ds->token != nullptr && !ds->token->canContinue(false)) {
66  return 1;
67  }
68  return 0;
69 }
70 
71 static void restoreHasherState(MultiPartHasher& hasher, StorageTargetRHandle* data) {
72  size_t data_len;
73  static constexpr size_t buf_len = 1024;
74  std::array<uint8_t, buf_len> buf{};
75  do {
76  data_len = data->rread(buf.data(), buf.size());
77  hasher.update(buf.data(), data_len);
78  } while (data_len != 0);
79 }
80 
81 bool PackageManagerInterface::fetchTarget(const Uptane::Target& target, Uptane::Fetcher& fetcher,
82  const KeyManager& keys, FetcherProgressCb progress_cb,
83  const api::FlowControlToken* token) {
84  (void)keys;
85  bool result = false;
86  try {
87  if (target.hashes().empty()) {
88  throw Uptane::Exception("image", "No hash defined for the target");
89  }
90  TargetStatus exists = PackageManagerInterface::verifyTarget(target);
91  if (exists == TargetStatus::kGood) {
92  LOG_INFO << "Image already downloaded; skipping download";
93  return true;
94  }
95  if (target.length() == 0) {
96  LOG_WARNING << "Skipping download of target with length 0";
97  return true;
98  }
99  std::unique_ptr<DownloadMetaStruct> ds = std_::make_unique<DownloadMetaStruct>(target, progress_cb, token);
100  if (exists == TargetStatus::kIncomplete) {
101  LOG_INFO << "Continuing incomplete download of file " << target.filename();
102  auto target_check = storage_->checkTargetFile(target);
103  ds->downloaded_length = target_check->first;
104  auto target_handle = storage_->openTargetFile(target);
105  ::restoreHasherState(ds->hasher(), target_handle.get());
106  target_handle->rclose();
107  ds->fhandle = target_handle->toWriteHandle();
108  } else {
109  // If the target was found, but is oversized or the hash doesn't match,
110  // just start over.
111  LOG_DEBUG << "Initiating download of file " << target.filename();
112  ds->fhandle = storage_->allocateTargetFile(target);
113  }
114 
115  const uint64_t required_bytes = target.length() - ds->downloaded_length;
116  if (!storage_->checkAvailableDiskSpace(required_bytes)) {
117  throw std::runtime_error("Insufficient disk space available to download target");
118  }
119 
120  std::string target_url = target.uri();
121  if (target_url.empty()) {
122  target_url = fetcher.getRepoServer() + "/targets/" + Utils::urlEncode(target.filename());
123  }
124 
125  HttpResponse response;
126  for (;;) {
127  response = http_->download(target_url, DownloadHandler, ProgressHandler, ds.get(),
128  static_cast<curl_off_t>(ds->downloaded_length));
129 
130  if (response.curl_code == CURLE_RANGE_ERROR) {
131  LOG_WARNING << "The image server doesn't support byte range requests,"
132  " try to download the image from the beginning: "
133  << target_url;
134  ds = std_::make_unique<DownloadMetaStruct>(target, progress_cb, token);
135  ds->fhandle = storage_->allocateTargetFile(target);
136  continue;
137  }
138 
139  if (!response.wasInterrupted()) {
140  break;
141  }
142  ds->fhandle.reset();
143  // sleep if paused or abort the download
144  if (!token->canContinue()) {
145  throw Uptane::Exception("image", "Download of a target was aborted");
146  }
147  ds->fhandle = storage_->openTargetFile(target)->toWriteHandle();
148  }
149  LOG_TRACE << "Download status: " << response.getStatusStr() << std::endl;
150  if (!response.isOk()) {
151  if (response.curl_code == CURLE_WRITE_ERROR) {
152  throw Uptane::OversizedTarget(target.filename());
153  }
154  throw Uptane::Exception("image", "Could not download file, error: " + response.error_message);
155  }
156  if (!target.MatchHash(Uptane::Hash(ds->hash_type, ds->hasher().getHexDigest()))) {
157  ds->fhandle->wabort();
158  throw Uptane::TargetHashMismatch(target.filename());
159  }
160  ds->fhandle->wcommit();
161  result = true;
162  } catch (const std::exception& e) {
163  LOG_WARNING << "Error while downloading a target: " << e.what();
164  }
165  return result;
166 }
167 
168 TargetStatus PackageManagerInterface::verifyTarget(const Uptane::Target& target) const {
169  auto target_exists = storage_->checkTargetFile(target);
170  if (!target_exists) {
171  LOG_DEBUG << "File " << target.filename() << " with expected hash not found in the database.";
172  return TargetStatus::kNotFound;
173  } else if (target_exists->first < target.length()) {
174  LOG_DEBUG << "File " << target.filename() << " was found in the database, but is incomplete.";
175  return TargetStatus::kIncomplete;
176  } else if (target_exists->first > target.length()) {
177  LOG_DEBUG << "File " << target.filename() << " was found in the database, but is oversized.";
178  return TargetStatus::kOversized;
179  }
180 
181  // Even if the file exists and the length matches, recheck the hash.
182  DownloadMetaStruct ds(target, nullptr, nullptr);
183  ds.downloaded_length = target_exists->first;
184  auto target_handle = storage_->openTargetFile(target);
185  ::restoreHasherState(ds.hasher(), target_handle.get());
186  target_handle->rclose();
187  if (!target.MatchHash(Uptane::Hash(ds.hash_type, ds.hasher().getHexDigest()))) {
188  LOG_ERROR << "Target exists with expected length, but hash does not match metadata! " << target;
189  return TargetStatus::kHashMismatch;
190  }
191 
192  return TargetStatus::kGood;
193 }
Uptane::Fetcher
Definition: fetcher.h:33
KeyManager
Definition: keymanager.h:13
MultiPartSHA256Hasher
Definition: crypto.h:83
data
General data structures.
Definition: types.cc:55
HttpResponse
Definition: httpinterface.h:17
MultiPartSHA512Hasher
Definition: crypto.h:68
Uptane::Hash
The hash of a file or TUF metadata.
Definition: tuf.h:209
Uptane::OversizedTarget
Definition: exceptions.h:34
DownloadMetaStruct
Definition: packagemanagerinterface.cc:6
api::FlowControlToken::canContinue
bool canContinue(bool blocking=true) const
Called by the controlled thread to query the currently requested state.
Definition: apiqueue.cc:33
Uptane::Exception
Definition: exceptions.h:10
api::FlowControlToken
Provides a thread-safe way to pause and terminate task execution.
Definition: apiqueue.h:19
Uptane::TargetHashMismatch
Definition: exceptions.h:27
StorageTargetRHandle
Definition: invstorage.h:61
result
Results of libaktualizr API calls.
Definition: results.h:13
Uptane::Target
Definition: tuf.h:238
MultiPartHasher
Definition: crypto.h:61