* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "progress_thread.h"
#include <file_utils.h>
#include <unistd.h>
#include <cinttypes>
#include <regex>
#include "curl/curl.h"
#include "curl/easy.h"
#include "firmware_common.h"
#include "isys_installer.h"
#include "securec.h"
#include "sys_installer_kits_impl.h"
#include "update_define.h"
#include "update_log.h"
namespace OHOS {
namespace UpdateService {
int32_t StreamProgressThread::StartDownload(const std::string &url, const int64_t size, const int64_t recordPoint)
{
ENGINE_LOGI("StartDownload url = %s", url.c_str());
serverUrl_ = url;
downloadedSize_ = recordPoint;
totalFileSize_ = size;
bufferPos_ = 0;
isCancel_.store(false);
exitDownload_ = false;
(void)memset_s(buffer_, BUFFER_SIZE, 0, BUFFER_SIZE);
curl_global_init(CURL_GLOBAL_ALL);
return StartProgress();
}
void StreamProgressThread::StopDownload()
{
ENGINE_LOGI("StopDownload");
exitDownload_ = true;
StopProgress();
curl_global_cleanup();
}
bool StreamProgressThread::ProcessThreadExecute()
{
ENGINE_LOGI("ProcessThreadExecute");
downloadHandle_ = curl_easy_init();
ENGINE_CHECK(downloadHandle_ != nullptr, ProcessThreadExit();
DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, "Failed to init curl");
return true, "Failed to init curl");
if (!CheckFileSize()) {
ProcessThreadExit();
DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, std::to_string(CAST_INT(DownloadEndReason::CURL_ERROR)));
return false;
}
if (!DownloadFile()) {
ProcessThreadExit();
DownloadCallback(0, UpgradeStatus::DOWNLOAD_CANCEL,
std::to_string(CAST_INT(DownloadEndReason::CANCEL)));
} else {
ProcessThreadExit();
DownloadCallback(DOWNLOAD_FINISH_PERCENT, UpgradeStatus::DOWNLOAD_SUCCESS, "");
}
return false;
}
size_t StreamProgressThread::HeaderCallback(char* buffer, size_t size, size_t nmemb, void* userp)
{
size_t realsize = size * nmemb;
std::string* header = static_cast<std::string*>(userp);
*header += std::string(buffer, realsize);
return realsize;
}
bool StreamProgressThread::CheckFileSize()
{
CURLcode res;
std::string headerData;
curl_easy_setopt(downloadHandle_, CURLOPT_URL, serverUrl_.c_str());
curl_easy_setopt(downloadHandle_, CURLOPT_NOBODY, 1L);
curl_easy_setopt(downloadHandle_, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(downloadHandle_, CURLOPT_HEADERFUNCTION, HeaderCallback);
curl_easy_setopt(downloadHandle_, CURLOPT_HEADERDATA, &headerData);
res = curl_easy_perform(downloadHandle_);
if (res != CURLE_OK) {
ENGINE_LOGE("Failed to curl_easy_perform res %s", curl_easy_strerror(res));
return false;
}
const std::string key = "Content-Length:";
size_t pos = headerData.find(key);
if (pos == std::string::npos) {
ENGINE_LOGE("Content-Length not found in headers");
return false;
}
std::string lenStr = headerData.substr(pos + key.length());
std::regex pattern(R"(\d+)");
std::smatch match;
if (!std::regex_search(lenStr, match, pattern)) {
ENGINE_LOGE("Failed to parse Content-Length");
return false;
}
int64_t fileSize = std::stoll(match.str());
if (fileSize <= 0 || fileSize != totalFileSize_) {
ENGINE_LOGE("File size mismatch fileSize:%{public}" PRId64 " totalFileSize_:%{public}" PRId64 "",
fileSize, totalFileSize_);
return false;
}
return true;
}
bool StreamProgressThread::DownloadFile()
{
CURLcode res;
curl_easy_setopt(downloadHandle_, CURLOPT_NOBODY, 0L);
curl_easy_setopt(downloadHandle_, CURLOPT_HEADER, 0L);
curl_easy_setopt(downloadHandle_, CURLOPT_WRITEDATA, this);
curl_easy_setopt(downloadHandle_, CURLOPT_WRITEFUNCTION, WriteFunc);
ENGINE_LOGI("curl_easy_setopt downloadedSize_ %{public}" PRId64 "", downloadedSize_);
if (downloadedSize_ > 0) {
curl_easy_setopt(downloadHandle_, CURLOPT_RESUME_FROM_LARGE, static_cast<curl_off_t>(downloadedSize_));
}
curl_easy_setopt(downloadHandle_, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(downloadHandle_, CURLOPT_PROGRESSDATA, this);
curl_easy_setopt(downloadHandle_, CURLOPT_PROGRESSFUNCTION, DownloadProgress);
res = curl_easy_perform(downloadHandle_);
if (res != CURLE_OK) {
ENGINE_LOGE("Failed to download res:%{public}s", curl_easy_strerror(res));
return false;
} else {
ENGINE_LOGI("Success to download");
return true;
}
}
void StreamProgressThread::ProcessThreadExit()
{
ENGINE_LOGI("ProcessThreadExit");
if (downloadHandle_ != nullptr) {
curl_easy_cleanup(downloadHandle_);
}
downloadHandle_ = nullptr;
}
int32_t StreamProgressThread::DownloadCallback(uint32_t percent, UpgradeStatus status, const std::string &error)
{
ENGINE_LOGI("DownloadCallback percent:%{public}d, status:%{public}d, error:%{public}s",
percent, CAST_INT(status), error.c_str());
downloadProgress_.endReason = error;
downloadProgress_.percent = percent;
downloadProgress_.status = status;
if (callback_ != nullptr) {
callback_(downloadProgress_);
}
return 0;
}
int32_t StreamProgressThread::DownloadProgress(const void *localData, double dlTotal, double dlNow, double ulTotal,
double ulNow)
{
auto engine = reinterpret_cast<StreamProgressThread *>(const_cast<void *>(localData));
ENGINE_CHECK(engine != nullptr, return -1, "Can not find engine");
unsigned int percent = 0;
if (dlTotal > 0) {
percent = dlNow / dlTotal * DOWNLOAD_FINISH_PERCENT;
ENGINE_LOGI("StreamProgressThread DownloadProgress dlTotal:%{public}f,dlNow:%{public}f,ulTotal:%{public}f, "
"ulNow:%{public}f,percent:%{public}d", dlTotal, dlNow, ulTotal, ulNow, percent);
} else {
ENGINE_LOGI("StreamProgressThread DownloadProgress dlTotal:%{public}f is less than 0", dlTotal);
return 0;
}
return engine->DealExitOrCancel() ? -1 : 0;
}
size_t StreamProgressThread::WriteFunc(uint8_t *ptr, size_t size, size_t nmemb, void *localData)
{
ENGINE_LOGI("StreamProgressThread WriteFunc");
auto engine = reinterpret_cast<StreamProgressThread *>(const_cast<void *>(localData));
ENGINE_CHECK(engine != nullptr, return -1, "Can not find engine");
size_t totalSize = size * nmemb;
uint8_t *buffer = engine->buffer_;
size_t& bufferPos = engine->bufferPos_;
int64_t& totalFileSize = engine->totalFileSize_;
int64_t& downloadedSize = engine->downloadedSize_;
size_t processed = 0;
while (processed < totalSize) {
size_t spaceLeft = BUFFER_SIZE - bufferPos - 1;
size_t bytesToCopy = std::min(spaceLeft, totalSize - processed);
errno_t err = memcpy_s(buffer + bufferPos, BUFFER_SIZE - bufferPos, ptr + processed, bytesToCopy);
if (err != 0) {
ENGINE_LOGE("WriteFunc memcpy_s failed with error code: %{public}d", err);
return -1;
}
bufferPos += bytesToCopy;
processed += bytesToCopy;
if (bufferPos == (BUFFER_SIZE - 1) || (downloadedSize + processed) == totalFileSize) {
ENGINE_LOGI("StreamProgressThread WriteFunc buffer full bufferPos:%{public}zu "
"(downloadedSize+processed):%{public}" PRId64 "",
bufferPos, downloadedSize + processed);
#ifndef UPDATER_UT
int32_t ret = SysInstaller::SysInstallerKitsImpl::GetInstance().ProcessStreamData(buffer, bufferPos);
if (ret != 0) {
ENGINE_LOGE("WriteFunc ProcessStreamData failed");
return -1;
}
#endif
(void)memset_s(buffer, BUFFER_SIZE, 0, BUFFER_SIZE);
bufferPos = 0;
}
}
downloadedSize += totalSize;
return totalSize;
}
bool StreamProgressThread::DealExitOrCancel()
{
if (exitDownload_) {
ENGINE_LOGI("DealExitOrCancel exit Download");
return true;
}
if (isCancel_.load()) {
ENGINE_LOGI("DealExitOrCancel install task cancel");
return true;
}
return false;
}
}
}