1 // Copyright (C) 2013 James Turner - zakalawe@mac.com
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Library General Public
5 // License as published by the Free Software Foundation; either
6 // version 2 of the License, or (at your option) any later version.
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 // Library General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 #include <simgear/package/Install.hxx>
20 #include <boost/foreach.hpp>
23 #include <simgear/package/unzip.h>
24 #include <simgear/package/md5.h>
26 #include <simgear/structure/exception.hxx>
27 #include <simgear/package/Catalog.hxx>
28 #include <simgear/package/Package.hxx>
29 #include <simgear/package/Root.hxx>
30 #include <simgear/io/HTTPRequest.hxx>
31 #include <simgear/io/HTTPClient.hxx>
32 #include <simgear/misc/sg_dir.hxx>
33 #include <simgear/misc/strutils.hxx>
36 void fill_memory_filefunc (zlib_filefunc_def*);
43 class Install::PackageArchiveDownloader : public HTTP::Request
46 PackageArchiveDownloader(InstallRef aOwner) :
47 HTTP::Request("" /* dummy URL */),
50 m_urls = m_owner->package()->downloadUrls();
52 throw sg_exception("no package download URLs");
55 // TODO randomise order of m_urls
57 m_extractPath = aOwner->path().dir();
58 m_extractPath.append("_DOWNLOAD"); // add some temporary value
63 virtual std::string url() const
65 return m_urls.front();
68 virtual void responseHeadersComplete()
73 memset(&m_md5, 0, sizeof(SG_MD5_CTX));
77 virtual void gotBodyData(const char* s, int n)
79 m_buffer += std::string(s, n);
80 SG_MD5Update(&m_md5, (unsigned char*) s, n);
82 m_owner->installProgress(m_buffer.size(), responseLength());
87 if (responseCode() != 200) {
88 SG_LOG(SG_GENERAL, SG_ALERT, "download failure");
89 doFailure(Delegate::FAIL_DOWNLOAD);
93 unsigned char digest[MD5_DIGEST_LENGTH];
94 SG_MD5Final(digest, &m_md5);
95 std::string const hex_md5 =
96 strutils::encodeHex(digest, MD5_DIGEST_LENGTH);
98 if (hex_md5 != m_owner->package()->md5()) {
99 SG_LOG(SG_GENERAL, SG_ALERT, "md5 verification failed:\n"
100 << "\t" << hex_md5 << "\n\t"
101 << m_owner->package()->md5() << "\n\t"
102 << "downloading from:" << url());
103 doFailure(Delegate::FAIL_CHECKSUM);
107 if (!extractUnzip()) {
108 SG_LOG(SG_GENERAL, SG_WARN, "zip extraction failed");
109 doFailure(Delegate::FAIL_EXTRACT);
113 if (m_owner->path().exists()) {
114 //std::cout << "removing existing path" << std::endl;
115 Dir destDir(m_owner->path());
116 destDir.remove(true /* recursive */);
119 m_extractPath.append(m_owner->package()->id());
120 bool ok = m_extractPath.rename(m_owner->path());
122 doFailure(Delegate::FAIL_FILESYSTEM);
126 m_owner->m_revision = m_owner->package()->revision();
127 m_owner->writeRevisionFile();
128 m_owner->installResult(Delegate::FAIL_SUCCESS);
133 void extractCurrentFile(unzFile zip, char* buffer, size_t bufferSize)
135 unz_file_info fileInfo;
136 unzGetCurrentFileInfo(zip, &fileInfo,
138 NULL, 0, /* extra field */
139 NULL, 0 /* comment field */);
141 std::string name(buffer);
142 // no absolute paths, no 'up' traversals
143 // we could also look for suspicious file extensions here (forbid .dll, .exe, .so)
144 if ((name[0] == '/') || (name.find("../") != std::string::npos) || (name.find("..\\") != std::string::npos)) {
145 throw sg_format_exception("Bad zip path", name);
148 if (fileInfo.uncompressed_size == 0) {
149 // assume it's a directory for now
150 // since we create parent directories when extracting
151 // a path, we're done here
155 int result = unzOpenCurrentFile(zip);
156 if (result != UNZ_OK) {
157 throw sg_io_exception("opening current zip file failed", sg_location(name));
160 std::ofstream outFile;
162 SGPath path(m_extractPath);
165 // create enclosing directory heirarchy as required
166 Dir parentDir(path.dir());
167 if (!parentDir.exists()) {
168 bool ok = parentDir.create(0755);
170 throw sg_io_exception("failed to create directory heirarchy for extraction", path.c_str());
174 outFile.open(path.c_str(), std::ios::binary | std::ios::trunc | std::ios::out);
175 if (outFile.fail()) {
176 throw sg_io_exception("failed to open output file for writing", path.c_str());
180 int bytes = unzReadCurrentFile(zip, buffer, bufferSize);
182 throw sg_io_exception("unzip failure reading curent archive", sg_location(name));
183 } else if (bytes == 0) {
186 outFile.write(buffer, bytes);
191 unzCloseCurrentFile(zip);
197 zlib_filefunc_def memoryAccessFuncs;
198 fill_memory_filefunc(&memoryAccessFuncs);
200 char bufferName[128];
201 snprintf(bufferName, 128, "%p+%lx", m_buffer.data(), m_buffer.size());
202 unzFile zip = unzOpen2(bufferName, &memoryAccessFuncs);
204 const size_t BUFFER_SIZE = 32 * 1024;
205 void* buf = malloc(BUFFER_SIZE);
208 int result = unzGoToFirstFile(zip);
209 if (result != UNZ_OK) {
210 throw sg_exception("failed to go to first file in archive");
214 extractCurrentFile(zip, (char*) buf, BUFFER_SIZE);
215 result = unzGoToNextFile(zip);
216 if (result == UNZ_END_OF_LIST_OF_FILE) {
218 } else if (result != UNZ_OK) {
219 throw sg_io_exception("failed to go to next file in the archive");
222 } catch (sg_exception& e) {
231 void doFailure(Delegate::FailureCode aReason)
233 Dir dir(m_extractPath);
234 dir.remove(true /* recursive */);
236 if (m_urls.size() == 1) {
237 std::cout << "failure:" << aReason << std::endl;
238 m_owner->installResult(aReason);
242 std::cout << "retrying download" << std::endl;
243 m_urls.erase(m_urls.begin()); // pop first URL
249 std::string m_buffer;
250 SGPath m_extractPath;
253 ////////////////////////////////////////////////////////////////////
254 Install::Install(PackageRef aPkg, const SGPath& aPath) :
258 _status(Delegate::FAIL_IN_PROGRESS)
261 m_package->catalog()->registerInstall(this);
266 m_package->catalog()->unregisterInstall(this);
269 InstallRef Install::createFromPath(const SGPath& aPath, CatalogRef aCat)
271 std::string id = aPath.file();
272 PackageRef pkg = aCat->getPackageById(id);
274 throw sg_exception("no package with id:" + id);
276 return new Install(pkg, aPath);
279 void Install::parseRevision()
281 SGPath revisionFile = m_path;
282 revisionFile.append(".revision");
283 if (!revisionFile.exists()) {
288 std::ifstream f(revisionFile.c_str(), std::ios::in);
292 void Install::writeRevisionFile()
294 SGPath revisionFile = m_path;
295 revisionFile.append(".revision");
296 std::ofstream f(revisionFile.c_str(), std::ios::out | std::ios::trunc);
297 f << m_revision << std::endl;
300 bool Install::hasUpdate() const
302 return m_package->revision() > m_revision;
305 void Install::startUpdate()
308 return; // already active
311 m_download = new PackageArchiveDownloader(this);
312 m_package->catalog()->root()->makeHTTPRequest(m_download);
313 m_package->catalog()->root()->startInstall(this);
316 bool Install::uninstall()
319 if (!d.remove(true)) {
320 SG_LOG(SG_GENERAL, SG_ALERT, "package uninstall failed: couldn't remove path " << m_path);
324 m_package->catalog()->unregisterInstall(this);
328 bool Install::isDownloading() const
330 return (m_download != NULL);
333 //------------------------------------------------------------------------------
334 Install* Install::done(const Callback& cb)
336 if( _status == Delegate::FAIL_SUCCESS )
339 _cb_done.push_back(cb);
344 //------------------------------------------------------------------------------
345 Install* Install::fail(const Callback& cb)
347 if( _status != Delegate::FAIL_SUCCESS
348 && _status != Delegate::FAIL_IN_PROGRESS )
351 _cb_fail.push_back(cb);
356 //------------------------------------------------------------------------------
357 Install* Install::always(const Callback& cb)
359 if( _status != Delegate::FAIL_IN_PROGRESS )
362 _cb_always.push_back(cb);
367 //------------------------------------------------------------------------------
368 Install* Install::progress(const ProgressCallback& cb)
370 _cb_progress.push_back(cb);
374 //------------------------------------------------------------------------------
375 void Install::installResult(Delegate::FailureCode aReason)
377 if (aReason == Delegate::FAIL_SUCCESS) {
378 m_package->catalog()->root()->finishInstall(this);
381 m_package->catalog()->root()->failedInstall(this, aReason);
388 //------------------------------------------------------------------------------
389 void Install::installProgress(unsigned int aBytes, unsigned int aTotal)
391 m_package->catalog()->root()->installProgress(this, aBytes, aTotal);
392 _cb_progress(this, aBytes, aTotal);
396 } // of namespace pkg
398 } // of namespace simgear