1 #include <sys/statvfs.h>
4 #include "libaktualizr/packagemanagerinterface.h"
6 #include "bootloader/bootloader.h"
7 #include "crypto/keymanager.h"
8 #include "http/httpclient.h"
9 #include "logging/logging.h"
10 #include "storage/invstorage.h"
11 #include "uptane/fetcher.h"
12 #include "utilities/apiqueue.h"
17 : hash_type{target_in.hashes()[0].type()},
18 target{std::move(target_in)},
20 progress_cb{std::move(progress_cb_in)},
21 time_lastreport{std::chrono::steady_clock::now()} {}
22 uintmax_t downloaded_length{0};
23 unsigned int last_progress{0};
24 std::ofstream fhandle;
25 const Hash::Type hash_type;
28 case Hash::Type::kSha256:
30 case Hash::Type::kSha512:
33 throw std::runtime_error(
"Unknown hash algorithm");
38 FetcherProgressCb progress_cb;
40 std::chrono::time_point<std::chrono::steady_clock> time_lastreport;
47 static size_t DownloadHandler(
char* contents,
size_t size,
size_t nmemb,
void* userp) {
50 size_t downloaded = size * nmemb;
51 uint64_t expected = ds->target.length();
52 if ((ds->downloaded_length + downloaded) > expected) {
53 return downloaded + 1;
56 ds->fhandle.write(contents,
static_cast<std::streamsize
>(downloaded));
57 ds->hasher().update(
reinterpret_cast<const unsigned char*
>(contents), downloaded);
58 ds->downloaded_length += downloaded;
62 static constexpr int64_t LogProgressInterval = 15000;
64 static int ProgressHandler(
void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
71 uint64_t expected = ds->target.length();
72 auto progress =
static_cast<unsigned int>((ds->downloaded_length * 100) / expected);
73 if (ds->progress_cb && progress > ds->last_progress) {
74 ds->last_progress = progress;
75 ds->progress_cb(ds->target,
"Downloading", progress);
77 auto now = std::chrono::steady_clock::now();
78 auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - ds->time_lastreport);
79 if (milliseconds.count() > LogProgressInterval) {
80 LOG_INFO <<
"Download progress for file " << ds->target.filename() <<
": " << progress <<
"%";
81 ds->time_lastreport = now;
84 if (ds->token !=
nullptr && !ds->token->canContinue(
false)) {
91 static constexpr
size_t buf_len = 1024;
92 std::array<uint8_t, buf_len> buf{};
94 data.read(
reinterpret_cast<char*
>(buf.data()), buf.size());
95 hasher.update(buf.data(),
static_cast<uint64_t
>(
data.gcount()));
96 }
while (
data.gcount() != 0);
100 const KeyManager& keys,
const FetcherProgressCb& progress_cb,
105 if (target.hashes().empty()) {
108 TargetStatus exists = PackageManagerInterface::verifyTarget(target);
109 if (exists == TargetStatus::kGood) {
110 LOG_INFO <<
"Image already downloaded; skipping download";
113 std::unique_ptr<DownloadMetaStruct> ds = std_::make_unique<DownloadMetaStruct>(target, progress_cb, token);
114 if (target.length() == 0) {
115 LOG_INFO <<
"Skipping download of target with length 0";
116 ds->fhandle = createTargetFile(target);
119 if (exists == TargetStatus::kIncomplete) {
120 LOG_INFO <<
"Continuing incomplete download of file " << target.filename();
121 auto target_check = checkTargetFile(target);
122 ds->downloaded_length = target_check->first;
123 ::restoreHasherState(ds->hasher(), openTargetFile(target));
124 ds->fhandle = appendTargetFile(target);
128 LOG_DEBUG <<
"Initiating download of file " << target.filename();
129 ds->fhandle = createTargetFile(target);
132 const uint64_t required_bytes = target.length() - ds->downloaded_length;
133 if (!checkAvailableDiskSpace(required_bytes)) {
134 throw std::runtime_error(
"Insufficient disk space available to download target");
137 std::string target_url = target.uri();
138 if (target_url.empty()) {
139 target_url = fetcher.getRepoServer() +
"/targets/" + Utils::urlEncode(target.filename());
144 response = http_->download(target_url, DownloadHandler, ProgressHandler, ds.get(),
145 static_cast<curl_off_t
>(ds->downloaded_length));
147 if (response.curl_code == CURLE_RANGE_ERROR) {
148 LOG_WARNING <<
"The image server doesn't support byte range requests,"
149 " try to download the image from the beginning: "
151 ds = std_::make_unique<DownloadMetaStruct>(target, progress_cb, token);
152 ds->fhandle = createTargetFile(target);
156 if (!response.wasInterrupted()) {
164 ds->fhandle = appendTargetFile(target);
166 LOG_TRACE <<
"Download status: " << response.getStatusStr() << std::endl;
167 if (!response.isOk()) {
168 if (response.curl_code == CURLE_WRITE_ERROR) {
171 throw Uptane::Exception(
"image",
"Could not download file, error: " + response.error_message);
173 if (!target.MatchHash(
Hash(ds->hash_type, ds->hasher().getHexDigest()))) {
175 removeTargetFile(target);
180 }
catch (
const std::exception& e) {
181 LOG_WARNING <<
"Error while downloading a target: " << e.what();
186 TargetStatus PackageManagerInterface::verifyTarget(
const Uptane::Target& target)
const {
187 auto target_exists = checkTargetFile(target);
188 if (!target_exists) {
189 LOG_DEBUG <<
"File " << target.filename() <<
" with expected hash not found in the database.";
190 return TargetStatus::kNotFound;
191 }
else if (target_exists->first < target.length()) {
192 LOG_DEBUG <<
"File " << target.filename() <<
" was found in the database, but is incomplete.";
193 return TargetStatus::kIncomplete;
194 }
else if (target_exists->first > target.length()) {
195 LOG_DEBUG <<
"File " << target.filename() <<
" was found in the database, but is oversized.";
196 return TargetStatus::kOversized;
201 ds.downloaded_length = target_exists->first;
202 ::restoreHasherState(ds.hasher(), openTargetFile(target));
203 if (!target.MatchHash(
Hash(ds.hash_type, ds.hasher().getHexDigest()))) {
204 LOG_ERROR <<
"Target exists with expected length, but hash does not match metadata! " << target;
205 return TargetStatus::kHashMismatch;
208 return TargetStatus::kGood;
211 bool PackageManagerInterface::checkAvailableDiskSpace(
const uint64_t required_bytes)
const {
212 struct statvfs stvfsbuf {};
213 const int stat_res = statvfs(config.images_path.c_str(), &stvfsbuf);
215 LOG_WARNING <<
"Unable to read filesystem statistics: error code " << stat_res;
218 const uint64_t available_bytes = (
static_cast<uint64_t
>(stvfsbuf.f_bsize) * stvfsbuf.f_bavail);
219 const uint64_t reserved_bytes = 1 << 20;
221 if (required_bytes + reserved_bytes < available_bytes) {
224 LOG_ERROR <<
"Insufficient disk space available to download target! Required: " << required_bytes
225 <<
", available: " << available_bytes <<
", reserved: " << reserved_bytes;
230 boost::optional<std::pair<uintmax_t, std::string>> PackageManagerInterface::checkTargetFile(
232 std::string filename = storage_->getTargetFilename(target.filename());
233 if (!filename.empty()) {
234 auto path = config.images_path / filename;
235 if (boost::filesystem::exists(path)) {
236 return {{boost::filesystem::file_size(path), path.string()}};
242 std::ifstream PackageManagerInterface::openTargetFile(
const Uptane::Target& target)
const {
243 auto file = checkTargetFile(target);
245 throw std::runtime_error(
"File doesn't exist for target " + target.filename());
247 std::ifstream stream(file->second, std::ios::binary);
248 if (!stream.good()) {
249 throw std::runtime_error(
"Can't open file " + file->second);
254 std::ofstream PackageManagerInterface::createTargetFile(
const Uptane::Target& target) {
255 std::string filename = target.hashes()[0].HashString();
256 std::string filepath = (config.images_path / filename).
string();
257 boost::filesystem::create_directories(config.images_path);
258 std::ofstream stream(filepath, std::ios::binary | std::ios::ate);
259 if (!stream.good()) {
260 throw std::runtime_error(
"Can't write to file " + filepath);
262 storage_->storeTargetFilename(target.filename(), filename);
266 std::ofstream PackageManagerInterface::appendTargetFile(
const Uptane::Target& target) {
267 auto file = checkTargetFile(target);
269 throw std::runtime_error(
"File doesn't exist for target " + target.filename());
271 std::ofstream stream(file->second, std::ios::binary | std::ios::app);
272 if (!stream.good()) {
273 throw std::runtime_error(
"Can't open file " + file->second);
278 void PackageManagerInterface::removeTargetFile(
const Uptane::Target& target) {
279 auto file = checkTargetFile(target);
281 throw std::runtime_error(
"File doesn't exist for target " + target.filename());
283 boost::filesystem::remove(file->second);
284 storage_->deleteTargetInfo(target.filename());
287 std::vector<Uptane::Target> PackageManagerInterface::getTargetFiles() {
288 std::vector<Uptane::Target> v;
289 auto names = storage_->getAllTargetNames();
290 v.reserve(names.size());
291 for (
const auto& name : names) {
292 v.emplace_back(name, Uptane::EcuMap{}, std::vector<Hash>{}, 0);