Tizen 2.0 Release
[framework/web/wrt-commons.git] / modules / core / src / zip_input.cpp
1 /*
2  * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16 /*
17  * @file        zip_input.cpp
18  * @author      Przemyslaw Dobrowolski (p.dobrowolsk@samsung.com)
19  * @version     1.0
20  * @brief       This file is the implementation file of zip input
21  */
22 #include <stddef.h>
23 #include <iomanip>
24 #include <sys/stat.h>
25 #include <sys/mman.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <dpl/zip_input.h>
29 #include <dpl/scoped_close.h>
30 #include <dpl/binary_queue.h>
31 #include <dpl/scoped_free.h>
32 #include <memory>
33 #include <dpl/scoped_array.h>
34 #include <dpl/foreach.h>
35 #include <dpl/log/log.h>
36 #include <minizip/framework_minizip.h>
37 #include <new>
38
39 namespace DPL
40 {
41 namespace // anonymous
42 {
43 const size_t EXTRACT_BUFFER_SIZE = 4096;
44
45 class ScopedUnzClose
46 {
47 private:
48     unzFile m_file;
49
50 public:
51     ScopedUnzClose(unzFile file)
52         : m_file(file)
53     {
54     }
55
56     ~ScopedUnzClose()
57     {
58         if (!m_file)
59             return;
60
61         if (unzClose(m_file) != UNZ_OK)
62             LogPedantic("Failed to close zip input file");
63     }
64
65     unzFile Release()
66     {
67         unzFile file = m_file;
68
69         m_file = NULL;
70
71         return file;
72     }
73 };
74 } // namespace anonymous
75
76 /*
77  * Seekable multiplexing device
78  *
79  * Explanation:
80  * Minizip library lacks serious support for multithreaded
81  * access to zip files. Thus, they cannot be easily extracted
82  * simulateously. Here is introduced seekable device which does
83  * have a context with seek index for each file. File is mapped to
84  * memory and because of that no real synchronization is needed.
85  * Memory addresses can be indexed.
86  *
87  * About generalization:
88  * To achieve the same results on abstract input device, there must be
89  * provided a mechanism to read data from random address without synchronization.
90  * In other words: stateless. As described above, stateless property can be
91  * achieved via memory mapping.
92  */
93 class Device
94 {
95 private:
96     int m_handle;
97     off64_t m_size; // file mapping size
98     unsigned char *m_address; // mapping base address
99
100     struct File
101     {
102         off64_t offset;
103         Device *device;
104
105         File(Device *d)
106             : offset(0),
107               device(d)
108         {
109         }
110     };
111
112 public:
113     Device(const std::string &fileName)
114     {
115         LogPedantic("Creating file mapping");
116         // Open device and map it to user space
117         int file = TEMP_FAILURE_RETRY(open(fileName.c_str(), O_RDONLY));
118
119         if (file == -1)
120         {
121             int error = errno;
122             ThrowMsg(ZipInput::Exception::OpenFailed,
123                      "Failed to open file. errno = " << error);
124         }
125
126         // Scoped close on file
127         ScopedClose scopedClose(file);
128
129         // Calculate file size
130         off64_t size = lseek64(file, 0, SEEK_END);
131
132         if (size == static_cast<off64_t>(-1))
133         {
134             int error = errno;
135             ThrowMsg(ZipInput::Exception::OpenFailed,
136                      "Failed to seek file. errno = " << error);
137         }
138
139         // Map file to usespace
140         void *address = mmap(0, static_cast<size_t>(size),
141                              PROT_READ, MAP_SHARED, file, 0);
142
143         if (address == MAP_FAILED)
144         {
145             int error = errno;
146             ThrowMsg(ZipInput::Exception::OpenFailed,
147                      "Failed to map file. errno = " << error);
148         }
149
150         // Release scoped close
151         m_handle = scopedClose.Release();
152
153         // Save mapped up address
154         m_size = size;
155         m_address = static_cast<unsigned char *>(address);
156
157         LogPedantic("Created file mapping: " << fileName <<
158                     " of size: " << m_size <<
159                     " at address: " << std::hex << static_cast<void *>(m_address));
160     }
161
162     ~Device()
163     {
164         // Close mapping
165         if (munmap(m_address, static_cast<size_t>(m_size)) == -1)
166         {
167             int error = errno;
168             LogPedantic("Failed to munmap file. errno = " << error);
169         }
170
171         // Close file descriptor
172         if (close(m_handle) == -1)
173         {
174             int error = errno;
175             LogPedantic("Failed to close file. errno = " << error);
176         }
177     }
178
179     // zlib_filefunc64_def interface: files
180     static voidpf ZCALLBACK open64_file(voidpf opaque,
181                                         const void* /*filename*/,
182                                         int /*mode*/)
183     {
184         Device *device = static_cast<Device *>(opaque);
185
186         // Open file for master device
187         return new File(device);
188     }
189
190     static uLong ZCALLBACK read_file(voidpf opaque,
191                                      voidpf pstream,
192                                      void* buf,
193                                      uLong size)
194     {
195         Device *device = static_cast<Device *>(opaque);
196         File *deviceFile = static_cast<File *>(pstream);
197
198         // Check if offset is out of bounds
199         if (deviceFile->offset >= device->m_size)
200         {
201             LogPedantic("Device: read offset out of bounds");
202             return -1;
203         }
204
205         off64_t bytesLeft = device->m_size -
206                             deviceFile->offset;
207
208         off64_t bytesToRead;
209
210         // Calculate bytes to read
211         if (static_cast<off64_t>(size) > bytesLeft)
212             bytesToRead = bytesLeft;
213         else
214             bytesToRead = static_cast<off64_t>(size);
215
216         // Do copy
217         memcpy(buf,
218                device->m_address + deviceFile->offset,
219                static_cast<size_t>(bytesToRead));
220
221         // Increment file offset
222         deviceFile->offset += bytesToRead;
223
224         // Return bytes that were actually read
225         return static_cast<uLong>(bytesToRead);
226     }
227
228     static uLong ZCALLBACK write_file(voidpf /*opaque*/,
229                                       voidpf /*stream*/,
230                                       const void* /*buf*/,
231                                       uLong /*size*/)
232     {
233         // Not supported by device
234         LogPedantic("Unsupported function called!");
235         return -1;
236     }
237
238     static int ZCALLBACK close_file(voidpf /*opaque*/, voidpf stream)
239     {
240         File *deviceFile = static_cast<File *>(stream);
241
242         // Delete file
243         delete deviceFile;
244
245         // Always OK
246         return 0;
247     }
248
249     static int ZCALLBACK testerror_file(voidpf /*opaque*/, voidpf /*stream*/)
250     {
251         // No errors
252         return 0;
253     }
254
255     static ZPOS64_T ZCALLBACK tell64_file(voidpf /*opaque*/, voidpf stream)
256     {
257         File *deviceFile = static_cast<File *>(stream);
258
259         return static_cast<ZPOS64_T>(deviceFile->offset);
260     }
261
262     static long ZCALLBACK seek64_file(voidpf opaque,
263                                       voidpf stream,
264                                       ZPOS64_T offset,
265                                       int origin)
266     {
267         Device *device = static_cast<Device *>(opaque);
268         File *deviceFile = static_cast<File *>(stream);
269
270         switch (origin)
271         {
272             case ZLIB_FILEFUNC_SEEK_SET:
273                 deviceFile->offset = static_cast<off64_t>(offset);
274
275                 break;
276
277             case ZLIB_FILEFUNC_SEEK_CUR:
278                 deviceFile->offset += static_cast<off64_t>(offset);
279
280                 break;
281
282             case ZLIB_FILEFUNC_SEEK_END:
283                 deviceFile->offset =
284                     device->m_size -
285                     static_cast<off64_t>(offset);
286
287                 break;
288
289             default:
290                 return -1;
291         }
292
293         return 0;
294     }
295 };
296
297 ZipInput::ZipInput(const std::string &fileName)
298     : m_device(NULL),
299       m_numberOfFiles(0),
300       m_globalComment(),
301       m_fileInfos()
302 {
303     LogPedantic("Zip input file: " << fileName);
304
305     // Create master device
306     LogPedantic("Creating master device");
307     std::unique_ptr<Device> device(new Device(fileName));
308
309     // Open master file
310     zlib_filefunc64_def interface;
311     interface.zopen64_file = &Device::open64_file;
312     interface.zread_file = &Device::read_file;
313     interface.zwrite_file = &Device::write_file;
314     interface.ztell64_file = &Device::tell64_file;
315     interface.zseek64_file = &Device::seek64_file;
316     interface.zclose_file = &Device::close_file;
317     interface.zerror_file = &Device::testerror_file;
318     interface.opaque = device.get();
319
320     LogPedantic("Opening zip file");
321     unzFile file = unzOpen2_64(NULL, &interface);
322
323     if (file == NULL)
324     {
325         LogPedantic("Failed to open zip file");
326
327          // Some errror occured
328         ThrowMsg(Exception::OpenFailed,
329                  "Failed to open zip file: " << fileName);
330     }
331
332     // Begin scope
333     ScopedUnzClose scopedUnzClose(file);
334
335     // Read contents
336     ReadGlobalInfo(file);
337     ReadGlobalComment(file);
338     ReadInfos(file);
339
340     // Release scoped unz close
341     m_masterFile = scopedUnzClose.Release();
342     m_device = device.release();
343
344     LogPedantic("Zip file opened");
345 }
346
347 ZipInput::~ZipInput()
348 {
349     // Close zip
350     if (unzClose(static_cast<unzFile>(m_masterFile)) != UNZ_OK)
351         LogPedantic("Failed to close zip input file");
352
353     // Close device
354     delete m_device;
355 }
356
357 void ZipInput::ReadGlobalInfo(void *masterFile)
358 {
359     // Read number of entries and global comment
360     unz_global_info globalInfo;
361
362     if (unzGetGlobalInfo(static_cast<unzFile>(masterFile),
363                          &globalInfo) != UNZ_OK)
364     {
365         LogPedantic("Failed to read zip global info");
366
367         ThrowMsg(Exception::ReadGlobalInfoFailed,
368                  "Failed to read global info");
369     }
370
371     m_numberOfFiles = static_cast<size_t>(globalInfo.number_entry);
372     m_globalCommentSize = static_cast<size_t>(globalInfo.size_comment);
373
374     LogPedantic("Number of files: " << m_numberOfFiles);
375     LogPedantic("Global comment size: " << m_globalCommentSize);
376 }
377
378 void ZipInput::ReadGlobalComment(void *masterFile)
379 {
380     ScopedArray<char> comment(new char[m_globalCommentSize + 1]);
381
382     if (unzGetGlobalComment(static_cast<unzFile>(masterFile),
383                             comment.Get(),
384                             m_globalCommentSize + 1) != UNZ_OK)
385     {
386         LogPedantic("Failed to read zip global comment");
387
388         ThrowMsg(Exception::ReadGlobalCommentFailed,
389                  "Failed to read global comment");
390     }
391
392     m_globalComment = comment.Get();
393     LogPedantic("Global comment: " << m_globalComment);
394 }
395
396 void ZipInput::ReadInfos(void *masterFile)
397 {
398     // Read infos
399     m_fileInfos.reserve(m_numberOfFiles);
400
401     if (unzGoToFirstFile(static_cast<unzFile>(masterFile)) != UNZ_OK)
402     {
403         LogPedantic("Failed to go to first file");
404         ThrowMsg(Exception::SeekFileFailed, "Failed to seek first file");
405     }
406
407     for (size_t i = 0; i < m_numberOfFiles; ++i)
408     {
409         unz_file_pos_s filePos;
410
411         if (unzGetFilePos(static_cast<unzFile>(masterFile),
412                           &filePos) != UNZ_OK)
413         {
414             LogPedantic("Failed to get file pos");
415             ThrowMsg(Exception::FileInfoFailed, "Failed to get zip file info");
416         }
417
418         unz_file_info64 fileInfo;
419
420         if (unzGetCurrentFileInfo64(static_cast<unzFile>(masterFile),
421                                     &fileInfo,
422                                     NULL,
423                                     0,
424                                     NULL,
425                                     0,
426                                     NULL,
427                                     0) != UNZ_OK)
428         {
429             LogPedantic("Failed to get file pos");
430             ThrowMsg(Exception::FileInfoFailed, "Failed to get zip file info");
431         }
432
433         ScopedArray<char> fileName(new char[fileInfo.size_filename + 1]);
434         ScopedArray<char> fileComment(new char[fileInfo.size_file_comment + 1]);
435
436         if (unzGetCurrentFileInfo64(static_cast<unzFile>(masterFile),
437                                     &fileInfo,
438                                     fileName.Get(),
439                                     fileInfo.size_filename + 1,
440                                     NULL,
441                                     0,
442                                     fileComment.Get(),
443                                     fileInfo.size_file_comment + 1) != UNZ_OK)
444         {
445             LogPedantic("Failed to get file pos");
446             ThrowMsg(Exception::FileInfoFailed, "Failed to get zip file info");
447         }
448
449         m_fileInfos.push_back(
450             FileInfo(
451                 FileHandle(
452                     static_cast<size_t>(filePos.pos_in_zip_directory),
453                     static_cast<size_t>(filePos.num_of_file)
454                 ),
455                 std::string(fileName.Get()),
456                 std::string(fileComment.Get()),
457                 static_cast<off64_t>(fileInfo.compressed_size),
458                 static_cast<off64_t>(fileInfo.uncompressed_size)
459             )
460         );
461
462         // If this is not the last file, go to next one
463         if (i != m_numberOfFiles - 1)
464         {
465             if (unzGoToNextFile(
466                     static_cast<unzFile>(masterFile))!= UNZ_OK)
467             {
468                 LogPedantic("Failed to go to next file");
469
470                 ThrowMsg(Exception::FileInfoFailed,
471                          "Failed to go to next file");
472             }
473         }
474     }
475 }
476
477 ZipInput::const_iterator ZipInput::begin() const
478 {
479     return m_fileInfos.begin();
480 }
481
482 ZipInput::const_iterator ZipInput::end() const
483 {
484     return m_fileInfos.end();
485 }
486
487 ZipInput::const_reverse_iterator ZipInput::rbegin() const
488 {
489     return m_fileInfos.rbegin();
490 }
491
492 ZipInput::const_reverse_iterator ZipInput::rend() const
493 {
494     return m_fileInfos.rend();
495 }
496
497 ZipInput::size_type ZipInput::size() const
498 {
499     return m_fileInfos.size();
500 }
501
502 ZipInput::File *ZipInput::OpenFile(const std::string &fileName)
503 {
504     FOREACH(iterator, m_fileInfos)
505     {
506         if (iterator->name == fileName)
507         {
508             return new File(m_device, iterator->handle);
509         }
510     }
511
512     ThrowMsg(Exception::OpenFileFailed,
513              "Failed to open zip file: " << fileName);
514 }
515
516 ZipInput::File::File(class Device *device, FileHandle handle)
517 {
518     // Open file file
519     zlib_filefunc64_def interface;
520     interface.zopen64_file = &Device::open64_file;
521     interface.zread_file = &Device::read_file;
522     interface.zwrite_file = &Device::write_file;
523     interface.ztell64_file = &Device::tell64_file;
524     interface.zseek64_file = &Device::seek64_file;
525     interface.zclose_file = &Device::close_file;
526     interface.zerror_file = &Device::testerror_file;
527     interface.opaque = device;
528
529     LogPedantic("Opening zip file");
530     unzFile file = unzOpen2_64(NULL, &interface);
531
532     if (file == NULL)
533     {
534         LogPedantic("Failed to open zip file");
535
536          // Some errror occured
537         ThrowMsg(ZipInput::Exception::OpenFileFailed,
538                  "Failed to open zip file");
539     }
540
541     // Begin scope
542     ScopedUnzClose scopedUnzClose(file);
543
544     // Look up file handle
545     unz64_file_pos filePos =
546     {
547         static_cast<ZPOS64_T>(handle.first),
548         static_cast<ZPOS64_T>(handle.second)
549     };
550
551     if (unzGoToFilePos64(file, &filePos) != UNZ_OK)
552     {
553         LogPedantic("Failed to seek to zip file");
554
555          // Some errror occured
556         ThrowMsg(ZipInput::Exception::OpenFileFailed,
557                  "Failed to open zip file");
558     }
559
560     // Open current file for reading
561     if (unzOpenCurrentFile(file) != UNZ_OK)
562     {
563         LogPedantic("Failed to open current zip file");
564
565          // Some errror occured
566         ThrowMsg(ZipInput::Exception::OpenFileFailed,
567                  "Failed to open current zip file");
568     }
569
570     // Release scoped unz close
571     m_file = scopedUnzClose.Release();
572
573     LogPedantic("Zip file opened");
574 }
575
576 ZipInput::File::~File()
577 {
578     // Close current file for reading
579     if (unzCloseCurrentFile(static_cast<unzFile>(m_file)) != UNZ_OK)
580         LogPedantic("Failed to close current zip input file");
581
582     // Close zip file
583     if (unzClose(static_cast<unzFile>(m_file)) != UNZ_OK)
584         LogPedantic("Failed to close zip input file");
585 }
586
587 DPL::BinaryQueueAutoPtr ZipInput::File::Read(size_t size)
588 {
589     // Do not even try to unzip if requested zero bytes
590     if (size == 0)
591         return DPL::BinaryQueueAutoPtr(new DPL::BinaryQueue());
592
593     // Calc data to read
594     size_t sizeToRead = size > EXTRACT_BUFFER_SIZE ?
595                         EXTRACT_BUFFER_SIZE :
596                         size;
597
598     // Extract zip file data (one-copy)
599     ScopedFree<void> rawBuffer(malloc(sizeToRead));
600
601     if (!rawBuffer)
602         throw std::bad_alloc();
603
604     // Do unpack
605     int bytes = unzReadCurrentFile(static_cast<unzFile>(m_file),
606                                    rawBuffer.Get(),
607                                    sizeToRead);
608
609     // Internal unzipper error
610     if (bytes < 0)
611     {
612         LogPedantic("Extract failed. Error: " << bytes);
613
614         ThrowMsg(ZipInput::Exception::ReadFileFailed,
615                  "Failed to extract file with error: " << bytes);
616     }
617
618     // Data was read (may be zero bytes)
619     DPL::BinaryQueueAutoPtr buffer(new DPL::BinaryQueue());
620
621     buffer->AppendUnmanaged(rawBuffer.Get(),
622                             static_cast<size_t>(bytes),
623                             &DPL::BinaryQueue::BufferDeleterFree,
624                             NULL);
625
626     rawBuffer.Release();
627
628     return buffer;
629 }
630
631 const std::string &ZipInput::GetGlobalComment() const
632 {
633     return m_globalComment;
634 }
635
636 bool ZipInput::empty() const
637 {
638     return m_fileInfos.empty();
639 }
640 } // namespace DPL