1/* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16#include "progress_thread.h" 17 18#include <unistd.h> 19#include <file_utils.h> 20 21#include "curl/curl.h" 22#include "curl/easy.h" 23 24#include "firmware_common.h" 25#include "update_define.h" 26#include "update_log.h" 27 28namespace OHOS { 29namespace UpdateEngine { 30bool ProgressThread::isNoNet_ = false; 31bool ProgressThread::isCancel_ = false; 32 33ProgressThread::~ProgressThread() {} 34 35void ProgressThread::QuitDownloadThread() 36{ 37 { 38 std::unique_lock<std::mutex> lock(mutex_); 39 isWake_ = true; 40 isExitThread_ = true; 41 condition_.notify_one(); 42 } 43 if (pDealThread_ != nullptr) { 44 pDealThread_->join(); 45 delete pDealThread_; 46 pDealThread_ = nullptr; 47 } 48} 49 50int32_t ProgressThread::StartProgress() 51{ 52 std::unique_lock<std::mutex> lock(mutex_); 53 if (pDealThread_ == nullptr) { 54 pDealThread_ = new (std::nothrow)std::thread([this] { this->ExecuteThreadFunc(); }); 55 ENGINE_CHECK(pDealThread_ != nullptr, return -1, "Failed to create thread"); 56 } 57 ENGINE_LOGI("StartProgress"); 58 isWake_ = true; 59 condition_.notify_one(); 60 return 0; 61} 62 63void ProgressThread::StopProgress() 64{ 65 std::unique_lock<std::mutex> lock(mutex_); 66 isWake_ = true; 67 isExitThread_ = false; 68 condition_.notify_one(); 69} 70 71void ProgressThread::ExecuteThreadFunc() 72{ 73 while (1) { 74 { 75 std::unique_lock<std::mutex> lock(mutex_); 76 while (!isWake_) { 77 ENGINE_LOGI("ExecuteThreadFunc wait"); 78 condition_.wait(lock); 79 } 80 if (isExitThread_) { 81 break; 82 } 83 isWake_ = false; 84 } 85 if (!ProcessThreadExecute()) { 86 return; 87 } 88 } 89 // thread exit and release resource 90 ProcessThreadExit(); 91} 92 93int32_t DownloadThread::StartDownload(const std::string &fileName, const std::string &url) 94{ 95 ENGINE_LOGI("StartDownload downloadFileName_ %s, serverUrl_ = %s", downloadFileName_.c_str(), serverUrl_.c_str()); 96 downloadFileName_ = fileName; 97 serverUrl_ = url; 98 exitDownload_ = false; 99 curl_global_init(CURL_GLOBAL_ALL); 100 return StartProgress(); 101} 102 103void DownloadThread::StopDownload() 104{ 105 ENGINE_LOGI("StopDownload"); 106 exitDownload_ = true; 107 StopProgress(); 108 curl_global_cleanup(); 109} 110 111bool DownloadThread::ProcessThreadExecute() 112{ 113 ENGINE_LOGI("ProcessThreadExecute"); 114 packageSize_ = GetLocalFileLength(downloadFileName_); 115 ENGINE_LOGI("download packageSize_: %zu ", packageSize_); 116 bool findDot = (downloadFileName_.find("/.") != std::string::npos) || 117 (downloadFileName_.find("./") != std::string::npos); 118 ENGINE_CHECK(!findDot, 119 DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, "Failed to check file"); 120 return true, "Failed to check file %s", downloadFileName_.c_str()); 121 downloadFile_ = FileOpen(downloadFileName_, "ab+"); 122 ENGINE_CHECK(downloadFile_ != nullptr, 123 DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, "Failed ot open file"); 124 return true, "Failed to open file %s", downloadFileName_.c_str()); 125 126 downloadHandle_ = curl_easy_init(); 127 ENGINE_CHECK(downloadHandle_ != nullptr, 128 ProcessThreadExit(); 129 DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, "Failed to init curl"); 130 return true, "Failed to init curl"); 131 132 curl_easy_setopt(downloadHandle_, CURLOPT_TIMEOUT, TIMEOUT_FOR_DOWNLOAD); 133 curl_easy_setopt(downloadHandle_, CURLOPT_CONNECTTIMEOUT, TIMEOUT_FOR_CONNECT); 134 curl_easy_setopt(downloadHandle_, CURLOPT_URL, serverUrl_.c_str()); 135 curl_easy_setopt(downloadHandle_, CURLOPT_WRITEDATA, downloadFile_); 136 curl_easy_setopt(downloadHandle_, CURLOPT_WRITEFUNCTION, WriteFunc); 137 if (packageSize_ > 0) { 138 curl_easy_setopt(downloadHandle_, CURLOPT_RESUME_FROM_LARGE, static_cast<curl_off_t>(packageSize_)); 139 } 140 curl_easy_setopt(downloadHandle_, CURLOPT_NOPROGRESS, 0L); 141 curl_easy_setopt(downloadHandle_, CURLOPT_PROGRESSDATA, this); 142 curl_easy_setopt(downloadHandle_, CURLOPT_PROGRESSFUNCTION, DownloadProgress); 143 CURLcode res = curl_easy_perform(downloadHandle_); 144 if (res != CURLE_OK) { 145 ProcessThreadExit(); 146 ENGINE_LOGI("Failed to download res %s", curl_easy_strerror(res)); 147 if (res != CURLE_ABORTED_BY_CALLBACK) { // cancel by user, do not callback 148 DownloadCallback(0, UpgradeStatus::DOWNLOAD_FAIL, 149 std::to_string(CAST_INT(DownloadEndReason::CURL_ERROR))); 150 } 151 } else { 152 ProcessThreadExit(); 153 ENGINE_LOGI("Success to download"); 154 DownloadCallback(DOWNLOAD_FINISH_PERCENT, UpgradeStatus::DOWNLOAD_SUCCESS, ""); 155 } 156 return false; 157} 158 159void DownloadThread::ProcessThreadExit() 160{ 161 ENGINE_LOGI("ProcessThreadExit"); 162 if (downloadHandle_ != nullptr) { 163 curl_easy_cleanup(downloadHandle_); 164 } 165 downloadHandle_ = nullptr; 166 if (downloadFile_ != nullptr) { 167 fclose(downloadFile_); 168 } 169 downloadFile_ = nullptr; 170} 171 172int32_t DownloadThread::DownloadCallback(uint32_t percent, UpgradeStatus status, const std::string &error) 173{ 174 if (exitDownload_) { 175 ENGINE_LOGI("StopDownloadCallback"); 176 return -1; 177 } 178 ENGINE_CHECK_NO_LOG(!DealAbnormal(percent), 179 ENGINE_LOGI("DealAbnormal"); 180 return -1); 181 if (downloadProgress_.status == status && downloadProgress_.percent == percent) { 182 // 避免回调过于频繁 183 return 0; 184 } 185 ENGINE_LOGI("DownloadCallback percent %d, status %d, exitDownload_ %d, error %s, downloadFileName_ %s", 186 percent, CAST_INT(status), exitDownload_ ? 1 : 0, error.c_str(), downloadFileName_.c_str()); 187 if (status == UpgradeStatus::DOWNLOAD_FAIL) { 188 if (access(downloadFileName_.c_str(), 0) == 0) { 189 unlink(downloadFileName_.c_str()); 190 } 191 } else if (percent != DOWNLOAD_FINISH_PERCENT && 192 (percent < (downloadProgress_.percent + DOWNLOAD_PERIOD_PERCENT))) { 193 return 0; 194 } 195 196 // wait until the download is complete, and then make a notification 197 if (percent == DOWNLOAD_FINISH_PERCENT 198 && status == UpgradeStatus::DOWNLOADING) { 199 return 0; 200 } 201 downloadProgress_.endReason = error; 202 downloadProgress_.percent = percent; 203 downloadProgress_.status = status; 204 if (callback_ != nullptr) { 205 callback_(serverUrl_, downloadFileName_, downloadProgress_); 206 } 207 return 0; 208} 209 210int32_t DownloadThread::DownloadProgress(const void *localData, 211 double dlTotal, double dlNow, double ulTotal, double ulNow) 212{ 213 ENGINE_CHECK_NO_LOG(dlTotal > 0, return 0); 214 auto engine = reinterpret_cast<DownloadThread*>(const_cast<void*>(localData)); 215 ENGINE_CHECK(engine != nullptr, return -1, "Can not find engine"); 216 double curr = engine->GetPackageSize(); 217 unsigned int percent = (dlNow + curr) / (curr + dlTotal) * DOWNLOAD_FINISH_PERCENT; 218 return engine->DownloadCallback(percent, UpgradeStatus::DOWNLOADING, ""); 219} 220 221size_t DownloadThread::WriteFunc(void *ptr, size_t size, size_t nmemb, const void *stream) 222{ 223 return fwrite(ptr, size, nmemb, reinterpret_cast<FILE*>(const_cast<void*>(stream))); 224} 225 226size_t DownloadThread::GetLocalFileLength(const std::string &fileName) 227{ 228 bool findDot = (fileName.find("/.") != std::string::npos) || (fileName.find("./") != std::string::npos); 229 ENGINE_CHECK_NO_LOG(!findDot, return 0); 230 231 FILE* fp = FileOpen(fileName, "r"); 232 ENGINE_CHECK_NO_LOG(fp != nullptr, return 0); 233 int ret = fseek(fp, 0, SEEK_END); 234 ENGINE_CHECK_NO_LOG(ret == 0, fclose(fp); 235 return 0); 236 size_t length = (size_t)ftell(fp); 237 ret = fclose(fp); 238 ENGINE_CHECK_NO_LOG(ret == 0, return 0); 239 return length; 240} 241 242bool DownloadThread::DealAbnormal(uint32_t percent) 243{ 244 bool dealResult = false; 245 if (isNoNet_ || isCancel_) { 246 ENGINE_LOGI("No network or user cancel"); 247 downloadProgress_.endReason = isNoNet_ ? std::to_string(CAST_INT(DownloadEndReason::NET_NOT_AVAILIABLE)) : 248 std::to_string(CAST_INT(DownloadEndReason::CANCEL)); 249 downloadProgress_.percent = percent; 250 downloadProgress_.status = isNoNet_ ? UpgradeStatus::DOWNLOAD_FAIL : UpgradeStatus::DOWNLOAD_CANCEL; 251 if (isCancel_) { 252 isCancel_ = false; 253 } 254 dealResult = true; 255 if (callback_ != nullptr) { 256 callback_(serverUrl_, downloadFileName_, downloadProgress_); 257 } 258 } 259 return dealResult; 260} 261 262FILE* DownloadThread::FileOpen(const std::string &fileName, const std::string &mode) 263{ 264 if (fileName.empty() || fileName.size() > PATH_MAX) { 265 ENGINE_LOGI("DownloadThread file is empty or exceed path_max"); 266 return nullptr; 267 } 268 std::string fileDir = fileName; 269 auto pos = fileDir.find_last_of("/"); 270 if (pos != std::string::npos) { 271 fileDir.erase(pos + 1); 272 } else { 273 ENGINE_LOGI("DownloadThread file %{public}s, mode: %{public}s", fileName.c_str(), mode.c_str()); 274 return nullptr; 275 } 276 277 char *path = realpath(fileDir.c_str(), NULL); 278 if (path == NULL) { 279 ENGINE_LOGI("DownloadThread file %{public}s, mode: %{public}s", fileName.c_str(), mode.c_str()); 280 return nullptr; 281 } 282 free(path); 283 FILE* fp = fopen(fileName.c_str(), mode.c_str()); 284 return fp; 285} 286} // namespace UpdateEngine 287} // namespace OHOS 288