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