1 : /******************************************************************************
2 : * $Id: cpl_vsil_abstract_archive.cpp 23817 2012-01-28 17:39:00Z rouault $
3 : *
4 : * Project: CPL - Common Portability Library
5 : * Purpose: Implement VSI large file api for archive files.
6 : * Author: Even Rouault, even.rouault at mines-paris.org
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2010, Even Rouault
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ****************************************************************************/
29 :
30 : #include "cpl_vsi_virtual.h"
31 : #include "cpl_string.h"
32 : #include "cpl_multiproc.h"
33 : #include <map>
34 : #include <set>
35 :
36 : #define ENABLE_DEBUG 0
37 :
38 : CPL_CVSID("$Id: cpl_vsil_abstract_archive.cpp 23817 2012-01-28 17:39:00Z rouault $");
39 :
40 : /************************************************************************/
41 : /* ~VSIArchiveEntryFileOffset() */
42 : /************************************************************************/
43 :
44 4794 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
45 : {
46 4794 : }
47 :
48 : /************************************************************************/
49 : /* ~VSIArchiveReader() */
50 : /************************************************************************/
51 :
52 568 : VSIArchiveReader::~VSIArchiveReader()
53 : {
54 568 : }
55 :
56 : /************************************************************************/
57 : /* VSIArchiveFilesystemHandler() */
58 : /************************************************************************/
59 :
60 2682 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
61 : {
62 2682 : hMutex = NULL;
63 2682 : }
64 :
65 : /************************************************************************/
66 : /* ~VSIArchiveFilesystemHandler() */
67 : /************************************************************************/
68 :
69 2594 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
70 :
71 : {
72 2594 : std::map<CPLString,VSIArchiveContent*>::const_iterator iter;
73 :
74 2658 : for( iter = oFileList.begin(); iter != oFileList.end(); ++iter )
75 : {
76 64 : VSIArchiveContent* content = iter->second;
77 : int i;
78 5516 : for(i=0;i<content->nEntries;i++)
79 : {
80 5452 : delete content->entries[i].file_pos;
81 5452 : CPLFree(content->entries[i].fileName);
82 : }
83 64 : CPLFree(content->entries);
84 64 : delete content;
85 : }
86 :
87 2594 : if( hMutex != NULL )
88 12 : CPLDestroyMutex( hMutex );
89 2594 : hMutex = NULL;
90 2594 : }
91 :
92 : /************************************************************************/
93 : /* GetContentOfArchive() */
94 : /************************************************************************/
95 :
96 1628 : const VSIArchiveContent* VSIArchiveFilesystemHandler::GetContentOfArchive
97 : (const char* archiveFilename, VSIArchiveReader* poReader)
98 : {
99 1628 : CPLMutexHolder oHolder( &hMutex );
100 :
101 1628 : if (oFileList.find(archiveFilename) != oFileList.end() )
102 : {
103 1556 : return oFileList[archiveFilename];
104 : }
105 :
106 72 : int bMustClose = (poReader == NULL);
107 72 : if (poReader == NULL)
108 : {
109 72 : poReader = CreateReader(archiveFilename);
110 72 : if (!poReader)
111 0 : return NULL;
112 : }
113 :
114 72 : if (poReader->GotoFirstFile() == FALSE)
115 : {
116 0 : if (bMustClose)
117 0 : delete(poReader);
118 0 : return NULL;
119 : }
120 :
121 72 : VSIArchiveContent* content = new VSIArchiveContent;
122 72 : content->nEntries = 0;
123 72 : content->entries = NULL;
124 72 : oFileList[archiveFilename] = content;
125 :
126 72 : std::set<CPLString> oSet;
127 :
128 4770 : do
129 : {
130 4770 : CPLString osFileName = poReader->GetFileName();
131 4770 : const char* fileName = osFileName.c_str();
132 :
133 : /* Remove ./ pattern at the beginning of a filename */
134 4770 : if (fileName[0] == '.' && fileName[1] == '/')
135 : {
136 0 : fileName += 2;
137 0 : if (fileName[0] == '\0')
138 0 : continue;
139 : }
140 :
141 4770 : char* pszStrippedFileName = CPLStrdup(fileName);
142 : char* pszIter;
143 175520 : for(pszIter = pszStrippedFileName;*pszIter;pszIter++)
144 : {
145 170750 : if (*pszIter == '\\')
146 0 : *pszIter = '/';
147 : }
148 :
149 : int bIsDir = strlen(fileName) > 0 &&
150 4770 : fileName[strlen(fileName)-1] == '/';
151 4770 : if (bIsDir)
152 : {
153 : /* Remove trailing slash */
154 82 : pszStrippedFileName[strlen(fileName)-1] = 0;
155 : }
156 :
157 4770 : if (oSet.find(pszStrippedFileName) == oSet.end())
158 : {
159 4770 : oSet.insert(pszStrippedFileName);
160 :
161 : /* Add intermediate directory structure */
162 175438 : for(pszIter = pszStrippedFileName;*pszIter;pszIter++)
163 : {
164 170668 : if (*pszIter == '/')
165 : {
166 12730 : char* pszStrippedFileName2 = CPLStrdup(pszStrippedFileName);
167 12730 : pszStrippedFileName2[pszIter - pszStrippedFileName] = 0;
168 12730 : if (oSet.find(pszStrippedFileName2) == oSet.end())
169 : {
170 780 : oSet.insert(pszStrippedFileName2);
171 :
172 : content->entries = (VSIArchiveEntry*)CPLRealloc(content->entries,
173 780 : sizeof(VSIArchiveEntry) * (content->nEntries + 1));
174 780 : content->entries[content->nEntries].fileName = pszStrippedFileName2;
175 780 : content->entries[content->nEntries].nModifiedTime = poReader->GetModifiedTime();
176 780 : content->entries[content->nEntries].uncompressed_size = 0;
177 780 : content->entries[content->nEntries].bIsDir = TRUE;
178 780 : content->entries[content->nEntries].file_pos = NULL;
179 : if (ENABLE_DEBUG)
180 : CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes", content->nEntries+1,
181 : content->entries[content->nEntries].fileName,
182 : content->entries[content->nEntries].uncompressed_size);
183 780 : content->nEntries++;
184 : }
185 : else
186 : {
187 11950 : CPLFree(pszStrippedFileName2);
188 : }
189 : }
190 : }
191 :
192 : content->entries = (VSIArchiveEntry*)CPLRealloc(content->entries,
193 4770 : sizeof(VSIArchiveEntry) * (content->nEntries + 1));
194 4770 : content->entries[content->nEntries].fileName = pszStrippedFileName;
195 4770 : content->entries[content->nEntries].nModifiedTime = poReader->GetModifiedTime();
196 4770 : content->entries[content->nEntries].uncompressed_size = poReader->GetFileSize();
197 4770 : content->entries[content->nEntries].bIsDir = bIsDir;
198 4770 : content->entries[content->nEntries].file_pos = poReader->GetFileOffset();
199 : if (ENABLE_DEBUG)
200 : CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes", content->nEntries+1,
201 : content->entries[content->nEntries].fileName,
202 : content->entries[content->nEntries].uncompressed_size);
203 4770 : content->nEntries++;
204 : }
205 : else
206 : {
207 0 : CPLFree(pszStrippedFileName);
208 0 : }
209 4770 : } while(poReader->GotoNextFile());
210 :
211 72 : if (bMustClose)
212 72 : delete(poReader);
213 :
214 72 : return content;
215 : }
216 :
217 : /************************************************************************/
218 : /* FindFileInArchive() */
219 : /************************************************************************/
220 :
221 532 : int VSIArchiveFilesystemHandler::FindFileInArchive(const char* archiveFilename,
222 : const char* fileInArchiveName,
223 : const VSIArchiveEntry** archiveEntry)
224 : {
225 532 : if (fileInArchiveName == NULL)
226 0 : return FALSE;
227 :
228 532 : const VSIArchiveContent* content = GetContentOfArchive(archiveFilename);
229 532 : if (content)
230 : {
231 : int i;
232 51618 : for(i=0;i<content->nEntries;i++)
233 : {
234 51552 : if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
235 : {
236 466 : if (archiveEntry)
237 466 : *archiveEntry = &content->entries[i];
238 466 : return TRUE;
239 : }
240 : }
241 : }
242 66 : return FALSE;
243 : }
244 :
245 : /************************************************************************/
246 : /* SplitFilename() */
247 : /************************************************************************/
248 :
249 3110 : char* VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
250 : CPLString &osFileInArchive,
251 : int bCheckMainFileExists)
252 : {
253 3110 : int i = 0;
254 :
255 3110 : if (strcmp(pszFilename, GetPrefix()) == 0)
256 0 : return NULL;
257 :
258 : /* Allow natural chaining of VSI drivers without requiring double slash */
259 :
260 3110 : CPLString osDoubleVsi(GetPrefix());
261 3110 : osDoubleVsi += "/vsi";
262 :
263 3110 : if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
264 106 : pszFilename += strlen(GetPrefix());
265 : else
266 3004 : pszFilename += strlen(GetPrefix()) + 1;
267 :
268 3110 : while(pszFilename[i])
269 : {
270 46102 : std::vector<CPLString> oExtensions = GetExtensions();
271 46102 : std::vector<CPLString>::const_iterator iter;
272 46102 : int nToSkip = 0;
273 :
274 268492 : for( iter = oExtensions.begin(); iter != oExtensions.end(); ++iter )
275 : {
276 225466 : const CPLString& osExtension = *iter;
277 225466 : if (EQUALN(pszFilename + i, osExtension.c_str(), strlen(osExtension.c_str())))
278 : {
279 3076 : nToSkip = strlen(osExtension.c_str());
280 3076 : break;
281 : }
282 : }
283 :
284 46102 : if (nToSkip != 0)
285 : {
286 : VSIStatBufL statBuf;
287 3076 : char* archiveFilename = CPLStrdup(pszFilename);
288 3076 : int bArchiveFileExists = FALSE;
289 :
290 3348 : if (archiveFilename[i + nToSkip] == '/' ||
291 272 : archiveFilename[i + nToSkip] == '\\')
292 : {
293 2804 : archiveFilename[i + nToSkip] = 0;
294 : }
295 :
296 3076 : if (!bCheckMainFileExists)
297 : {
298 138 : bArchiveFileExists = TRUE;
299 : }
300 : else
301 : {
302 2938 : CPLMutexHolder oHolder( &hMutex );
303 :
304 2938 : if (oFileList.find(archiveFilename) != oFileList.end() )
305 : {
306 2718 : bArchiveFileExists = TRUE;
307 2938 : }
308 : }
309 :
310 3076 : if (!bArchiveFileExists)
311 : {
312 : VSIFilesystemHandler *poFSHandler =
313 220 : VSIFileManager::GetHandler( archiveFilename );
314 220 : if (poFSHandler->Stat(archiveFilename, &statBuf,
315 220 : VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
316 : !VSI_ISDIR(statBuf.st_mode))
317 : {
318 162 : bArchiveFileExists = TRUE;
319 : }
320 : }
321 :
322 3076 : if (bArchiveFileExists)
323 : {
324 6036 : if (pszFilename[i + nToSkip] == '/' ||
325 230 : pszFilename[i + nToSkip] == '\\')
326 : {
327 2788 : char* pszArchiveInFileName = CPLStrdup(pszFilename + i + nToSkip + 1);
328 :
329 : /* Replace a/../b by b and foo/a/../b by foo/b */
330 0 : while(TRUE)
331 : {
332 2788 : char* pszPrevDir = strstr(pszArchiveInFileName, "/../");
333 2788 : if (pszPrevDir == NULL || pszPrevDir == pszArchiveInFileName)
334 : break;
335 :
336 0 : char* pszPrevSlash = pszPrevDir - 1;
337 0 : while(pszPrevSlash != pszArchiveInFileName &&
338 : *pszPrevSlash != '/')
339 0 : pszPrevSlash --;
340 0 : if (pszPrevSlash == pszArchiveInFileName)
341 0 : memmove(pszArchiveInFileName, pszPrevDir + nToSkip, strlen(pszPrevDir + nToSkip) + 1);
342 : else
343 0 : memmove(pszPrevSlash + 1, pszPrevDir + nToSkip, strlen(pszPrevDir + nToSkip) + 1);
344 : }
345 :
346 5576 : osFileInArchive = pszArchiveInFileName;
347 2788 : CPLFree(pszArchiveInFileName);
348 : }
349 : else
350 230 : osFileInArchive = "";
351 :
352 : /* Remove trailing slash */
353 3018 : if (osFileInArchive.size())
354 : {
355 2788 : char lastC = osFileInArchive[strlen(osFileInArchive) - 1];
356 2788 : if (lastC == '\\' || lastC == '/')
357 4 : osFileInArchive.resize(strlen(osFileInArchive) - 1);
358 : }
359 :
360 3018 : return archiveFilename;
361 : }
362 58 : CPLFree(archiveFilename);
363 : }
364 43084 : i++;
365 : }
366 92 : return NULL;
367 : }
368 :
369 : /************************************************************************/
370 : /* OpenArchiveFile() */
371 : /************************************************************************/
372 :
373 476 : VSIArchiveReader* VSIArchiveFilesystemHandler::OpenArchiveFile(const char* archiveFilename,
374 : const char* fileInArchiveName)
375 : {
376 476 : VSIArchiveReader* poReader = CreateReader(archiveFilename);
377 :
378 476 : if (poReader == NULL)
379 : {
380 12 : return NULL;
381 : }
382 :
383 488 : if (fileInArchiveName == NULL || strlen(fileInArchiveName) == 0)
384 : {
385 24 : if (poReader->GotoFirstFile() == FALSE)
386 : {
387 0 : delete(poReader);
388 0 : return NULL;
389 : }
390 :
391 : /* Skip optionnal leading subdir */
392 24 : CPLString osFileName = poReader->GetFileName();
393 24 : const char* fileName = osFileName.c_str();
394 24 : if (fileName[strlen(fileName)-1] == '/' || fileName[strlen(fileName)-1] == '\\')
395 : {
396 4 : if (poReader->GotoNextFile() == FALSE)
397 : {
398 0 : delete(poReader);
399 0 : return NULL;
400 : }
401 : }
402 :
403 24 : if (poReader->GotoNextFile())
404 : {
405 0 : CPLString msg;
406 : msg.Printf("Support only 1 file in archive file %s when no explicit in-archive filename is specified",
407 0 : archiveFilename);
408 0 : const VSIArchiveContent* content = GetContentOfArchive(archiveFilename, poReader);
409 0 : if (content)
410 : {
411 : int i;
412 0 : msg += "\nYou could try one of the following :\n";
413 0 : for(i=0;i<content->nEntries;i++)
414 : {
415 0 : msg += CPLString().Printf(" %s/%s/%s\n", GetPrefix(), archiveFilename, content->entries[i].fileName);
416 : }
417 : }
418 :
419 0 : CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
420 :
421 0 : delete(poReader);
422 0 : return NULL;
423 0 : }
424 : }
425 : else
426 : {
427 440 : const VSIArchiveEntry* archiveEntry = NULL;
428 440 : if (FindFileInArchive(archiveFilename, fileInArchiveName, &archiveEntry) == FALSE ||
429 : archiveEntry->bIsDir)
430 : {
431 42 : delete(poReader);
432 42 : return NULL;
433 : }
434 398 : if (!poReader->GotoFileOffset(archiveEntry->file_pos))
435 : {
436 0 : delete poReader;
437 0 : return NULL;
438 : }
439 : }
440 422 : return poReader;
441 : }
442 :
443 : /************************************************************************/
444 : /* Stat() */
445 : /************************************************************************/
446 :
447 116 : int VSIArchiveFilesystemHandler::Stat( const char *pszFilename, VSIStatBufL *pStatBuf, int nFlags )
448 : {
449 116 : int ret = -1;
450 116 : CPLString osFileInArchive;
451 :
452 116 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
453 :
454 116 : char* archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
455 116 : if (archiveFilename == NULL)
456 4 : return -1;
457 :
458 112 : if (strlen(osFileInArchive) != 0)
459 : {
460 : if (ENABLE_DEBUG) CPLDebug("VSIArchive", "Looking for %s %s\n",
461 : archiveFilename, osFileInArchive.c_str());
462 :
463 92 : const VSIArchiveEntry* archiveEntry = NULL;
464 92 : if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
465 : {
466 : /* Patching st_size with uncompressed file size */
467 68 : pStatBuf->st_size = (long)archiveEntry->uncompressed_size;
468 68 : pStatBuf->st_mtime = (time_t)archiveEntry->nModifiedTime;
469 68 : if (archiveEntry->bIsDir)
470 0 : pStatBuf->st_mode = S_IFDIR;
471 : else
472 68 : pStatBuf->st_mode = S_IFREG;
473 68 : ret = 0;
474 : }
475 : }
476 : else
477 : {
478 20 : VSIArchiveReader* poReader = CreateReader(archiveFilename);
479 20 : CPLFree(archiveFilename);
480 20 : archiveFilename = NULL;
481 :
482 20 : if (poReader != NULL && poReader->GotoFirstFile())
483 : {
484 : /* Skip optionnal leading subdir */
485 20 : CPLString osFileName = poReader->GetFileName();
486 20 : const char* fileName = osFileName.c_str();
487 20 : if (fileName[strlen(fileName)-1] == '/' || fileName[strlen(fileName)-1] == '\\')
488 : {
489 2 : if (poReader->GotoNextFile() == FALSE)
490 : {
491 0 : delete(poReader);
492 0 : return -1;
493 : }
494 : }
495 :
496 20 : if (poReader->GotoNextFile())
497 : {
498 : /* Several files in archive --> treat as dir */
499 10 : pStatBuf->st_size = 0;
500 10 : pStatBuf->st_mode = S_IFDIR;
501 : }
502 : else
503 : {
504 : /* Patching st_size with uncompressed file size */
505 10 : pStatBuf->st_size = (long)poReader->GetFileSize();
506 10 : pStatBuf->st_mtime = (time_t)poReader->GetModifiedTime();
507 10 : pStatBuf->st_mode = S_IFREG;
508 : }
509 :
510 20 : ret = 0;
511 : }
512 :
513 20 : delete(poReader);
514 : }
515 :
516 112 : CPLFree(archiveFilename);
517 112 : return ret;
518 : }
519 :
520 : /************************************************************************/
521 : /* Unlink() */
522 : /************************************************************************/
523 :
524 0 : int VSIArchiveFilesystemHandler::Unlink( const char *pszFilename )
525 : {
526 0 : return -1;
527 : }
528 :
529 : /************************************************************************/
530 : /* Rename() */
531 : /************************************************************************/
532 :
533 0 : int VSIArchiveFilesystemHandler::Rename( const char *oldpath, const char *newpath )
534 : {
535 0 : return -1;
536 : }
537 :
538 : /************************************************************************/
539 : /* Mkdir() */
540 : /************************************************************************/
541 :
542 0 : int VSIArchiveFilesystemHandler::Mkdir( const char *pszDirname, long nMode )
543 : {
544 0 : return -1;
545 : }
546 :
547 : /************************************************************************/
548 : /* Rmdir() */
549 : /************************************************************************/
550 :
551 0 : int VSIArchiveFilesystemHandler::Rmdir( const char *pszDirname )
552 : {
553 0 : return -1;
554 : }
555 :
556 : /************************************************************************/
557 : /* ReadDir() */
558 : /************************************************************************/
559 :
560 1100 : char** VSIArchiveFilesystemHandler::ReadDir( const char *pszDirname )
561 : {
562 1100 : CPLString osInArchiveSubDir;
563 1100 : char* archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
564 1100 : if (archiveFilename == NULL)
565 4 : return NULL;
566 1096 : int lenInArchiveSubDir = strlen(osInArchiveSubDir);
567 :
568 1096 : char **papszDir = NULL;
569 :
570 1096 : const VSIArchiveContent* content = GetContentOfArchive(archiveFilename);
571 1096 : if (!content)
572 : {
573 0 : CPLFree(archiveFilename);
574 0 : return NULL;
575 : }
576 :
577 : if (ENABLE_DEBUG) CPLDebug("VSIArchive", "Read dir %s", pszDirname);
578 : int i;
579 24544 : for(i=0;i<content->nEntries;i++)
580 : {
581 23448 : const char* fileName = content->entries[i].fileName;
582 : /* Only list entries at the same level of inArchiveSubDir */
583 27636 : if (lenInArchiveSubDir != 0 &&
584 : strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
585 3106 : (fileName[lenInArchiveSubDir] == '/' || fileName[lenInArchiveSubDir] == '\\') &&
586 1082 : fileName[lenInArchiveSubDir + 1] != 0)
587 : {
588 1082 : const char* slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
589 1082 : if (slash == NULL)
590 738 : slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
591 1082 : if (slash == NULL || slash[1] == 0)
592 : {
593 738 : char* tmpFileName = CPLStrdup(fileName);
594 738 : if (slash != NULL)
595 : {
596 0 : tmpFileName[strlen(tmpFileName)-1] = 0;
597 : }
598 : if (ENABLE_DEBUG)
599 : CPLDebug("VSIArchive", "Add %s as in directory %s\n",
600 : tmpFileName + lenInArchiveSubDir + 1, pszDirname);
601 738 : papszDir = CSLAddString(papszDir, tmpFileName + lenInArchiveSubDir + 1);
602 738 : CPLFree(tmpFileName);
603 : }
604 : }
605 22366 : else if (lenInArchiveSubDir == 0 &&
606 : strchr(fileName, '/') == NULL && strchr(fileName, '\\') == NULL)
607 : {
608 : /* Only list toplevel files and directories */
609 : if (ENABLE_DEBUG) CPLDebug("VSIArchive", "Add %s as in directory %s\n", fileName, pszDirname);
610 630 : papszDir = CSLAddString(papszDir, fileName);
611 : }
612 : }
613 :
614 1096 : CPLFree(archiveFilename);
615 1096 : return papszDir;
616 : }
|