Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
packagemanagerinterface.cc
1 #include "packagemanagerinterface.h"
2 
3 #include <sys/statvfs.h>
4 
5 #include "http/httpclient.h"
6 #include "logging/logging.h"
7 
9  DownloadMetaStruct(Uptane::Target target_in, FetcherProgressCb progress_cb_in, const api::FlowControlToken* token_in)
10  : hash_type{target_in.hashes()[0].type()},
11  target{std::move(target_in)},
12  token{token_in},
13  progress_cb{std::move(progress_cb_in)} {}
14  uintmax_t downloaded_length{0};
15  unsigned int last_progress{0};
16  std::ofstream fhandle;
17  const Hash::Type hash_type;
18  MultiPartHasher& hasher() {
19  switch (hash_type) {
20  case Hash::Type::kSha256:
21  return sha256_hasher;
22  case Hash::Type::kSha512:
23  return sha512_hasher;
24  default:
25  throw std::runtime_error("Unknown hash algorithm");
26  }
27  }
28  Uptane::Target target;
29  const api::FlowControlToken* token;
30  FetcherProgressCb progress_cb;
31 
32  private:
33  MultiPartSHA256Hasher sha256_hasher;
34  MultiPartSHA512Hasher sha512_hasher;
35 };
36 
37 static size_t DownloadHandler(char* contents, size_t size, size_t nmemb, void* userp) {
38  assert(userp);
39  auto* ds = static_cast<DownloadMetaStruct*>(userp);
40  size_t downloaded = size * nmemb;
41  uint64_t expected = ds->target.length();
42  if ((ds->downloaded_length + downloaded) > expected) {
43  return downloaded + 1; // curl will abort if return unexpected size;
44  }
45 
46  ds->fhandle.write(contents, static_cast<std::streamsize>(downloaded));
47  ds->hasher().update(reinterpret_cast<const unsigned char*>(contents), downloaded);
48  ds->downloaded_length += downloaded;
49  return downloaded;
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, std::ifstream data) {
72  static constexpr size_t buf_len = 1024;
73  std::array<uint8_t, buf_len> buf{};
74  do {
75  data.read(reinterpret_cast<char*>(buf.data()), buf.size());
76  hasher.update(buf.data(), static_cast<uint64_t>(data.gcount()));
77  } while (data.gcount() != 0);
78 }
79 
80 bool PackageManagerInterface::fetchTarget(const Uptane::Target& target, Uptane::Fetcher& fetcher,
81  const KeyManager& keys, const FetcherProgressCb& progress_cb,
82  const api::FlowControlToken* token) {
83  (void)keys;
84  bool result = false;
85  try {
86  if (target.hashes().empty()) {
87  throw Uptane::Exception("image", "No hash defined for the target");
88  }
89  TargetStatus exists = PackageManagerInterface::verifyTarget(target);
90  if (exists == TargetStatus::kGood) {
91  LOG_INFO << "Image already downloaded; skipping download";
92  return true;
93  }
94  std::unique_ptr<DownloadMetaStruct> ds = std_::make_unique<DownloadMetaStruct>(target, progress_cb, token);
95  if (target.length() == 0) {
96  LOG_INFO << "Skipping download of target with length 0";
97  ds->fhandle = createTargetFile(target);
98  return true;
99  }
100  if (exists == TargetStatus::kIncomplete) {
101  LOG_INFO << "Continuing incomplete download of file " << target.filename();
102  auto target_check = checkTargetFile(target);
103  ds->downloaded_length = target_check->first;
104  ::restoreHasherState(ds->hasher(), openTargetFile(target));
105  ds->fhandle = appendTargetFile(target);
106  } else {
107  // If the target was found, but is oversized or the hash doesn't match,
108  // just start over.
109  LOG_DEBUG << "Initiating download of file " << target.filename();
110  ds->fhandle = createTargetFile(target);
111  }
112 
113  const uint64_t required_bytes = target.length() - ds->downloaded_length;
114  if (!checkAvailableDiskSpace(required_bytes)) {
115  throw std::runtime_error("Insufficient disk space available to download target");
116  }
117 
118  std::string target_url = target.uri();
119  if (target_url.empty()) {
120  target_url = fetcher.getRepoServer() + "/targets/" + Utils::urlEncode(target.filename());
121  }
122 
123  HttpResponse response;
124  for (;;) {
125  response = http_->download(target_url, DownloadHandler, ProgressHandler, ds.get(),
126  static_cast<curl_off_t>(ds->downloaded_length));
127 
128  if (response.curl_code == CURLE_RANGE_ERROR) {
129  LOG_WARNING << "The image server doesn't support byte range requests,"
130  " try to download the image from the beginning: "
131  << target_url;
132  ds = std_::make_unique<DownloadMetaStruct>(target, progress_cb, token);
133  ds->fhandle = createTargetFile(target);
134  continue;
135  }
136 
137  if (!response.wasInterrupted()) {
138  break;
139  }
140  ds->fhandle.close();
141  // sleep if paused or abort the download
142  if (!token->canContinue()) {
143  throw Uptane::Exception("image", "Download of a target was aborted");
144  }
145  ds->fhandle = appendTargetFile(target);
146  }
147  LOG_TRACE << "Download status: " << response.getStatusStr() << std::endl;
148  if (!response.isOk()) {
149  if (response.curl_code == CURLE_WRITE_ERROR) {
150  throw Uptane::OversizedTarget(target.filename());
151  }
152  throw Uptane::Exception("image", "Could not download file, error: " + response.error_message);
153  }
154  if (!target.MatchHash(Hash(ds->hash_type, ds->hasher().getHexDigest()))) {
155  ds->fhandle.close();
156  removeTargetFile(target);
157  throw Uptane::TargetHashMismatch(target.filename());
158  }
159  ds->fhandle.close();
160  result = true;
161  } catch (const std::exception& e) {
162  LOG_WARNING << "Error while downloading a target: " << e.what();
163  }
164  return result;
165 }
166 
167 TargetStatus PackageManagerInterface::verifyTarget(const Uptane::Target& target) const {
168  auto target_exists = checkTargetFile(target);
169  if (!target_exists) {
170  LOG_DEBUG << "File " << target.filename() << " with expected hash not found in the database.";
171  return TargetStatus::kNotFound;
172  } else if (target_exists->first < target.length()) {
173  LOG_DEBUG << "File " << target.filename() << " was found in the database, but is incomplete.";
174  return TargetStatus::kIncomplete;
175  } else if (target_exists->first > target.length()) {
176  LOG_DEBUG << "File " << target.filename() << " was found in the database, but is oversized.";
177  return TargetStatus::kOversized;
178  }
179 
180  // Even if the file exists and the length matches, recheck the hash.
181  DownloadMetaStruct ds(target, nullptr, nullptr);
182  ds.downloaded_length = target_exists->first;
183  ::restoreHasherState(ds.hasher(), openTargetFile(target));
184  if (!target.MatchHash(Hash(ds.hash_type, ds.hasher().getHexDigest()))) {
185  LOG_ERROR << "Target exists with expected length, but hash does not match metadata! " << target;
186  return TargetStatus::kHashMismatch;
187  }
188 
189  return TargetStatus::kGood;
190 }
191 
192 bool PackageManagerInterface::checkAvailableDiskSpace(const uint64_t required_bytes) const {
193  struct statvfs stvfsbuf {};
194  const int stat_res = statvfs(config.images_path.c_str(), &stvfsbuf);
195  if (stat_res < 0) {
196  LOG_WARNING << "Unable to read filesystem statistics: error code " << stat_res;
197  return true;
198  }
199  const uint64_t available_bytes = (static_cast<uint64_t>(stvfsbuf.f_bsize) * stvfsbuf.f_bavail);
200  const uint64_t reserved_bytes = 1 << 20;
201 
202  if (required_bytes + reserved_bytes < available_bytes) {
203  return true;
204  } else {
205  LOG_ERROR << "Insufficient disk space available to download target! Required: " << required_bytes
206  << ", available: " << available_bytes << ", reserved: " << reserved_bytes;
207  return false;
208  }
209 }
210 
211 boost::optional<std::pair<uintmax_t, std::string>> PackageManagerInterface::checkTargetFile(
212  const Uptane::Target& target) const {
213  std::string filename = storage_->getTargetFilename(target.filename());
214  if (!filename.empty()) {
215  auto path = config.images_path / filename;
216  if (boost::filesystem::exists(path)) {
217  return {{boost::filesystem::file_size(path), path.string()}};
218  }
219  }
220  return boost::none;
221 }
222 
223 std::ifstream PackageManagerInterface::openTargetFile(const Uptane::Target& target) const {
224  auto file = checkTargetFile(target);
225  if (!file) {
226  throw std::runtime_error("File doesn't exist for target " + target.filename());
227  }
228  std::ifstream stream(file->second, std::ios::binary);
229  if (!stream.good()) {
230  throw std::runtime_error("Can't open file " + file->second);
231  }
232  return stream;
233 }
234 
235 std::ofstream PackageManagerInterface::createTargetFile(const Uptane::Target& target) {
236  std::string filename = target.hashes()[0].HashString();
237  std::string filepath = (config.images_path / filename).string();
238  boost::filesystem::create_directories(config.images_path);
239  std::ofstream stream(filepath, std::ios::binary | std::ios::ate);
240  if (!stream.good()) {
241  throw std::runtime_error("Can't write to file " + filepath);
242  }
243  storage_->storeTargetFilename(target.filename(), filename);
244  return stream;
245 }
246 
247 std::ofstream PackageManagerInterface::appendTargetFile(const Uptane::Target& target) {
248  auto file = checkTargetFile(target);
249  if (!file) {
250  throw std::runtime_error("File doesn't exist for target " + target.filename());
251  }
252  std::ofstream stream(file->second, std::ios::binary | std::ios::app);
253  if (!stream.good()) {
254  throw std::runtime_error("Can't open file " + file->second);
255  }
256  return stream;
257 }
258 
259 void PackageManagerInterface::removeTargetFile(const Uptane::Target& target) {
260  auto file = checkTargetFile(target);
261  if (!file) {
262  throw std::runtime_error("File doesn't exist for target " + target.filename());
263  }
264  boost::filesystem::remove(file->second);
265  storage_->deleteTargetInfo(target.filename());
266 }
267 
268 std::vector<Uptane::Target> PackageManagerInterface::getTargetFiles() {
269  std::vector<Uptane::Target> v;
270  auto names = storage_->getAllTargetNames();
271  v.reserve(names.size());
272  for (const auto& name : names) {
273  v.emplace_back(name, Uptane::EcuMap{}, std::vector<Hash>{}, 0);
274  }
275  return v;
276 }
Provides a thread-safe way to pause and terminate task execution.
Definition: apiqueue.h:19
General data structures.
Definition: types.h:215
bool canContinue(bool blocking=true) const
Called by the controlled thread to query the currently requested state.
Definition: apiqueue.cc:33
The Hash class The hash of a file or Uptane metadata.
Definition: types.h:157
Results of libaktualizr API calls.
Definition: results.h:12