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 
28 namespace OHOS {
29 namespace UpdateEngine {
30 bool ProgressThread::isNoNet_ = false;
31 bool ProgressThread::isCancel_ = false;
32 
~ProgressThread()33 ProgressThread::~ProgressThread() {}
34 
QuitDownloadThread()35 void 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 
StartProgress()50 int32_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 
StopProgress()63 void ProgressThread::StopProgress()
64 {
65     std::unique_lock<std::mutex> lock(mutex_);
66     isWake_ = true;
67     isExitThread_ = false;
68     condition_.notify_one();
69 }
70 
ExecuteThreadFunc()71 void 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 
StartDownload(const std::string & fileName,const std::string & url)93 int32_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 
StopDownload()103 void DownloadThread::StopDownload()
104 {
105     ENGINE_LOGI("StopDownload");
106     exitDownload_ = true;
107     StopProgress();
108     curl_global_cleanup();
109 }
110 
ProcessThreadExecute()111 bool 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 
ProcessThreadExit()159 void 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 
DownloadCallback(uint32_t percent,UpgradeStatus status,const std::string & error)172 int32_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 
DownloadProgress(const void * localData,double dlTotal,double dlNow,double ulTotal,double ulNow)210 int32_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 
WriteFunc(void * ptr,size_t size,size_t nmemb,const void * stream)221 size_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 
GetLocalFileLength(const std::string & fileName)226 size_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 
DealAbnormal(uint32_t percent)242 bool 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 
FileOpen(const std::string & fileName,const std::string & mode)262 FILE* 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