1 #include "packagemanagerinterface.h" 3 #include <sys/statvfs.h> 5 #include "http/httpclient.h" 6 #include "logging/logging.h" 10 : hash_type{target_in.hashes()[0].type()},
11 target{std::move(target_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;
20 case Hash::Type::kSha256:
22 case Hash::Type::kSha512:
25 throw std::runtime_error(
"Unknown hash algorithm");
30 FetcherProgressCb progress_cb;
37 static size_t DownloadHandler(
char* contents,
size_t size,
size_t nmemb,
void* 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;
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;
52 static int ProgressHandler(
void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
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)) {
72 static constexpr
size_t buf_len = 1024;
73 std::array<uint8_t, buf_len> buf{};
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);
81 const KeyManager& keys,
const FetcherProgressCb& progress_cb,
86 if (target.hashes().empty()) {
89 TargetStatus exists = PackageManagerInterface::verifyTarget(target);
90 if (exists == TargetStatus::kGood) {
91 LOG_INFO <<
"Image already downloaded; skipping download";
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);
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);
109 LOG_DEBUG <<
"Initiating download of file " << target.filename();
110 ds->fhandle = createTargetFile(target);
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");
118 std::string target_url = target.uri();
119 if (target_url.empty()) {
120 target_url = fetcher.getRepoServer() +
"/targets/" + Utils::urlEncode(target.filename());
125 response = http_->download(target_url, DownloadHandler, ProgressHandler, ds.get(),
126 static_cast<curl_off_t
>(ds->downloaded_length));
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: " 132 ds = std_::make_unique<DownloadMetaStruct>(target, progress_cb, token);
133 ds->fhandle = createTargetFile(target);
137 if (!response.wasInterrupted()) {
145 ds->fhandle = appendTargetFile(target);
147 LOG_TRACE <<
"Download status: " << response.getStatusStr() << std::endl;
148 if (!response.isOk()) {
149 if (response.curl_code == CURLE_WRITE_ERROR) {
152 throw Uptane::Exception(
"image",
"Could not download file, error: " + response.error_message);
154 if (!target.MatchHash(
Hash(ds->hash_type, ds->hasher().getHexDigest()))) {
156 removeTargetFile(target);
161 }
catch (
const std::exception& e) {
162 LOG_WARNING <<
"Error while downloading a target: " << e.what();
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;
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;
189 return TargetStatus::kGood;
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);
196 LOG_WARNING <<
"Unable to read filesystem statistics: error code " << stat_res;
199 const uint64_t available_bytes = (
static_cast<uint64_t
>(stvfsbuf.f_bsize) * stvfsbuf.f_bavail);
200 const uint64_t reserved_bytes = 1 << 20;
202 if (required_bytes + reserved_bytes < available_bytes) {
205 LOG_ERROR <<
"Insufficient disk space available to download target! Required: " << required_bytes
206 <<
", available: " << available_bytes <<
", reserved: " << reserved_bytes;
211 boost::optional<std::pair<uintmax_t, std::string>> PackageManagerInterface::checkTargetFile(
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()}};
223 std::ifstream PackageManagerInterface::openTargetFile(
const Uptane::Target& target)
const {
224 auto file = checkTargetFile(target);
226 throw std::runtime_error(
"File doesn't exist for target " + target.filename());
228 std::ifstream stream(file->second, std::ios::binary);
229 if (!stream.good()) {
230 throw std::runtime_error(
"Can't open file " + file->second);
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);
243 storage_->storeTargetFilename(target.filename(), filename);
247 std::ofstream PackageManagerInterface::appendTargetFile(
const Uptane::Target& target) {
248 auto file = checkTargetFile(target);
250 throw std::runtime_error(
"File doesn't exist for target " + target.filename());
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);
259 void PackageManagerInterface::removeTargetFile(
const Uptane::Target& target) {
260 auto file = checkTargetFile(target);
262 throw std::runtime_error(
"File doesn't exist for target " + target.filename());
264 boost::filesystem::remove(file->second);
265 storage_->deleteTargetInfo(target.filename());
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);
Provides a thread-safe way to pause and terminate task execution.
bool canContinue(bool blocking=true) const
Called by the controlled thread to query the currently requested state.
The Hash class The hash of a file or Uptane metadata.
Results of libaktualizr API calls.