Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Implement VSI large file api for /vsi7z/ and /vsirar/
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "cpl_port.h"
30 : #include "cpl_vsi_virtual.h"
31 :
32 : #ifndef HAVE_LIBARCHIVE
33 :
34 : /************************************************************************/
35 : /* VSIInstall7zFileHandler() */
36 : /************************************************************************/
37 :
38 : /*!
39 : \brief Install /vsi7z/ 7zip file system handler (requires libarchive)
40 :
41 : \verbatim embed:rst
42 : See :ref:`/vsi7z/ documentation <vsi7z>`
43 : \endverbatim
44 :
45 : @since GDAL 3.7
46 : */
47 0 : void VSIInstall7zFileHandler(void)
48 : {
49 : // dummy
50 0 : }
51 :
52 : /************************************************************************/
53 : /* VSIInstallRarFileHandler() */
54 : /************************************************************************/
55 :
56 : /*!
57 : \brief Install /vsirar/ RAR file system handler (requires libarchive)
58 :
59 : \verbatim embed:rst
60 : See :ref:`/vsirar/ documentation <vsirar>`
61 : \endverbatim
62 :
63 : @since GDAL 3.7
64 : */
65 0 : void VSIInstallRarFileHandler(void)
66 : {
67 : // dummy
68 0 : }
69 :
70 : #else
71 :
72 : //! @cond Doxygen_Suppress
73 :
74 : #include <algorithm>
75 : #include <limits>
76 : #include <memory>
77 :
78 : // libarchive
79 : #ifdef USE_INTERNAL_LIBARCHIVE
80 : #include "archive_gdal_config.h"
81 : #endif
82 :
83 : #include "archive.h"
84 : #include "archive_entry.h"
85 :
86 : /************************************************************************/
87 : /* ==================================================================== */
88 : /* VSILibArchiveClientData */
89 : /* ==================================================================== */
90 : /************************************************************************/
91 :
92 : struct VSILibArchiveClientData
93 : {
94 : CPL_DISALLOW_COPY_ASSIGN(VSILibArchiveClientData)
95 :
96 : const std::string m_osFilename;
97 : VSIVirtualHandle *m_poBaseHandle = nullptr;
98 : std::vector<GByte> m_abyBuffer{};
99 :
100 : VSILibArchiveClientData(const char *pszFilename) : m_osFilename(pszFilename)
101 : {
102 : m_abyBuffer.resize(4096);
103 : }
104 :
105 : static int openCbk(struct archive *pArchive, void *pClientData)
106 : {
107 : auto poClientData = static_cast<VSILibArchiveClientData *>(pClientData);
108 : CPLDebug("VSIARCH", "Opening %s", poClientData->m_osFilename.c_str());
109 : poClientData->m_poBaseHandle = reinterpret_cast<VSIVirtualHandle *>(
110 : VSIFOpenL(poClientData->m_osFilename.c_str(), "rb"));
111 : if (poClientData->m_poBaseHandle == nullptr)
112 : {
113 : archive_set_error(pArchive, -1, "Cannot open file");
114 : return ARCHIVE_FATAL;
115 : }
116 : return ARCHIVE_OK;
117 : }
118 :
119 : static int closeCbk(struct archive *pArchive, void *pClientData)
120 : {
121 : auto poClientData = static_cast<VSILibArchiveClientData *>(pClientData);
122 : int ret = 0;
123 : if (poClientData->m_poBaseHandle)
124 : {
125 : ret = poClientData->m_poBaseHandle->Close();
126 : delete poClientData->m_poBaseHandle;
127 : }
128 : delete poClientData;
129 : if (ret == 0)
130 : return ARCHIVE_OK;
131 : archive_set_error(pArchive, -1, "Cannot close file");
132 : return ARCHIVE_FATAL;
133 : }
134 :
135 : static la_ssize_t readCbk(struct archive *, void *pClientData,
136 : const void **ppBuffer)
137 : {
138 : auto poClientData = static_cast<VSILibArchiveClientData *>(pClientData);
139 : *ppBuffer = poClientData->m_abyBuffer.data();
140 : return static_cast<la_ssize_t>(poClientData->m_poBaseHandle->Read(
141 : poClientData->m_abyBuffer.data(), 1,
142 : poClientData->m_abyBuffer.size()));
143 : }
144 :
145 : static la_int64_t seekCkb(struct archive *, void *pClientData,
146 : la_int64_t offset, int whence)
147 : {
148 : auto poClientData = static_cast<VSILibArchiveClientData *>(pClientData);
149 : if (whence == SEEK_CUR && offset < 0)
150 : {
151 : whence = SEEK_SET;
152 : offset = poClientData->m_poBaseHandle->Tell() + offset;
153 : }
154 : if (poClientData->m_poBaseHandle->Seek(
155 : static_cast<vsi_l_offset>(offset), whence) != 0)
156 : return ARCHIVE_FATAL;
157 : return static_cast<la_int64_t>(poClientData->m_poBaseHandle->Tell());
158 : }
159 : };
160 :
161 : /************************************************************************/
162 : /* VSILibArchiveReadOpen() */
163 : /************************************************************************/
164 :
165 : /**Open an archive, with the base handle being a VSIVirtualHandle* */
166 : static int VSILibArchiveReadOpen(struct archive *pArchive,
167 : const char *pszFilename)
168 : {
169 : archive_read_set_seek_callback(pArchive, VSILibArchiveClientData::seekCkb);
170 : return archive_read_open(pArchive, new VSILibArchiveClientData(pszFilename),
171 : VSILibArchiveClientData::openCbk,
172 : VSILibArchiveClientData::readCbk,
173 : VSILibArchiveClientData::closeCbk);
174 : }
175 :
176 : /************************************************************************/
177 : /* VSICreateArchiveHandle() */
178 : /************************************************************************/
179 :
180 : static struct archive *VSICreateArchiveHandle(const std::string &osFSPrefix)
181 : {
182 : auto pArchive = archive_read_new();
183 :
184 : if (osFSPrefix == "/vsi7z")
185 : {
186 : archive_read_support_format_7zip(pArchive);
187 : }
188 : else
189 : {
190 : archive_read_support_format_rar(pArchive);
191 : archive_read_support_format_rar5(pArchive);
192 : }
193 :
194 : return pArchive;
195 : }
196 :
197 : /************************************************************************/
198 : /* ==================================================================== */
199 : /* VSILibArchiveReader */
200 : /* ==================================================================== */
201 : /************************************************************************/
202 :
203 : class VSILibArchiveReader final : public VSIArchiveReader
204 : {
205 : CPL_DISALLOW_COPY_ASSIGN(VSILibArchiveReader)
206 :
207 : std::string m_osArchiveFileName;
208 : struct archive *m_pArchive;
209 : std::string m_osPrefix;
210 : bool m_bFirst = true;
211 : std::string m_osFilename{};
212 : GUIntBig m_nFilesize = 0;
213 : GIntBig m_nMTime = 0;
214 :
215 : public:
216 : VSILibArchiveReader(const char *pszArchiveFileName,
217 : struct archive *pArchive, const std::string &osPrefix)
218 : : m_osArchiveFileName(pszArchiveFileName), m_pArchive(pArchive),
219 : m_osPrefix(osPrefix)
220 : {
221 : }
222 :
223 : ~VSILibArchiveReader() override;
224 :
225 : struct archive *GetArchiveHandler()
226 : {
227 : return m_pArchive;
228 : }
229 :
230 : int GotoFirstFileForced();
231 :
232 : virtual int GotoFirstFile() override;
233 : virtual int GotoNextFile() override;
234 : virtual VSIArchiveEntryFileOffset *GetFileOffset() override;
235 :
236 : virtual GUIntBig GetFileSize() override
237 : {
238 : return m_nFilesize;
239 : }
240 :
241 : virtual CPLString GetFileName() override
242 : {
243 : return m_osFilename;
244 : }
245 :
246 : virtual GIntBig GetModifiedTime() override
247 : {
248 : return m_nMTime;
249 : }
250 :
251 : virtual int GotoFileOffset(VSIArchiveEntryFileOffset *pOffset) override;
252 :
253 : int GotoFileOffsetForced(VSIArchiveEntryFileOffset *pOffset);
254 : };
255 :
256 : /************************************************************************/
257 : /* ~VSILibArchiveReader() */
258 : /************************************************************************/
259 :
260 : VSILibArchiveReader::~VSILibArchiveReader()
261 : {
262 : archive_free(m_pArchive);
263 : }
264 :
265 : /************************************************************************/
266 : /* GotoFirstFile() */
267 : /************************************************************************/
268 :
269 : int VSILibArchiveReader::GotoFirstFile()
270 : {
271 : if (!m_bFirst)
272 : {
273 : archive_free(m_pArchive);
274 :
275 : m_pArchive = VSICreateArchiveHandle(m_osPrefix);
276 :
277 : if (VSILibArchiveReadOpen(m_pArchive, m_osArchiveFileName.c_str()))
278 : {
279 : CPLDebug("VSIARCH", "%s: %s", m_osArchiveFileName.c_str(),
280 : archive_error_string(m_pArchive));
281 : return false;
282 : }
283 : m_bFirst = true;
284 : }
285 : return GotoNextFile();
286 : }
287 :
288 : /************************************************************************/
289 : /* GotoNextFile() */
290 : /************************************************************************/
291 :
292 : int VSILibArchiveReader::GotoNextFile()
293 : {
294 : struct archive_entry *entry;
295 : int r = archive_read_next_header(m_pArchive, &entry);
296 : if (r == ARCHIVE_EOF)
297 : return FALSE;
298 : if (r != ARCHIVE_OK)
299 : {
300 : CPLDebug("VSIARCH", "%s", archive_error_string(m_pArchive));
301 : return FALSE;
302 : }
303 : m_osFilename = archive_entry_pathname_utf8(entry);
304 : m_nFilesize = archive_entry_size(entry);
305 : m_nMTime = archive_entry_mtime(entry);
306 : return TRUE;
307 : }
308 :
309 : /************************************************************************/
310 : /* VSILibArchiveEntryFileOffset */
311 : /************************************************************************/
312 :
313 : struct VSILibArchiveEntryFileOffset : public VSIArchiveEntryFileOffset
314 : {
315 : const std::string m_osFilename;
316 :
317 : VSILibArchiveEntryFileOffset(const std::string &osFilename)
318 : : m_osFilename(osFilename)
319 : {
320 : }
321 : };
322 :
323 : /************************************************************************/
324 : /* GetFileOffset() */
325 : /************************************************************************/
326 :
327 : VSIArchiveEntryFileOffset *VSILibArchiveReader::GetFileOffset()
328 : {
329 : return new VSILibArchiveEntryFileOffset(m_osFilename);
330 : }
331 :
332 : /************************************************************************/
333 : /* GotoFileOffset() */
334 : /************************************************************************/
335 :
336 : int VSILibArchiveReader::GotoFileOffset(VSIArchiveEntryFileOffset *pOffset)
337 : {
338 : VSILibArchiveEntryFileOffset *pMyOffset =
339 : static_cast<VSILibArchiveEntryFileOffset *>(pOffset);
340 : if (!GotoFirstFile())
341 : return false;
342 : while (m_osFilename != pMyOffset->m_osFilename)
343 : {
344 : if (!GotoNextFile())
345 : return false;
346 : }
347 : return true;
348 : }
349 :
350 : /************************************************************************/
351 : /* GotoFileOffsetForced() */
352 : /************************************************************************/
353 :
354 : int VSILibArchiveReader::GotoFileOffsetForced(
355 : VSIArchiveEntryFileOffset *pOffset)
356 : {
357 : m_bFirst = false;
358 : return GotoFileOffset(pOffset);
359 : }
360 :
361 : /************************************************************************/
362 : /* ==================================================================== */
363 : /* VSILibArchiveHandler */
364 : /* ==================================================================== */
365 : /************************************************************************/
366 :
367 : class VSILibArchiveHandler final : public VSIVirtualHandle
368 : {
369 : const std::string m_osFilename;
370 : std::unique_ptr<VSILibArchiveReader> m_poReader;
371 : std::unique_ptr<VSIArchiveEntryFileOffset> m_pOffset;
372 : vsi_l_offset m_nOffset = 0;
373 : bool m_bEOF = false;
374 : bool m_bError = false;
375 :
376 : public:
377 : VSILibArchiveHandler(const std::string &osFilename,
378 : VSILibArchiveReader *poReader)
379 : : m_osFilename(osFilename), m_poReader(poReader),
380 : m_pOffset(poReader->GetFileOffset())
381 : {
382 : }
383 :
384 : virtual size_t Read(void *pBuffer, size_t nSize, size_t nCount) override;
385 : virtual int Seek(vsi_l_offset nOffset, int nWhence) override;
386 :
387 : virtual vsi_l_offset Tell() override
388 : {
389 : return m_nOffset;
390 : }
391 :
392 : virtual size_t Write(const void *, size_t, size_t) override
393 : {
394 : return 0;
395 : }
396 :
397 : virtual int Eof() override
398 : {
399 : return m_bEOF ? 1 : 0;
400 : }
401 :
402 : virtual int Close() override
403 : {
404 : return 0;
405 : }
406 : };
407 :
408 : /************************************************************************/
409 : /* Read() */
410 : /************************************************************************/
411 :
412 : size_t VSILibArchiveHandler::Read(void *pBuffer, size_t nSize, size_t nCount)
413 : {
414 : if (m_bError || nSize == 0 || nCount == 0)
415 : return 0;
416 : if (m_nOffset == m_poReader->GetFileSize())
417 : {
418 : m_bEOF = true;
419 : return 0;
420 : }
421 : size_t nToRead = nSize * nCount;
422 : auto nRead = static_cast<size_t>(
423 : archive_read_data(m_poReader->GetArchiveHandler(), pBuffer, nToRead));
424 : if (nRead < nToRead)
425 : m_bEOF = true;
426 : m_nOffset += nRead;
427 : return nRead / nSize;
428 : }
429 :
430 : /************************************************************************/
431 : /* Seek() */
432 : /************************************************************************/
433 :
434 : int VSILibArchiveHandler::Seek(vsi_l_offset nOffset, int nWhence)
435 : {
436 : if (m_bError)
437 : return -1;
438 : m_bEOF = false;
439 : if (nWhence == SEEK_END && nOffset == 0)
440 : {
441 : m_nOffset = m_poReader->GetFileSize();
442 : return 0;
443 : }
444 : auto nNewOffset = m_nOffset;
445 : if (nWhence == SEEK_CUR)
446 : nNewOffset += nOffset;
447 : else
448 : nNewOffset = nOffset;
449 : if (nNewOffset == m_nOffset)
450 : return 0;
451 :
452 : if (nNewOffset < m_nOffset)
453 : {
454 : CPLDebug("VSIARCH", "Seeking backwards in %s", m_osFilename.c_str());
455 : // If we need to go backwards, we must completely reset the
456 : // reader!
457 : if (!m_poReader->GotoFileOffsetForced(m_pOffset.get()))
458 : {
459 : m_bError = true;
460 : return -1;
461 : }
462 : m_nOffset = 0;
463 : }
464 :
465 : std::vector<GByte> abyBuffer(4096);
466 : while (m_nOffset < nNewOffset)
467 : {
468 : size_t nToRead = static_cast<size_t>(
469 : std::min<vsi_l_offset>(abyBuffer.size(), nNewOffset - m_nOffset));
470 : if (Read(abyBuffer.data(), 1, nToRead) != nToRead)
471 : break;
472 : }
473 :
474 : return 0;
475 : }
476 :
477 : /************************************************************************/
478 : /* ==================================================================== */
479 : /* VSILibArchiveFilesystemHandler */
480 : /* ==================================================================== */
481 : /************************************************************************/
482 :
483 : class VSILibArchiveFilesystemHandler final : public VSIArchiveFilesystemHandler
484 : {
485 : CPL_DISALLOW_COPY_ASSIGN(VSILibArchiveFilesystemHandler)
486 :
487 : const std::string m_osPrefix;
488 :
489 : virtual const char *GetPrefix() override
490 : {
491 : return m_osPrefix.c_str();
492 : }
493 :
494 : virtual std::vector<CPLString> GetExtensions() override
495 : {
496 : if (m_osPrefix == "/vsi7z")
497 : {
498 : return {".7z", ".lpk", ".lpkx", ".mpk", ".mpkx", ".ppkx"};
499 : }
500 : else
501 : {
502 : return {".rar"};
503 : }
504 : }
505 :
506 : virtual VSIArchiveReader *
507 : CreateReader(const char *pszArchiveFileName) override;
508 :
509 : public:
510 : VSILibArchiveFilesystemHandler(const std::string &osPrefix)
511 : : m_osPrefix(osPrefix)
512 : {
513 : }
514 :
515 : virtual VSIVirtualHandle *Open(const char *pszFilename,
516 : const char *pszAccess, bool bSetError,
517 : CSLConstList papszOptions) override;
518 : };
519 :
520 : /************************************************************************/
521 : /* Open() */
522 : /************************************************************************/
523 :
524 : VSIVirtualHandle *VSILibArchiveFilesystemHandler::Open(const char *pszFilename,
525 : const char *pszAccess,
526 : bool, CSLConstList)
527 : {
528 : if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
529 : {
530 : CPLError(CE_Failure, CPLE_AppDefined,
531 : "Only read-only mode is supported for %s", m_osPrefix.c_str());
532 : return nullptr;
533 : }
534 :
535 : CPLString osFileInArchive;
536 : char *pszArchiveFileName =
537 : SplitFilename(pszFilename, osFileInArchive, TRUE);
538 : if (pszArchiveFileName == nullptr)
539 : return nullptr;
540 :
541 : VSILibArchiveReader *poReader = cpl::down_cast<VSILibArchiveReader *>(
542 : OpenArchiveFile(pszArchiveFileName, osFileInArchive));
543 : CPLFree(pszArchiveFileName);
544 : if (poReader == nullptr)
545 : {
546 : return nullptr;
547 : }
548 :
549 : return new VSILibArchiveHandler(pszFilename, poReader);
550 : }
551 :
552 : /************************************************************************/
553 : /* CreateReader() */
554 : /************************************************************************/
555 :
556 : VSIArchiveReader *
557 : VSILibArchiveFilesystemHandler::CreateReader(const char *pszArchiveFileName)
558 : {
559 : auto pArchive = VSICreateArchiveHandle(m_osPrefix);
560 :
561 : if (VSILibArchiveReadOpen(pArchive, pszArchiveFileName))
562 : {
563 : CPLDebug("VSIARCH", "%s: %s", pszArchiveFileName,
564 : archive_error_string(pArchive));
565 : archive_read_free(pArchive);
566 : return nullptr;
567 : }
568 : return new VSILibArchiveReader(pszArchiveFileName, pArchive, m_osPrefix);
569 : }
570 :
571 : //! @endcond
572 :
573 : /************************************************************************/
574 : /* VSIInstall7zFileHandler() */
575 : /************************************************************************/
576 :
577 : /*!
578 : \brief Install /vsi7z/ 7zip file system handler (requires libarchive)
579 :
580 : \verbatim embed:rst
581 : See :ref:`/vsi7z/ documentation <vsi7z>`
582 : \endverbatim
583 :
584 : @since GDAL 3.7
585 : */
586 : void VSIInstall7zFileHandler(void)
587 : {
588 : VSIFileManager::InstallHandler(
589 : "/vsi7z/", new VSILibArchiveFilesystemHandler("/vsi7z"));
590 : }
591 :
592 : /************************************************************************/
593 : /* VSIInstallRarFileHandler() */
594 : /************************************************************************/
595 :
596 : /*!
597 : \brief Install /vsirar/ rar file system handler (requires libarchive)
598 :
599 : \verbatim embed:rst
600 : See :ref:`/vsirar/ documentation <vsirar>`
601 : \endverbatim
602 :
603 : @since GDAL 3.7
604 : */
605 : void VSIInstallRarFileHandler(void)
606 : {
607 : VSIFileManager::InstallHandler(
608 : "/vsirar/", new VSILibArchiveFilesystemHandler("/vsirar"));
609 : }
610 :
611 : #endif
|