1 #include <sys/statvfs.h>
3 #include "libaktualizr/packagemanagerinterface.h"
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"
16 : hash_type{target_in.hashes()[0].type()},
17 target{std::move(target_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;
26 case Hash::Type::kSha256:
28 case Hash::Type::kSha512:
31 throw std::runtime_error(
"Unknown hash algorithm");
36 FetcherProgressCb progress_cb;
43 static size_t DownloadHandler(
char* contents,
size_t size,
size_t nmemb,
void* 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;
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;
58 static int ProgressHandler(
void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
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);
71 if (ds->token !=
nullptr && !ds->token->canContinue(
false)) {
78 static constexpr
size_t buf_len = 1024;
79 std::array<uint8_t, buf_len> buf{};
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);
87 const KeyManager& keys,
const FetcherProgressCb& progress_cb,
92 if (target.hashes().empty()) {
95 TargetStatus exists = PackageManagerInterface::verifyTarget(target);
96 if (exists == TargetStatus::kGood) {
97 LOG_INFO <<
"Image already downloaded; skipping download";
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);
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);
115 LOG_DEBUG <<
"Initiating download of file " << target.filename();
116 ds->fhandle = createTargetFile(target);
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");
124 std::string target_url = target.uri();
125 if (target_url.empty()) {
126 target_url = fetcher.getRepoServer() +
"/targets/" + Utils::urlEncode(target.filename());
131 response = http_->download(target_url, DownloadHandler, ProgressHandler, ds.get(),
132 static_cast<curl_off_t
>(ds->downloaded_length));
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: "
138 ds = std_::make_unique<DownloadMetaStruct>(target, progress_cb, token);
139 ds->fhandle = createTargetFile(target);
143 if (!response.wasInterrupted()) {
151 ds->fhandle = appendTargetFile(target);
153 LOG_TRACE <<
"Download status: " << response.getStatusStr() << std::endl;
154 if (!response.isOk()) {
155 if (response.curl_code == CURLE_WRITE_ERROR) {
158 throw Uptane::Exception(
"image",
"Could not download file, error: " + response.error_message);
160 if (!target.MatchHash(
Hash(ds->hash_type, ds->hasher().getHexDigest()))) {
162 removeTargetFile(target);
167 }
catch (
const std::exception& e) {
168 LOG_WARNING <<
"Error while downloading a target: " << e.what();
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;
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;
195 return TargetStatus::kGood;
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);
202 LOG_WARNING <<
"Unable to read filesystem statistics: error code " << stat_res;
205 const uint64_t available_bytes = (
static_cast<uint64_t
>(stvfsbuf.f_bsize) * stvfsbuf.f_bavail);
206 const uint64_t reserved_bytes = 1 << 20;
208 if (required_bytes + reserved_bytes < available_bytes) {
211 LOG_ERROR <<
"Insufficient disk space available to download target! Required: " << required_bytes
212 <<
", available: " << available_bytes <<
", reserved: " << reserved_bytes;
217 boost::optional<std::pair<uintmax_t, std::string>> PackageManagerInterface::checkTargetFile(
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()}};
229 std::ifstream PackageManagerInterface::openTargetFile(
const Uptane::Target& target)
const {
230 auto file = checkTargetFile(target);
232 throw std::runtime_error(
"File doesn't exist for target " + target.filename());
234 std::ifstream stream(file->second, std::ios::binary);
235 if (!stream.good()) {
236 throw std::runtime_error(
"Can't open file " + file->second);
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);
249 storage_->storeTargetFilename(target.filename(), filename);
253 std::ofstream PackageManagerInterface::appendTargetFile(
const Uptane::Target& target) {
254 auto file = checkTargetFile(target);
256 throw std::runtime_error(
"File doesn't exist for target " + target.filename());
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);
265 void PackageManagerInterface::removeTargetFile(
const Uptane::Target& target) {
266 auto file = checkTargetFile(target);
268 throw std::runtime_error(
"File doesn't exist for target " + target.filename());
270 boost::filesystem::remove(file->second);
271 storage_->deleteTargetInfo(target.filename());
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);