]> git.mxchange.org Git - simgear.git/blob - simgear/package/Install.cxx
Make nasal/iolib.h available to flightgear (for io.open)
[simgear.git] / simgear / package / Install.cxx
1 // Copyright (C) 2013  James Turner - zakalawe@mac.com
2 //
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.
7 //
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.
12 //
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.
16 //
17
18 #include <simgear/package/Install.hxx>
19
20 #include <boost/foreach.hpp>
21 #include <fstream>
22
23 #include <simgear/package/unzip.h>
24 #include <simgear/package/md5.h>
25
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>
34
35 extern "C" {
36     void fill_memory_filefunc (zlib_filefunc_def*);
37 }
38
39 namespace simgear {
40     
41 namespace pkg {
42
43 class Install::PackageArchiveDownloader : public HTTP::Request
44 {
45 public:
46     PackageArchiveDownloader(InstallRef aOwner) :
47         HTTP::Request("" /* dummy URL */),
48         m_owner(aOwner)
49     {
50         m_urls = m_owner->package()->downloadUrls();
51         if (m_urls.empty()) {
52             throw sg_exception("no package download URLs");
53         }
54         
55         // TODO randomise order of m_urls
56         
57         m_extractPath = aOwner->path().dir();
58         m_extractPath.append("_DOWNLOAD"); // add some temporary value
59         
60     }
61     
62 protected:
63     virtual std::string url() const
64     {
65         return m_urls.front();
66     }
67     
68     virtual void responseHeadersComplete()
69     {
70         Dir d(m_extractPath);
71         d.create(0755);        
72         
73         memset(&m_md5, 0, sizeof(SG_MD5_CTX));
74         SG_MD5Init(&m_md5);
75     }
76     
77     virtual void gotBodyData(const char* s, int n)
78     {
79         m_buffer += std::string(s, n);
80         SG_MD5Update(&m_md5, (unsigned char*) s, n);
81         
82         m_owner->installProgress(m_buffer.size(), responseLength());
83     }
84     
85     virtual void onDone()
86     {
87         if (responseCode() != 200) {
88             SG_LOG(SG_GENERAL, SG_ALERT, "download failure");
89             doFailure(Delegate::FAIL_DOWNLOAD);
90             return;
91         }
92
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);
97
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);
104             return;
105         }
106         
107         if (!extractUnzip()) {
108             SG_LOG(SG_GENERAL, SG_WARN, "zip extraction failed");
109             doFailure(Delegate::FAIL_EXTRACT);
110             return;
111         }
112                   
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 */);
117         }
118         
119         m_extractPath.append(m_owner->package()->id());
120         bool ok = m_extractPath.rename(m_owner->path());
121         if (!ok) {
122             doFailure(Delegate::FAIL_FILESYSTEM);
123             return;
124         }
125         
126         m_owner->m_revision = m_owner->package()->revision();
127         m_owner->writeRevisionFile();
128         m_owner->installResult(Delegate::FAIL_SUCCESS);
129     }
130     
131 private:
132
133     void extractCurrentFile(unzFile zip, char* buffer, size_t bufferSize)
134     {
135         unz_file_info fileInfo;
136         unzGetCurrentFileInfo(zip, &fileInfo, 
137             buffer, bufferSize, 
138             NULL, 0,  /* extra field */
139             NULL, 0 /* comment field */);
140             
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);
146         }
147         
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
152             return;
153         }
154         
155         int result = unzOpenCurrentFile(zip);
156         if (result != UNZ_OK) {
157             throw sg_io_exception("opening current zip file failed", sg_location(name));
158         }
159             
160         std::ofstream outFile;
161         bool eof = false;
162         SGPath path(m_extractPath);
163         path.append(name);
164                         
165     // create enclosing directory heirarchy as required
166         Dir parentDir(path.dir());
167         if (!parentDir.exists()) {
168             bool ok = parentDir.create(0755);
169             if (!ok) {
170                 throw sg_io_exception("failed to create directory heirarchy for extraction", path.c_str());
171             }
172         }
173             
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());
177         }
178             
179         while (!eof) {
180             int bytes = unzReadCurrentFile(zip, buffer, bufferSize);
181             if (bytes < 0) {
182                 throw sg_io_exception("unzip failure reading curent archive", sg_location(name));
183             } else if (bytes == 0) {
184                 eof = true;
185             } else {
186                 outFile.write(buffer, bytes);
187             }
188         }
189             
190         outFile.close();
191         unzCloseCurrentFile(zip);
192     }
193     
194     bool extractUnzip()
195     {
196         bool result = true;
197         zlib_filefunc_def memoryAccessFuncs;
198         fill_memory_filefunc(&memoryAccessFuncs);
199            
200         char bufferName[128];
201         snprintf(bufferName, 128, "%p+%lx", m_buffer.data(), m_buffer.size());
202         unzFile zip = unzOpen2(bufferName, &memoryAccessFuncs);
203         
204         const size_t BUFFER_SIZE = 32 * 1024;
205         void* buf = malloc(BUFFER_SIZE);
206         
207         try {
208             int result = unzGoToFirstFile(zip);
209             if (result != UNZ_OK) {
210                 throw sg_exception("failed to go to first file in archive");
211             }
212             
213             while (true) {
214                 extractCurrentFile(zip, (char*) buf, BUFFER_SIZE);
215                 result = unzGoToNextFile(zip);
216                 if (result == UNZ_END_OF_LIST_OF_FILE) {
217                     break;
218                 } else if (result != UNZ_OK) {
219                     throw sg_io_exception("failed to go to next file in the archive");
220                 }
221             }
222         } catch (sg_exception& e) {
223             result = false;
224         }
225         
226         free(buf);
227         unzClose(zip);
228         return result;
229     }
230         
231     void doFailure(Delegate::FailureCode aReason)
232     {
233         Dir dir(m_extractPath);
234         dir.remove(true /* recursive */);
235
236         if (m_urls.size() == 1) {
237             std::cout << "failure:" << aReason << std::endl;
238             m_owner->installResult(aReason);
239             return;
240         }
241         
242         std::cout << "retrying download" << std::endl;
243         m_urls.erase(m_urls.begin()); // pop first URL
244     }
245     
246     InstallRef m_owner;
247     string_list m_urls;
248     SG_MD5_CTX m_md5;
249     std::string m_buffer;
250     SGPath m_extractPath;
251 };
252
253 ////////////////////////////////////////////////////////////////////
254 Install::Install(PackageRef aPkg, const SGPath& aPath) :
255     m_package(aPkg),
256     m_path(aPath),
257     m_download(NULL),
258     _status(Delegate::FAIL_IN_PROGRESS)
259 {
260     parseRevision();
261     m_package->catalog()->registerInstall(this);
262 }
263   
264 Install::~Install()
265 {
266     m_package->catalog()->unregisterInstall(this);
267 }
268
269 InstallRef Install::createFromPath(const SGPath& aPath, CatalogRef aCat)
270 {
271     std::string id = aPath.file();
272     PackageRef pkg = aCat->getPackageById(id);
273     if (!pkg)
274         throw sg_exception("no package with id:" + id);
275     
276     return new Install(pkg, aPath);
277 }
278
279 void Install::parseRevision()
280 {
281     SGPath revisionFile = m_path;
282     revisionFile.append(".revision");
283     if (!revisionFile.exists()) {
284         m_revision = 0;
285         return;
286     }
287     
288     std::ifstream f(revisionFile.c_str(), std::ios::in);
289     f >> m_revision;
290 }
291
292 void Install::writeRevisionFile()
293 {
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;
298 }
299
300 bool Install::hasUpdate() const
301 {
302     return m_package->revision() > m_revision;
303 }
304
305 void Install::startUpdate()
306 {
307     if (m_download) {
308         return; // already active
309     }
310     
311     m_download = new PackageArchiveDownloader(this);
312     m_package->catalog()->root()->makeHTTPRequest(m_download);
313     m_package->catalog()->root()->startInstall(this);
314 }
315
316 void Install::uninstall()
317 {
318     Dir d(m_path);
319     d.remove(true);
320     m_package->catalog()->unregisterInstall(this);
321 }
322
323 //------------------------------------------------------------------------------
324 Install* Install::done(const Callback& cb)
325 {
326   if( _status == Delegate::FAIL_SUCCESS )
327     cb(this);
328   else
329     _cb_done.push_back(cb);
330
331   return this;
332 }
333
334 //------------------------------------------------------------------------------
335 Install* Install::fail(const Callback& cb)
336 {
337   if(    _status != Delegate::FAIL_SUCCESS
338       && _status != Delegate::FAIL_IN_PROGRESS )
339     cb(this);
340   else
341     _cb_fail.push_back(cb);
342
343   return this;
344 }
345
346 //------------------------------------------------------------------------------
347 Install* Install::always(const Callback& cb)
348 {
349   if( _status != Delegate::FAIL_IN_PROGRESS )
350     cb(this);
351   else
352     _cb_always.push_back(cb);
353
354   return this;
355 }
356
357 //------------------------------------------------------------------------------
358 Install* Install::progress(const ProgressCallback& cb)
359 {
360   _cb_progress.push_back(cb);
361   return this;
362 }
363
364 //------------------------------------------------------------------------------
365 void Install::installResult(Delegate::FailureCode aReason)
366 {
367     if (aReason == Delegate::FAIL_SUCCESS) {
368         m_package->catalog()->root()->finishInstall(this);
369         _cb_done(this);
370     } else {
371         m_package->catalog()->root()->failedInstall(this, aReason);
372         _cb_fail(this);
373     }
374
375     _cb_always(this);
376 }
377
378 //------------------------------------------------------------------------------
379 void Install::installProgress(unsigned int aBytes, unsigned int aTotal)
380 {
381   m_package->catalog()->root()->installProgress(this, aBytes, aTotal);
382   _cb_progress(this, aBytes, aTotal);
383 }
384
385
386 } // of namespace pkg
387
388 } // of namespace simgear