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