/indra/viewer_components/updater/llupdatedownloader.cpp

https://bitbucket.org/lindenlab/viewer-beta/ · C++ · 526 lines · 400 code · 94 blank · 32 comment · 73 complexity · 5e1fdfb35071213eeb9d5e440a8af5ac MD5 · raw file

  1. /**
  2. * @file llupdatedownloader.cpp
  3. *
  4. * $LicenseInfo:firstyear=2010&license=viewerlgpl$
  5. * Second Life Viewer Source Code
  6. * Copyright (C) 2010, Linden Research, Inc.
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU Lesser General Public
  10. * License as published by the Free Software Foundation;
  11. * version 2.1 of the License only.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * Lesser General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Lesser General Public
  19. * License along with this library; if not, write to the Free Software
  20. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  21. *
  22. * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
  23. * $/LicenseInfo$
  24. */
  25. #include "linden_common.h"
  26. #include "llupdatedownloader.h"
  27. #include <stdexcept>
  28. #include <boost/format.hpp>
  29. #include <boost/lexical_cast.hpp>
  30. #include <curl/curl.h>
  31. #include "lldir.h"
  32. #include "llevents.h"
  33. #include "llfile.h"
  34. #include "llmd5.h"
  35. #include "llsd.h"
  36. #include "llsdserialize.h"
  37. #include "llthread.h"
  38. #include "llupdaterservice.h"
  39. #include "llcurl.h"
  40. class LLUpdateDownloader::Implementation:
  41. public LLThread
  42. {
  43. public:
  44. Implementation(LLUpdateDownloader::Client & client);
  45. ~Implementation();
  46. void cancel(void);
  47. void download(LLURI const & uri,
  48. std::string const & hash,
  49. std::string const & updateVersion,
  50. bool required);
  51. bool isDownloading(void);
  52. size_t onHeader(void * header, size_t size);
  53. size_t onBody(void * header, size_t size);
  54. int onProgress(double downloadSize, double bytesDownloaded);
  55. void resume(void);
  56. void setBandwidthLimit(U64 bytesPerSecond);
  57. private:
  58. curl_off_t mBandwidthLimit;
  59. bool mCancelled;
  60. LLUpdateDownloader::Client & mClient;
  61. CURL * mCurl;
  62. LLSD mDownloadData;
  63. llofstream mDownloadStream;
  64. unsigned char mDownloadPercent;
  65. std::string mDownloadRecordPath;
  66. curl_slist * mHeaderList;
  67. void initializeCurlGet(std::string const & url, bool processHeader);
  68. void resumeDownloading(size_t startByte);
  69. void run(void);
  70. void startDownloading(LLURI const & uri, std::string const & hash);
  71. void throwOnCurlError(CURLcode code);
  72. bool validateDownload(void);
  73. LOG_CLASS(LLUpdateDownloader::Implementation);
  74. };
  75. namespace {
  76. class DownloadError:
  77. public std::runtime_error
  78. {
  79. public:
  80. DownloadError(const char * message):
  81. std::runtime_error(message)
  82. {
  83. ; // No op.
  84. }
  85. };
  86. const char * gSecondLifeUpdateRecord = "SecondLifeUpdateDownload.xml";
  87. };
  88. // LLUpdateDownloader
  89. //-----------------------------------------------------------------------------
  90. std::string LLUpdateDownloader::downloadMarkerPath(void)
  91. {
  92. return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, gSecondLifeUpdateRecord);
  93. }
  94. LLUpdateDownloader::LLUpdateDownloader(Client & client):
  95. mImplementation(new LLUpdateDownloader::Implementation(client))
  96. {
  97. ; // No op.
  98. }
  99. void LLUpdateDownloader::cancel(void)
  100. {
  101. mImplementation->cancel();
  102. }
  103. void LLUpdateDownloader::download(LLURI const & uri,
  104. std::string const & hash,
  105. std::string const & updateVersion,
  106. bool required)
  107. {
  108. mImplementation->download(uri, hash, updateVersion, required);
  109. }
  110. bool LLUpdateDownloader::isDownloading(void)
  111. {
  112. return mImplementation->isDownloading();
  113. }
  114. void LLUpdateDownloader::resume(void)
  115. {
  116. mImplementation->resume();
  117. }
  118. void LLUpdateDownloader::setBandwidthLimit(U64 bytesPerSecond)
  119. {
  120. mImplementation->setBandwidthLimit(bytesPerSecond);
  121. }
  122. // LLUpdateDownloader::Implementation
  123. //-----------------------------------------------------------------------------
  124. namespace {
  125. size_t write_function(void * data, size_t blockSize, size_t blocks, void * downloader)
  126. {
  127. size_t bytes = blockSize * blocks;
  128. return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->onBody(data, bytes);
  129. }
  130. size_t header_function(void * data, size_t blockSize, size_t blocks, void * downloader)
  131. {
  132. size_t bytes = blockSize * blocks;
  133. return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->onHeader(data, bytes);
  134. }
  135. int progress_callback(void * downloader,
  136. double dowloadTotal,
  137. double downloadNow,
  138. double uploadTotal,
  139. double uploadNow)
  140. {
  141. return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->
  142. onProgress(dowloadTotal, downloadNow);
  143. }
  144. }
  145. LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client & client):
  146. LLThread("LLUpdateDownloader"),
  147. mBandwidthLimit(0),
  148. mCancelled(false),
  149. mClient(client),
  150. mCurl(0),
  151. mDownloadPercent(0),
  152. mHeaderList(0)
  153. {
  154. CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case.
  155. llverify(code == CURLE_OK); // TODO: real error handling here.
  156. }
  157. LLUpdateDownloader::Implementation::~Implementation()
  158. {
  159. if(isDownloading())
  160. {
  161. cancel();
  162. shutdown();
  163. }
  164. else
  165. {
  166. ; // No op.
  167. }
  168. if(mCurl)
  169. {
  170. LLCurl::deleteEasyHandle(mCurl);
  171. }
  172. }
  173. void LLUpdateDownloader::Implementation::cancel(void)
  174. {
  175. mCancelled = true;
  176. }
  177. void LLUpdateDownloader::Implementation::download(LLURI const & uri,
  178. std::string const & hash,
  179. std::string const & updateVersion,
  180. bool required)
  181. {
  182. if(isDownloading()) mClient.downloadError("download in progress");
  183. mDownloadRecordPath = downloadMarkerPath();
  184. mDownloadData = LLSD();
  185. mDownloadData["required"] = required;
  186. mDownloadData["update_version"] = updateVersion;
  187. try {
  188. startDownloading(uri, hash);
  189. } catch(DownloadError const & e) {
  190. mClient.downloadError(e.what());
  191. }
  192. }
  193. bool LLUpdateDownloader::Implementation::isDownloading(void)
  194. {
  195. return !isStopped();
  196. }
  197. void LLUpdateDownloader::Implementation::resume(void)
  198. {
  199. mCancelled = false;
  200. if(isDownloading()) {
  201. mClient.downloadError("download in progress");
  202. }
  203. mDownloadRecordPath = downloadMarkerPath();
  204. llifstream dataStream(mDownloadRecordPath);
  205. if(!dataStream) {
  206. mClient.downloadError("no download marker");
  207. return;
  208. }
  209. LLSDSerialize::fromXMLDocument(mDownloadData, dataStream);
  210. if(!mDownloadData.asBoolean()) {
  211. mClient.downloadError("no download information in marker");
  212. return;
  213. }
  214. std::string filePath = mDownloadData["path"].asString();
  215. try {
  216. if(LLFile::isfile(filePath)) {
  217. llstat fileStatus;
  218. LLFile::stat(filePath, &fileStatus);
  219. if(fileStatus.st_size != mDownloadData["size"].asInteger()) {
  220. resumeDownloading(fileStatus.st_size);
  221. } else if(!validateDownload()) {
  222. LLFile::remove(filePath);
  223. download(LLURI(mDownloadData["url"].asString()),
  224. mDownloadData["hash"].asString(),
  225. mDownloadData["update_version"].asString(),
  226. mDownloadData["required"].asBoolean());
  227. } else {
  228. mClient.downloadComplete(mDownloadData);
  229. }
  230. } else {
  231. download(LLURI(mDownloadData["url"].asString()),
  232. mDownloadData["hash"].asString(),
  233. mDownloadData["update_version"].asString(),
  234. mDownloadData["required"].asBoolean());
  235. }
  236. } catch(DownloadError & e) {
  237. mClient.downloadError(e.what());
  238. }
  239. }
  240. void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond)
  241. {
  242. if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean()) {
  243. llassert(mCurl != 0);
  244. mBandwidthLimit = bytesPerSecond;
  245. CURLcode code = curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit);
  246. if(code != CURLE_OK) LL_WARNS("UpdateDownload") <<
  247. "unable to change dowload bandwidth" << LL_ENDL;
  248. } else {
  249. mBandwidthLimit = bytesPerSecond;
  250. }
  251. }
  252. size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
  253. {
  254. char const * headerPtr = reinterpret_cast<const char *> (buffer);
  255. std::string header(headerPtr, headerPtr + size);
  256. size_t colonPosition = header.find(':');
  257. if(colonPosition == std::string::npos) return size; // HTML response; ignore.
  258. if(header.substr(0, colonPosition) == "Content-Length") {
  259. try {
  260. size_t firstDigitPos = header.find_first_of("0123456789", colonPosition);
  261. size_t lastDigitPos = header.find_last_of("0123456789");
  262. std::string contentLength = header.substr(firstDigitPos, lastDigitPos - firstDigitPos + 1);
  263. size_t size = boost::lexical_cast<size_t>(contentLength);
  264. LL_INFOS("UpdateDownload") << "download size is " << size << LL_ENDL;
  265. mDownloadData["size"] = LLSD(LLSD::Integer(size));
  266. llofstream odataStream(mDownloadRecordPath);
  267. LLSDSerialize::toPrettyXML(mDownloadData, odataStream);
  268. } catch (std::exception const & e) {
  269. LL_WARNS("UpdateDownload") << "unable to read content length ("
  270. << e.what() << ")" << LL_ENDL;
  271. }
  272. } else {
  273. ; // No op.
  274. }
  275. return size;
  276. }
  277. size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size)
  278. {
  279. if(mCancelled) return 0; // Forces a write error which will halt curl thread.
  280. if((size == 0) || (buffer == 0)) return 0;
  281. mDownloadStream.write(reinterpret_cast<const char *>(buffer), size);
  282. if(mDownloadStream.bad()) {
  283. return 0;
  284. } else {
  285. return size;
  286. }
  287. }
  288. int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double bytesDownloaded)
  289. {
  290. int downloadPercent = static_cast<int>(100. * (bytesDownloaded / downloadSize));
  291. if(downloadPercent > mDownloadPercent) {
  292. mDownloadPercent = downloadPercent;
  293. LLSD event;
  294. event["pump"] = LLUpdaterService::pumpName();
  295. LLSD payload;
  296. payload["type"] = LLSD(LLUpdaterService::PROGRESS);
  297. payload["download_size"] = downloadSize;
  298. payload["bytes_downloaded"] = bytesDownloaded;
  299. event["payload"] = payload;
  300. LLEventPumps::instance().obtain("mainlooprepeater").post(event);
  301. LL_INFOS("UpdateDownload") << "progress event " << payload << LL_ENDL;
  302. } else {
  303. ; // Keep events to a reasonalbe number.
  304. }
  305. return 0;
  306. }
  307. void LLUpdateDownloader::Implementation::run(void)
  308. {
  309. CURLcode code = curl_easy_perform(mCurl);
  310. mDownloadStream.close();
  311. if(code == CURLE_OK) {
  312. LLFile::remove(mDownloadRecordPath);
  313. if(validateDownload()) {
  314. LL_INFOS("UpdateDownload") << "download successful" << LL_ENDL;
  315. mClient.downloadComplete(mDownloadData);
  316. } else {
  317. LL_INFOS("UpdateDownload") << "download failed hash check" << LL_ENDL;
  318. std::string filePath = mDownloadData["path"].asString();
  319. if(filePath.size() != 0) LLFile::remove(filePath);
  320. mClient.downloadError("failed hash check");
  321. }
  322. } else if(mCancelled && (code == CURLE_WRITE_ERROR)) {
  323. LL_INFOS("UpdateDownload") << "download canceled by user" << LL_ENDL;
  324. // Do not call back client.
  325. } else {
  326. LL_WARNS("UpdateDownload") << "download failed with error '" <<
  327. curl_easy_strerror(code) << "'" << LL_ENDL;
  328. LLFile::remove(mDownloadRecordPath);
  329. if(mDownloadData.has("path")) LLFile::remove(mDownloadData["path"].asString());
  330. mClient.downloadError("curl error");
  331. }
  332. if(mHeaderList) {
  333. curl_slist_free_all(mHeaderList);
  334. mHeaderList = 0;
  335. }
  336. }
  337. void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader)
  338. {
  339. if(mCurl == 0)
  340. {
  341. mCurl = LLCurl::newEasyHandle();
  342. }
  343. else
  344. {
  345. curl_easy_reset(mCurl);
  346. }
  347. if(mCurl == 0) throw DownloadError("failed to initialize curl");
  348. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true));
  349. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true));
  350. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function));
  351. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, this));
  352. if(processHeader) {
  353. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERFUNCTION, &header_function));
  354. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERDATA, this));
  355. }
  356. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPGET, true));
  357. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_URL, url.c_str()));
  358. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_PROGRESSFUNCTION, &progress_callback));
  359. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_PROGRESSDATA, this));
  360. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOPROGRESS, false));
  361. // if it's a required update set the bandwidth limit to 0 (unlimited)
  362. curl_off_t limit = mDownloadData["required"].asBoolean() ? 0 : mBandwidthLimit;
  363. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, limit));
  364. mDownloadPercent = 0;
  365. }
  366. void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)
  367. {
  368. LL_INFOS("UpdateDownload") << "resuming download from " << mDownloadData["url"].asString()
  369. << " at byte " << startByte << LL_ENDL;
  370. initializeCurlGet(mDownloadData["url"].asString(), false);
  371. // The header 'Range: bytes n-' will request the bytes remaining in the
  372. // source begining with byte n and ending with the last byte.
  373. boost::format rangeHeaderFormat("Range: bytes=%u-");
  374. rangeHeaderFormat % startByte;
  375. mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str());
  376. if(mHeaderList == 0) throw DownloadError("cannot add Range header");
  377. throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList));
  378. mDownloadStream.open(mDownloadData["path"].asString(),
  379. std::ios_base::out | std::ios_base::binary | std::ios_base::app);
  380. start();
  381. }
  382. void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std::string const & hash)
  383. {
  384. mDownloadData["url"] = uri.asString();
  385. mDownloadData["hash"] = hash;
  386. mDownloadData["current_version"] = ll_get_version();
  387. LLSD path = uri.pathArray();
  388. if(path.size() == 0) throw DownloadError("no file path");
  389. std::string fileName = path[path.size() - 1].asString();
  390. std::string filePath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, fileName);
  391. mDownloadData["path"] = filePath;
  392. LL_INFOS("UpdateDownload") << "downloading " << filePath
  393. << " from " << uri.asString() << LL_ENDL;
  394. LL_INFOS("UpdateDownload") << "hash of file is " << hash << LL_ENDL;
  395. llofstream dataStream(mDownloadRecordPath);
  396. LLSDSerialize::toPrettyXML(mDownloadData, dataStream);
  397. mDownloadStream.open(filePath, std::ios_base::out | std::ios_base::binary);
  398. initializeCurlGet(uri.asString(), true);
  399. start();
  400. }
  401. void LLUpdateDownloader::Implementation::throwOnCurlError(CURLcode code)
  402. {
  403. if(code != CURLE_OK) {
  404. const char * errorString = curl_easy_strerror(code);
  405. if(errorString != 0) {
  406. throw DownloadError(curl_easy_strerror(code));
  407. } else {
  408. throw DownloadError("unknown curl error");
  409. }
  410. } else {
  411. ; // No op.
  412. }
  413. }
  414. bool LLUpdateDownloader::Implementation::validateDownload(void)
  415. {
  416. std::string filePath = mDownloadData["path"].asString();
  417. llifstream fileStream(filePath, std::ios_base::in | std::ios_base::binary);
  418. if(!fileStream) return false;
  419. std::string hash = mDownloadData["hash"].asString();
  420. if(hash.size() != 0) {
  421. LL_INFOS("UpdateDownload") << "checking hash..." << LL_ENDL;
  422. char digest[33];
  423. LLMD5(fileStream).hex_digest(digest);
  424. if(hash != digest) {
  425. LL_WARNS("UpdateDownload") << "download hash mismatch; expeted " << hash <<
  426. " but download is " << digest << LL_ENDL;
  427. }
  428. return hash == digest;
  429. } else {
  430. return true; // No hash check provided.
  431. }
  432. }