Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Implement VSI large file api for archive files.
5 : * Author: Even Rouault, even.rouault at spatialys.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "cpl_vsi_virtual.h"
15 :
16 : #include <cstring>
17 : #include <ctime>
18 : #include <fcntl.h>
19 : #include <map>
20 : #include <memory>
21 : #include <set>
22 : #include <string>
23 : #include <utility>
24 : #include <vector>
25 :
26 : #include "cpl_conv.h"
27 : #include "cpl_error.h"
28 : #include "cpl_multiproc.h"
29 : #include "cpl_string.h"
30 : #include "cpl_vsi.h"
31 :
32 : //! @cond Doxygen_Suppress
33 :
34 43914 : static bool IsEitherSlash(char c)
35 : {
36 43914 : return c == '/' || c == '\\';
37 : }
38 :
39 : /************************************************************************/
40 : /* ~VSIArchiveEntryFileOffset() */
41 : /************************************************************************/
42 :
43 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset() = default;
44 :
45 : /************************************************************************/
46 : /* ~VSIArchiveReader() */
47 : /************************************************************************/
48 :
49 : VSIArchiveReader::~VSIArchiveReader() = default;
50 :
51 : /************************************************************************/
52 : /* ~VSIArchiveContent() */
53 : /************************************************************************/
54 :
55 : VSIArchiveContent::~VSIArchiveContent() = default;
56 :
57 : /************************************************************************/
58 : /* VSIArchiveFilesystemHandler() */
59 : /************************************************************************/
60 :
61 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler() = default;
62 :
63 : /************************************************************************/
64 : /* ~VSIArchiveFilesystemHandler() */
65 : /************************************************************************/
66 :
67 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler() = default;
68 :
69 : /************************************************************************/
70 : /* GetStrippedFilename() */
71 : /************************************************************************/
72 :
73 5255 : static std::string GetStrippedFilename(const std::string &osFileName,
74 : bool &bIsDir)
75 : {
76 5255 : bIsDir = false;
77 5255 : int nStartPos = 0;
78 :
79 : // Remove ./ pattern at the beginning of a filename.
80 5255 : if (osFileName.size() >= 2 && osFileName[0] == '.' && osFileName[1] == '/')
81 : {
82 0 : nStartPos = 2;
83 0 : if (osFileName.size() == 2)
84 0 : return std::string();
85 : }
86 :
87 10510 : std::string ret(osFileName, nStartPos);
88 186009 : for (char &c : ret)
89 : {
90 180754 : if (c == '\\')
91 1 : c = '/';
92 : }
93 :
94 5255 : bIsDir = !ret.empty() && ret.back() == '/';
95 5255 : if (bIsDir)
96 : {
97 : // Remove trailing slash.
98 242 : ret.pop_back();
99 : }
100 5255 : return ret;
101 : }
102 :
103 : /************************************************************************/
104 : /* BuildDirectoryIndex() */
105 : /************************************************************************/
106 :
107 225 : static void BuildDirectoryIndex(VSIArchiveContent *content)
108 : {
109 225 : content->dirIndex.clear();
110 225 : const int nEntries = static_cast<int>(content->entries.size());
111 6594 : for (int i = 0; i < nEntries; i++)
112 : {
113 6369 : const char *fileName = content->entries[i].fileName.c_str();
114 12738 : std::string parentDir = CPLGetPathSafe(fileName);
115 6369 : content->dirIndex[parentDir].push_back(i);
116 : }
117 225 : }
118 :
119 : /************************************************************************/
120 : /* GetContentOfArchive() */
121 : /************************************************************************/
122 :
123 : const VSIArchiveContent *
124 8694 : VSIArchiveFilesystemHandler::GetContentOfArchive(const char *archiveFilename,
125 : VSIArchiveReader *poReader)
126 : {
127 17388 : std::unique_lock oLock(oMutex);
128 :
129 : VSIStatBufL sStat;
130 8694 : if (VSIStatL(archiveFilename, &sStat) != 0)
131 0 : return nullptr;
132 :
133 8694 : auto oIter = oFileList.find(archiveFilename);
134 8694 : if (oIter != oFileList.end())
135 : {
136 8469 : const VSIArchiveContent *content = oIter->second.get();
137 8469 : if (static_cast<time_t>(sStat.st_mtime) > content->mTime ||
138 8468 : static_cast<vsi_l_offset>(sStat.st_size) != content->nFileSize)
139 : {
140 8 : CPLDebug("VSIArchive",
141 : "The content of %s has changed since it was cached",
142 : archiveFilename);
143 8 : oFileList.erase(archiveFilename);
144 : }
145 : else
146 : {
147 8461 : return content;
148 : }
149 : }
150 :
151 233 : std::unique_ptr<VSIArchiveReader> temporaryReader; // keep in that scope
152 233 : if (poReader == nullptr)
153 : {
154 232 : temporaryReader = CreateReader(archiveFilename);
155 232 : poReader = temporaryReader.get();
156 232 : if (!poReader)
157 8 : return nullptr;
158 : }
159 :
160 225 : if (poReader->GotoFirstFile() == FALSE)
161 : {
162 0 : return nullptr;
163 : }
164 :
165 450 : auto content = std::make_unique<VSIArchiveContent>();
166 225 : content->mTime = sStat.st_mtime;
167 225 : content->nFileSize = static_cast<vsi_l_offset>(sStat.st_size);
168 :
169 225 : std::set<std::string> oSet;
170 :
171 4885 : do
172 : {
173 5110 : bool bIsDir = false;
174 : std::string osStrippedFilename =
175 5110 : GetStrippedFilename(poReader->GetFileName(), bIsDir);
176 10220 : if (osStrippedFilename.empty() || osStrippedFilename[0] == '/' ||
177 5110 : osStrippedFilename.find("//") != std::string::npos)
178 : {
179 0 : continue;
180 : }
181 :
182 5110 : if (oSet.find(osStrippedFilename) == oSet.end())
183 : {
184 5110 : oSet.insert(osStrippedFilename);
185 :
186 : // Add intermediate directory structure.
187 183811 : for (size_t i = 0; i < osStrippedFilename.size(); ++i)
188 : {
189 178701 : if (osStrippedFilename[i] == '/')
190 : {
191 12940 : std::string osSubdirName(osStrippedFilename, 0, i);
192 6470 : if (oSet.find(osSubdirName) == oSet.end())
193 : {
194 1259 : oSet.insert(osSubdirName);
195 :
196 2518 : VSIArchiveEntry entry;
197 1259 : entry.fileName = std::move(osSubdirName);
198 1259 : entry.nModifiedTime = poReader->GetModifiedTime();
199 1259 : entry.bIsDir = true;
200 : #ifdef DEBUG_VERBOSE
201 : CPLDebug(
202 : "VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes",
203 : static_cast<int>(content->entries.size() + 1),
204 : entry.fileName.c_str(), entry.uncompressed_size);
205 : #endif
206 1259 : content->entries.push_back(std::move(entry));
207 : }
208 : }
209 : }
210 :
211 10220 : VSIArchiveEntry entry;
212 5110 : entry.fileName = std::move(osStrippedFilename);
213 5110 : entry.nModifiedTime = poReader->GetModifiedTime();
214 5110 : entry.uncompressed_size = poReader->GetFileSize();
215 5110 : entry.bIsDir = bIsDir;
216 5110 : entry.file_pos.reset(poReader->GetFileOffset());
217 : #ifdef DEBUG_VERBOSE
218 : CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes",
219 : static_cast<int>(content->entries.size() + 1),
220 : entry.fileName.c_str(), entry.uncompressed_size);
221 : #endif
222 5110 : content->entries.push_back(std::move(entry));
223 : }
224 :
225 5110 : } while (poReader->GotoNextFile());
226 :
227 : // Build directory index for fast lookups
228 225 : BuildDirectoryIndex(content.get());
229 :
230 : return oFileList
231 450 : .insert(std::pair<CPLString, std::unique_ptr<VSIArchiveContent>>(
232 450 : archiveFilename, std::move(content)))
233 225 : .first->second.get();
234 : }
235 :
236 : /************************************************************************/
237 : /* FindFileInArchive() */
238 : /************************************************************************/
239 :
240 7291 : bool VSIArchiveFilesystemHandler::FindFileInArchive(
241 : const char *archiveFilename, const char *fileInArchiveName,
242 : const VSIArchiveEntry **archiveEntry)
243 : {
244 7291 : CPLAssert(fileInArchiveName);
245 :
246 7291 : const VSIArchiveContent *content = GetContentOfArchive(archiveFilename);
247 7291 : if (content)
248 : {
249 7288 : const std::string parentDir = CPLGetPathSafe(fileInArchiveName);
250 :
251 : // Use directory index to search within parent directory's children
252 7288 : auto dirIter = content->dirIndex.find(parentDir);
253 7288 : if (dirIter != content->dirIndex.end())
254 : {
255 7276 : const std::vector<int> &childIndices = dirIter->second;
256 557812 : for (int childIdx : childIndices)
257 : {
258 557362 : if (content->entries[childIdx].fileName == fileInArchiveName)
259 : {
260 6826 : if (archiveEntry)
261 6826 : *archiveEntry = &content->entries[childIdx];
262 6826 : return true;
263 : }
264 : }
265 : }
266 : }
267 465 : return false;
268 : }
269 :
270 : /************************************************************************/
271 : /* CompactFilename() */
272 : /************************************************************************/
273 :
274 13656 : static std::string CompactFilename(const char *pszArchiveInFileNameIn)
275 : {
276 27312 : std::string osRet(pszArchiveInFileNameIn);
277 :
278 : // Replace a/../b by b and foo/a/../b by foo/b.
279 : while (true)
280 : {
281 13656 : size_t nSlashDotDot = osRet.find("/../");
282 13656 : if (nSlashDotDot == std::string::npos || nSlashDotDot == 0)
283 : break;
284 0 : size_t nPos = nSlashDotDot - 1;
285 0 : while (nPos > 0 && osRet[nPos] != '/')
286 0 : --nPos;
287 0 : if (nPos == 0)
288 0 : osRet = osRet.substr(nSlashDotDot + strlen("/../"));
289 : else
290 0 : osRet = osRet.substr(0, nPos + 1) +
291 0 : osRet.substr(nSlashDotDot + strlen("/../"));
292 0 : }
293 13656 : return osRet;
294 : }
295 :
296 : /************************************************************************/
297 : /* SplitFilename() */
298 : /************************************************************************/
299 :
300 : std::unique_ptr<char, VSIFreeReleaser>
301 15513 : VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
302 : CPLString &osFileInArchive,
303 : bool bCheckMainFileExists,
304 : bool bSetError) const
305 : {
306 : // TODO(schwehr): Cleanup redundant calls to GetPrefix and strlen.
307 15513 : if (strcmp(pszFilename, GetPrefix()) == 0)
308 4 : return nullptr;
309 :
310 15509 : int i = 0;
311 :
312 : // Detect extended syntax: /vsiXXX/{archive_filename}/file_in_archive.
313 15509 : if (pszFilename[strlen(GetPrefix()) + 1] == '{')
314 : {
315 1378 : pszFilename += strlen(GetPrefix()) + 1;
316 1378 : int nCountCurlies = 0;
317 53253 : while (pszFilename[i])
318 : {
319 53252 : if (pszFilename[i] == '{')
320 1381 : nCountCurlies++;
321 51871 : else if (pszFilename[i] == '}')
322 : {
323 1380 : nCountCurlies--;
324 1380 : if (nCountCurlies == 0)
325 1377 : break;
326 : }
327 51875 : i++;
328 : }
329 1378 : if (nCountCurlies > 0)
330 1 : return nullptr;
331 1377 : char *archiveFilename = CPLStrdup(pszFilename + 1);
332 1377 : archiveFilename[i - 1] = 0;
333 :
334 1377 : bool bArchiveFileExists = false;
335 1377 : if (!bCheckMainFileExists)
336 : {
337 40 : bArchiveFileExists = true;
338 : }
339 : else
340 : {
341 2674 : std::unique_lock oLock(oMutex);
342 :
343 1337 : if (oFileList.find(archiveFilename) != oFileList.end())
344 : {
345 1195 : bArchiveFileExists = true;
346 : }
347 : }
348 :
349 1377 : if (!bArchiveFileExists)
350 : {
351 : VSIStatBufL statBuf;
352 : VSIFilesystemHandler *poFSHandler =
353 142 : VSIFileManager::GetHandler(archiveFilename);
354 142 : int nFlags = VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG;
355 142 : if (bSetError)
356 31 : nFlags |= VSI_STAT_SET_ERROR_FLAG;
357 283 : if (poFSHandler->Stat(archiveFilename, &statBuf, nFlags) == 0 &&
358 141 : !VSI_ISDIR(statBuf.st_mode))
359 : {
360 141 : bArchiveFileExists = true;
361 : }
362 : }
363 :
364 1377 : if (bArchiveFileExists)
365 : {
366 1376 : if (IsEitherSlash(pszFilename[i + 1]))
367 : {
368 1309 : osFileInArchive = CompactFilename(pszFilename + i + 2);
369 : }
370 67 : else if (pszFilename[i + 1] == '\0')
371 : {
372 66 : osFileInArchive = "";
373 : }
374 : else
375 : {
376 1 : CPLFree(archiveFilename);
377 1 : return nullptr;
378 : }
379 :
380 : // Remove trailing slash.
381 1375 : if (!osFileInArchive.empty())
382 : {
383 1305 : const char lastC = osFileInArchive.back();
384 1305 : if (IsEitherSlash(lastC))
385 2 : osFileInArchive.pop_back();
386 : }
387 :
388 1375 : return std::unique_ptr<char, VSIFreeReleaser>(archiveFilename);
389 : }
390 :
391 1 : CPLFree(archiveFilename);
392 1 : return nullptr;
393 : }
394 :
395 : // Allow natural chaining of VSI drivers without requiring double slash.
396 :
397 28262 : CPLString osDoubleVsi(GetPrefix());
398 14131 : osDoubleVsi += "/vsi";
399 :
400 14131 : if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
401 4061 : pszFilename += strlen(GetPrefix());
402 : else
403 10070 : pszFilename += strlen(GetPrefix()) + 1;
404 :
405 : // Parsing strings like
406 : // /vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar//vsitar/a.tgzb.tgzc.tgzd.tgze.tgzf.tgz.h.tgz.i.tgz
407 : // takes a huge amount of time, so limit the number of nesting of such
408 : // file systems.
409 14131 : int *pnCounter = static_cast<int *>(CPLGetTLS(CTLS_ABSTRACTARCHIVE_SPLIT));
410 14131 : if (pnCounter == nullptr)
411 : {
412 25 : pnCounter = static_cast<int *>(CPLMalloc(sizeof(int)));
413 25 : *pnCounter = 0;
414 25 : CPLSetTLS(CTLS_ABSTRACTARCHIVE_SPLIT, pnCounter, TRUE);
415 : }
416 14131 : if (*pnCounter == 3)
417 : {
418 64 : CPLError(CE_Failure, CPLE_AppDefined,
419 : "Too deep recursion level in "
420 : "VSIArchiveFilesystemHandler::SplitFilename()");
421 64 : return nullptr;
422 : }
423 :
424 28134 : const std::vector<CPLString> oExtensions = GetExtensions();
425 14067 : int nAttempts = 0;
426 443758 : while (pszFilename[i])
427 : {
428 443535 : int nToSkip = 0;
429 :
430 3009080 : for (std::vector<CPLString>::const_iterator iter = oExtensions.begin();
431 5574620 : iter != oExtensions.end(); ++iter)
432 : {
433 2580060 : const CPLString &osExtension = *iter;
434 2580060 : if (EQUALN(pszFilename + i, osExtension.c_str(),
435 : osExtension.size()))
436 : {
437 14522 : nToSkip = static_cast<int>(osExtension.size());
438 14522 : break;
439 : }
440 : }
441 :
442 : #ifdef DEBUG
443 : // For AFL, so that .cur_input is detected as the archive filename.
444 443535 : if (EQUALN(pszFilename + i, ".cur_input", strlen(".cur_input")))
445 : {
446 14 : nToSkip = static_cast<int>(strlen(".cur_input"));
447 : }
448 : #endif
449 :
450 443535 : if (nToSkip != 0)
451 : {
452 14536 : nAttempts++;
453 : // Arbitrary threshold to avoid DoS with things like
454 : // /vsitar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar/my.tar
455 14536 : if (nAttempts == 5)
456 : {
457 21 : break;
458 : }
459 : VSIStatBufL statBuf;
460 14515 : char *archiveFilename = CPLStrdup(pszFilename);
461 14515 : bool bArchiveFileExists = false;
462 :
463 14515 : if (IsEitherSlash(archiveFilename[i + nToSkip]))
464 : {
465 12833 : archiveFilename[i + nToSkip] = 0;
466 : }
467 :
468 14515 : if (!bCheckMainFileExists)
469 : {
470 1016 : bArchiveFileExists = true;
471 : }
472 : else
473 : {
474 26998 : std::unique_lock oLock(oMutex);
475 :
476 13499 : if (oFileList.find(archiveFilename) != oFileList.end())
477 : {
478 12374 : bArchiveFileExists = true;
479 : }
480 : }
481 :
482 14515 : if (!bArchiveFileExists)
483 : {
484 1125 : (*pnCounter)++;
485 :
486 : VSIFilesystemHandler *poFSHandler =
487 1125 : VSIFileManager::GetHandler(archiveFilename);
488 3375 : if (poFSHandler->Stat(archiveFilename, &statBuf,
489 : VSI_STAT_EXISTS_FLAG |
490 1978 : VSI_STAT_NATURE_FLAG) == 0 &&
491 853 : !VSI_ISDIR(statBuf.st_mode))
492 : {
493 433 : bArchiveFileExists = true;
494 : }
495 :
496 1125 : (*pnCounter)--;
497 : }
498 :
499 14515 : if (bArchiveFileExists)
500 : {
501 13823 : if (IsEitherSlash(pszFilename[i + nToSkip]))
502 : {
503 : osFileInArchive =
504 12347 : CompactFilename(pszFilename + i + nToSkip + 1);
505 : }
506 : else
507 : {
508 1476 : osFileInArchive = "";
509 : }
510 :
511 : // Remove trailing slash.
512 13823 : if (!osFileInArchive.empty())
513 : {
514 12339 : const char lastC = osFileInArchive.back();
515 12339 : if (IsEitherSlash(lastC))
516 63 : osFileInArchive.resize(osFileInArchive.size() - 1);
517 : }
518 :
519 13823 : return std::unique_ptr<char, VSIFreeReleaser>(archiveFilename);
520 : }
521 692 : CPLFree(archiveFilename);
522 : }
523 429691 : i++;
524 : }
525 244 : return nullptr;
526 : }
527 :
528 : /************************************************************************/
529 : /* OpenArchiveFile() */
530 : /************************************************************************/
531 :
532 : std::unique_ptr<VSIArchiveReader>
533 4449 : VSIArchiveFilesystemHandler::OpenArchiveFile(const char *archiveFilename,
534 : const char *fileInArchiveName)
535 : {
536 8898 : auto poReader = CreateReader(archiveFilename);
537 :
538 4449 : if (poReader == nullptr)
539 : {
540 6 : return nullptr;
541 : }
542 :
543 4443 : if (fileInArchiveName == nullptr || strlen(fileInArchiveName) == 0)
544 : {
545 73 : if (poReader->GotoFirstFile() == FALSE)
546 : {
547 1 : return nullptr;
548 : }
549 :
550 : // Skip optional leading subdir.
551 73 : const CPLString osFileName = poReader->GetFileName();
552 73 : if (osFileName.empty() || IsEitherSlash(osFileName.back()))
553 : {
554 2 : if (poReader->GotoNextFile() == FALSE)
555 : {
556 0 : return nullptr;
557 : }
558 : }
559 :
560 73 : if (poReader->GotoNextFile())
561 : {
562 2 : CPLString msg;
563 : msg.Printf("Support only 1 file in archive file %s when "
564 : "no explicit in-archive filename is specified",
565 1 : archiveFilename);
566 : const VSIArchiveContent *content =
567 1 : GetContentOfArchive(archiveFilename, poReader.get());
568 1 : if (content)
569 : {
570 1 : msg += "\nYou could try one of the following :\n";
571 6 : for (const auto &entry : content->entries)
572 : {
573 10 : msg += CPLString().Printf(" %s/{%s}/%s\n", GetPrefix(),
574 : archiveFilename,
575 5 : entry.fileName.c_str());
576 : }
577 : }
578 :
579 1 : CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
580 :
581 1 : return nullptr;
582 72 : }
583 : }
584 : else
585 : {
586 : // Optimization: instead of iterating over all files which can be
587 : // slow on .tar.gz files, try reading the first one first.
588 : // This can help if it is really huge.
589 : {
590 4370 : std::unique_lock oLock(oMutex);
591 :
592 4370 : if (oFileList.find(archiveFilename) == oFileList.end())
593 : {
594 145 : if (poReader->GotoFirstFile() == FALSE)
595 : {
596 57 : return nullptr;
597 : }
598 :
599 145 : const CPLString osFileName = poReader->GetFileName();
600 145 : bool bIsDir = false;
601 : const CPLString osStrippedFilename =
602 145 : GetStrippedFilename(osFileName, bIsDir);
603 145 : if (!osStrippedFilename.empty())
604 : {
605 : const bool bMatch =
606 145 : strcmp(osStrippedFilename, fileInArchiveName) == 0;
607 145 : if (bMatch)
608 : {
609 57 : if (bIsDir)
610 : {
611 2 : return nullptr;
612 : }
613 55 : return poReader;
614 : }
615 : }
616 : }
617 : }
618 :
619 4313 : const VSIArchiveEntry *archiveEntry = nullptr;
620 4313 : if (FindFileInArchive(archiveFilename, fileInArchiveName,
621 8428 : &archiveEntry) == FALSE ||
622 4115 : archiveEntry->bIsDir)
623 : {
624 202 : return nullptr;
625 : }
626 4111 : if (!poReader->GotoFileOffset(archiveEntry->file_pos.get()))
627 : {
628 0 : return nullptr;
629 : }
630 : }
631 4183 : return poReader;
632 : }
633 :
634 : /************************************************************************/
635 : /* Stat() */
636 : /************************************************************************/
637 :
638 3551 : int VSIArchiveFilesystemHandler::Stat(const char *pszFilename,
639 : VSIStatBufL *pStatBuf, int nFlags)
640 : {
641 3551 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
642 :
643 7102 : CPLString osFileInArchive;
644 : auto archiveFilename =
645 : SplitFilename(pszFilename, osFileInArchive, true,
646 7102 : (nFlags & VSI_STAT_SET_ERROR_FLAG) != 0);
647 3551 : if (archiveFilename == nullptr)
648 85 : return -1;
649 :
650 3466 : int ret = -1;
651 3466 : if (!osFileInArchive.empty())
652 : {
653 : #ifdef DEBUG_VERBOSE
654 : CPLDebug("VSIArchive", "Looking for %s %s", archiveFilename.get(),
655 : osFileInArchive.c_str());
656 : #endif
657 :
658 2978 : const VSIArchiveEntry *archiveEntry = nullptr;
659 2978 : if (FindFileInArchive(archiveFilename.get(), osFileInArchive,
660 : &archiveEntry))
661 : {
662 : // Patching st_size with uncompressed file size.
663 2711 : pStatBuf->st_size = archiveEntry->uncompressed_size;
664 2711 : pStatBuf->st_mtime =
665 2711 : static_cast<time_t>(archiveEntry->nModifiedTime);
666 2711 : if (archiveEntry->bIsDir)
667 1154 : pStatBuf->st_mode = S_IFDIR;
668 : else
669 1557 : pStatBuf->st_mode = S_IFREG;
670 2711 : ret = 0;
671 : }
672 : }
673 : else
674 : {
675 488 : auto poReader = CreateReader(archiveFilename.get());
676 :
677 488 : if (poReader != nullptr && poReader->GotoFirstFile())
678 : {
679 : // Skip optional leading subdir.
680 483 : const CPLString osFileName = poReader->GetFileName();
681 483 : if (IsEitherSlash(osFileName.back()))
682 : {
683 11 : if (poReader->GotoNextFile() == FALSE)
684 : {
685 0 : return -1;
686 : }
687 : }
688 :
689 483 : if (poReader->GotoNextFile())
690 : {
691 : // Several files in archive --> treat as dir.
692 455 : pStatBuf->st_size = 0;
693 455 : pStatBuf->st_mode = S_IFDIR;
694 : }
695 : else
696 : {
697 : // Patching st_size with uncompressed file size.
698 28 : pStatBuf->st_size = poReader->GetFileSize();
699 28 : pStatBuf->st_mtime =
700 28 : static_cast<time_t>(poReader->GetModifiedTime());
701 28 : pStatBuf->st_mode = S_IFREG;
702 : }
703 :
704 483 : ret = 0;
705 : }
706 : }
707 :
708 3466 : return ret;
709 : }
710 :
711 : /************************************************************************/
712 : /* ReadDirEx() */
713 : /************************************************************************/
714 :
715 1402 : char **VSIArchiveFilesystemHandler::ReadDirEx(const char *pszDirname,
716 : int nMaxFiles)
717 : {
718 2804 : CPLString osInArchiveSubDir;
719 : auto archiveFilename =
720 2804 : SplitFilename(pszDirname, osInArchiveSubDir, true, true);
721 1402 : if (archiveFilename == nullptr)
722 0 : return nullptr;
723 :
724 1402 : const size_t lenInArchiveSubDir = osInArchiveSubDir.size();
725 :
726 2804 : CPLStringList oDir;
727 :
728 : const VSIArchiveContent *content =
729 1402 : GetContentOfArchive(archiveFilename.get());
730 1402 : if (!content)
731 : {
732 5 : return nullptr;
733 : }
734 :
735 : #ifdef DEBUG_VERBOSE
736 : CPLDebug("VSIArchive", "Read dir %s", pszDirname);
737 : #endif
738 :
739 2794 : std::string searchDir;
740 1397 : if (lenInArchiveSubDir != 0)
741 1166 : searchDir = std::move(osInArchiveSubDir);
742 :
743 : // Use directory index to find the list of children for this directory
744 1397 : auto dirIter = content->dirIndex.find(searchDir);
745 1397 : if (dirIter == content->dirIndex.end())
746 : {
747 : // Directory not found in index - no children
748 0 : return oDir.StealList();
749 : }
750 1397 : const std::vector<int> &childIndices = dirIter->second;
751 :
752 : // Scan the children of this directory
753 42367 : for (int childIdx : childIndices)
754 : {
755 40971 : const char *fileName = content->entries[childIdx].fileName.c_str();
756 :
757 40971 : const char *baseName = fileName;
758 40971 : if (lenInArchiveSubDir != 0)
759 : {
760 : // Skip the directory prefix and slash to get just the child name
761 38527 : baseName = fileName + lenInArchiveSubDir + 1;
762 : }
763 40971 : oDir.AddStringDirectly(CPLStrdup(baseName));
764 :
765 40971 : if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
766 1 : break;
767 : }
768 1397 : return oDir.StealList();
769 : }
770 :
771 : /************************************************************************/
772 : /* IsLocal() */
773 : /************************************************************************/
774 :
775 4 : bool VSIArchiveFilesystemHandler::IsLocal(const char *pszPath) const
776 : {
777 4 : if (!STARTS_WITH(pszPath, GetPrefix()))
778 0 : return false;
779 4 : const char *pszBaseFileName = pszPath + strlen(GetPrefix());
780 : VSIFilesystemHandler *poFSHandler =
781 4 : VSIFileManager::GetHandler(pszBaseFileName);
782 4 : return poFSHandler->IsLocal(pszPath);
783 : }
784 :
785 : /************************************************************************/
786 : /* IsArchive() */
787 : /************************************************************************/
788 :
789 0 : bool VSIArchiveFilesystemHandler::IsArchive(const char *pszPath) const
790 : {
791 0 : if (!STARTS_WITH(pszPath, GetPrefix()))
792 0 : return false;
793 0 : CPLString osFileInArchive;
794 0 : return SplitFilename(pszPath, osFileInArchive, false, false) != nullptr &&
795 0 : osFileInArchive.empty();
796 : }
797 :
798 : //! @endcond
|