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