1 #include "packagemanagerinterface.h"
3 #include "http/httpclient.h"
4 #include "logging/logging.h"
8 : hash_type{target_in.hashes()[0].type()},
9 target{std::move(target_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;
18 case Uptane::Hash::Type::kSha256:
20 case Uptane::Hash::Type::kSha512:
23 throw std::runtime_error(
"Unknown hash algorithm");
28 FetcherProgressCb progress_cb;
35 static size_t DownloadHandler(
char* contents,
size_t size,
size_t nmemb,
void* 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;
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);
48 ds->downloaded_length += downloaded;
52 static int ProgressHandler(
void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
57 auto* ds = static_cast<DownloadMetaStruct*>(clientp);
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);
65 if (ds->token !=
nullptr && !ds->token->canContinue(
false)) {
73 static constexpr
size_t buf_len = 1024;
74 std::array<uint8_t, buf_len> buf{};
76 data_len =
data->rread(buf.data(), buf.size());
77 hasher.update(buf.data(), data_len);
78 }
while (data_len != 0);
82 const KeyManager& keys, FetcherProgressCb progress_cb,
87 if (target.hashes().empty()) {
90 TargetStatus exists = PackageManagerInterface::verifyTarget(target);
91 if (exists == TargetStatus::kGood) {
92 LOG_INFO <<
"Image already downloaded; skipping download";
95 if (target.length() == 0) {
96 LOG_WARNING <<
"Skipping download of target with length 0";
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();
111 LOG_DEBUG <<
"Initiating download of file " << target.filename();
112 ds->fhandle = storage_->allocateTargetFile(target);
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");
120 std::string target_url = target.uri();
121 if (target_url.empty()) {
122 target_url = fetcher.getRepoServer() +
"/targets/" + Utils::urlEncode(target.filename());
127 response = http_->download(target_url, DownloadHandler, ProgressHandler, ds.get(),
128 static_cast<curl_off_t>(ds->downloaded_length));
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: "
134 ds = std_::make_unique<DownloadMetaStruct>(target, progress_cb, token);
135 ds->fhandle = storage_->allocateTargetFile(target);
139 if (!response.wasInterrupted()) {
147 ds->fhandle = storage_->openTargetFile(target)->toWriteHandle();
149 LOG_TRACE <<
"Download status: " << response.getStatusStr() << std::endl;
150 if (!response.isOk()) {
151 if (response.curl_code == CURLE_WRITE_ERROR) {
154 throw Uptane::Exception(
"image",
"Could not download file, error: " + response.error_message);
156 if (!target.MatchHash(
Uptane::Hash(ds->hash_type, ds->hasher().getHexDigest()))) {
157 ds->fhandle->wabort();
160 ds->fhandle->wcommit();
162 }
catch (
const std::exception& e) {
163 LOG_WARNING <<
"Error while downloading a target: " << e.what();
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;
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;
192 return TargetStatus::kGood;