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 2465 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
45 : {
46 2465 : }
47 :
48 : /************************************************************************/
49 : /* ~VSIArchiveReader() */
50 : /************************************************************************/
51 :
52 376 : VSIArchiveReader::~VSIArchiveReader()
53 : {
54 376 : }
55 :
56 : /************************************************************************/
57 : /* VSIArchiveFilesystemHandler() */
58 : /************************************************************************/
59 :
60 1424 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
61 : {
62 1424 : hMutex = NULL;
63 1424 : }
64 :
65 : /************************************************************************/
66 : /* ~VSIArchiveFilesystemHandler() */
67 : /************************************************************************/
68 :
69 1374 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
70 :
71 : {
72 1374 : std::map<CPLString,VSIArchiveContent*>::const_iterator iter;
73 :
74 1423 : for( iter = oFileList.begin(); iter != oFileList.end(); ++iter )
75 : {
76 49 : VSIArchiveContent* content = iter->second;
77 : int i;
78 3870 : for(i=0;i<content->nEntries;i++)
79 : {
80 3821 : delete content->entries[i].file_pos;
81 3821 : CPLFree(content->entries[i].fileName);
82 : }
83 49 : CPLFree(content->entries);
84 49 : delete content;
85 : }
86 :
87 1374 : if( hMutex != NULL )
88 6 : CPLDestroyMutex( hMutex );
89 1374 : hMutex = NULL;
90 1374 : }
91 :
92 : /************************************************************************/
93 : /* GetContentOfArchive() */
94 : /************************************************************************/
95 :
96 2913 : const VSIArchiveContent* VSIArchiveFilesystemHandler::GetContentOfArchive
97 : (const char* archiveFilename, VSIArchiveReader* poReader)
98 : {
99 2913 : CPLMutexHolder oHolder( &hMutex );
100 :
101 2913 : if (oFileList.find(archiveFilename) != oFileList.end() )
102 : {
103 2860 : return oFileList[archiveFilename];
104 : }
105 :
106 53 : int bMustClose = (poReader == NULL);
107 53 : if (poReader == NULL)
108 : {
109 53 : poReader = CreateReader(archiveFilename);
110 53 : if (!poReader)
111 0 : return NULL;
112 : }
113 :
114 53 : if (poReader->GotoFirstFile() == FALSE)
115 : {
116 0 : if (bMustClose)
117 0 : delete(poReader);
118 0 : return NULL;
119 : }
120 :
121 53 : VSIArchiveContent* content = new VSIArchiveContent;
122 53 : content->nEntries = 0;
123 53 : content->entries = NULL;
124 53 : oFileList[archiveFilename] = content;
125 :
126 53 : std::set<CPLString> oSet;
127 :
128 2449 : do
129 : {
130 2449 : CPLString osFileName = poReader->GetFileName();
131 2449 : const char* fileName = osFileName.c_str();
132 :
133 : /* Remove ./ pattern at the beginning of a filename */
134 2449 : if (fileName[0] == '.' && fileName[1] == '/')
135 : {
136 0 : fileName += 2;
137 0 : if (fileName[0] == '\0')
138 0 : continue;
139 : }
140 :
141 2449 : char* pszStrippedFileName = CPLStrdup(fileName);
142 : char* pszIter;
143 90438 : for(pszIter = pszStrippedFileName;*pszIter;pszIter++)
144 : {
145 87989 : if (*pszIter == '\\')
146 0 : *pszIter = '/';
147 : }
148 :
149 : int bIsDir = strlen(fileName) > 0 &&
150 2449 : fileName[strlen(fileName)-1] == '/';
151 2449 : if (bIsDir)
152 : {
153 : /* Remove trailing slash */
154 43 : pszStrippedFileName[strlen(fileName)-1] = 0;
155 : }
156 :
157 2449 : if (oSet.find(pszStrippedFileName) == oSet.end())
158 : {
159 2449 : oSet.insert(pszStrippedFileName);
160 :
161 : /* Add intermediate directory structure */
162 90395 : for(pszIter = pszStrippedFileName;*pszIter;pszIter++)
163 : {
164 87946 : if (*pszIter == '/')
165 : {
166 7443 : char* pszStrippedFileName2 = CPLStrdup(pszStrippedFileName);
167 7443 : pszStrippedFileName2[pszIter - pszStrippedFileName] = 0;
168 7443 : if (oSet.find(pszStrippedFileName2) == oSet.end())
169 : {
170 1421 : oSet.insert(pszStrippedFileName2);
171 :
172 : content->entries = (VSIArchiveEntry*)CPLRealloc(content->entries,
173 1421 : sizeof(VSIArchiveEntry) * (content->nEntries + 1));
174 1421 : content->entries[content->nEntries].fileName = pszStrippedFileName2;
175 1421 : content->entries[content->nEntries].nModifiedTime = poReader->GetModifiedTime();
176 1421 : content->entries[content->nEntries].uncompressed_size = 0;
177 1421 : content->entries[content->nEntries].bIsDir = TRUE;
178 1421 : 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 1421 : content->nEntries++;
184 : }
185 : else
186 : {
187 6022 : CPLFree(pszStrippedFileName2);
188 : }
189 : }
190 : }
191 :
192 : content->entries = (VSIArchiveEntry*)CPLRealloc(content->entries,
193 2449 : sizeof(VSIArchiveEntry) * (content->nEntries + 1));
194 2449 : content->entries[content->nEntries].fileName = pszStrippedFileName;
195 2449 : content->entries[content->nEntries].nModifiedTime = poReader->GetModifiedTime();
196 2449 : content->entries[content->nEntries].uncompressed_size = poReader->GetFileSize();
197 2449 : content->entries[content->nEntries].bIsDir = bIsDir;
198 2449 : 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 2449 : content->nEntries++;
204 : }
205 : else
206 : {
207 0 : CPLFree(pszStrippedFileName);
208 0 : }
209 2449 : } while(poReader->GotoNextFile());
210 :
211 53 : if (bMustClose)
212 53 : delete(poReader);
213 :
214 53 : return content;
215 : }
216 :
217 : /************************************************************************/
218 : /* FindFileInArchive() */
219 : /************************************************************************/
220 :
221 1345 : int VSIArchiveFilesystemHandler::FindFileInArchive(const char* archiveFilename,
222 : const char* fileInArchiveName,
223 : const VSIArchiveEntry** archiveEntry)
224 : {
225 1345 : if (fileInArchiveName == NULL)
226 0 : return FALSE;
227 :
228 1345 : const VSIArchiveContent* content = GetContentOfArchive(archiveFilename);
229 1345 : if (content)
230 : {
231 : int i;
232 527688 : for(i=0;i<content->nEntries;i++)
233 : {
234 527647 : if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
235 : {
236 1304 : if (archiveEntry)
237 1304 : *archiveEntry = &content->entries[i];
238 1304 : return TRUE;
239 : }
240 : }
241 : }
242 41 : return FALSE;
243 : }
244 :
245 : /************************************************************************/
246 : /* SplitFilename() */
247 : /************************************************************************/
248 :
249 5691 : char* VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
250 : CPLString &osFileInArchive,
251 : int bCheckMainFileExists)
252 : {
253 5691 : int i = 0;
254 :
255 5691 : if (strcmp(pszFilename, GetPrefix()) == 0)
256 0 : return NULL;
257 :
258 : /* Allow natural chaining of VSI drivers without requiring double slash */
259 :
260 5691 : CPLString osDoubleVsi(GetPrefix());
261 5691 : osDoubleVsi += "/vsi";
262 :
263 5691 : if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
264 4060 : pszFilename += strlen(GetPrefix());
265 : else
266 1631 : pszFilename += strlen(GetPrefix()) + 1;
267 :
268 5691 : while(pszFilename[i])
269 : {
270 107134 : std::vector<CPLString> oExtensions = GetExtensions();
271 107134 : std::vector<CPLString>::const_iterator iter;
272 107134 : int nToSkip = 0;
273 :
274 618065 : for( iter = oExtensions.begin(); iter != oExtensions.end(); ++iter )
275 : {
276 516605 : const CPLString& osExtension = *iter;
277 516605 : if (EQUALN(pszFilename + i, osExtension.c_str(), strlen(osExtension.c_str())))
278 : {
279 5674 : nToSkip = strlen(osExtension.c_str());
280 5674 : break;
281 : }
282 : }
283 :
284 107134 : if (nToSkip != 0)
285 : {
286 : VSIStatBufL statBuf;
287 5674 : char* archiveFilename = CPLStrdup(pszFilename);
288 5674 : int bArchiveFileExists = FALSE;
289 :
290 5855 : if (archiveFilename[i + nToSkip] == '/' ||
291 181 : archiveFilename[i + nToSkip] == '\\')
292 : {
293 5493 : archiveFilename[i + nToSkip] = 0;
294 : }
295 :
296 5674 : if (!bCheckMainFileExists)
297 : {
298 71 : bArchiveFileExists = TRUE;
299 : }
300 : else
301 : {
302 5603 : CPLMutexHolder oHolder( &hMutex );
303 :
304 5603 : if (oFileList.find(archiveFilename) != oFileList.end() )
305 : {
306 5457 : bArchiveFileExists = TRUE;
307 5603 : }
308 : }
309 :
310 5674 : if (!bArchiveFileExists)
311 : {
312 : VSIFilesystemHandler *poFSHandler =
313 146 : VSIFileManager::GetHandler( archiveFilename );
314 146 : if (poFSHandler->Stat(archiveFilename, &statBuf,
315 146 : VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
316 : !VSI_ISDIR(statBuf.st_mode))
317 : {
318 117 : bArchiveFileExists = TRUE;
319 : }
320 : }
321 :
322 5674 : if (bArchiveFileExists)
323 : {
324 11290 : if (pszFilename[i + nToSkip] == '/' ||
325 160 : pszFilename[i + nToSkip] == '\\')
326 : {
327 5485 : 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 5485 : char* pszPrevDir = strstr(pszArchiveInFileName, "/../");
333 5485 : 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 10970 : osFileInArchive = pszArchiveInFileName;
347 5485 : CPLFree(pszArchiveInFileName);
348 : }
349 : else
350 160 : osFileInArchive = "";
351 :
352 : /* Remove trailing slash */
353 5645 : if (osFileInArchive.size())
354 : {
355 5485 : char lastC = osFileInArchive[strlen(osFileInArchive) - 1];
356 5485 : if (lastC == '\\' || lastC == '/')
357 2 : osFileInArchive.resize(strlen(osFileInArchive) - 1);
358 : }
359 :
360 5645 : return archiveFilename;
361 : }
362 29 : CPLFree(archiveFilename);
363 : }
364 101489 : i++;
365 : }
366 46 : return NULL;
367 : }
368 :
369 : /************************************************************************/
370 : /* OpenArchiveFile() */
371 : /************************************************************************/
372 :
373 312 : VSIArchiveReader* VSIArchiveFilesystemHandler::OpenArchiveFile(const char* archiveFilename,
374 : const char* fileInArchiveName)
375 : {
376 312 : VSIArchiveReader* poReader = CreateReader(archiveFilename);
377 :
378 312 : if (poReader == NULL)
379 : {
380 6 : return NULL;
381 : }
382 :
383 322 : if (fileInArchiveName == NULL || strlen(fileInArchiveName) == 0)
384 : {
385 16 : if (poReader->GotoFirstFile() == FALSE)
386 : {
387 0 : delete(poReader);
388 0 : return NULL;
389 : }
390 :
391 : /* Skip optionnal leading subdir */
392 16 : CPLString osFileName = poReader->GetFileName();
393 16 : const char* fileName = osFileName.c_str();
394 16 : if (fileName[strlen(fileName)-1] == '/' || fileName[strlen(fileName)-1] == '\\')
395 : {
396 3 : if (poReader->GotoNextFile() == FALSE)
397 : {
398 0 : delete(poReader);
399 0 : return NULL;
400 : }
401 : }
402 :
403 16 : 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 290 : const VSIArchiveEntry* archiveEntry = NULL;
428 290 : if (FindFileInArchive(archiveFilename, fileInArchiveName, &archiveEntry) == FALSE ||
429 : archiveEntry->bIsDir)
430 : {
431 27 : delete(poReader);
432 27 : return NULL;
433 : }
434 263 : if (!poReader->GotoFileOffset(archiveEntry->file_pos))
435 : {
436 0 : delete poReader;
437 0 : return NULL;
438 : }
439 : }
440 279 : return poReader;
441 : }
442 :
443 : /************************************************************************/
444 : /* Stat() */
445 : /************************************************************************/
446 :
447 1068 : int VSIArchiveFilesystemHandler::Stat( const char *pszFilename, VSIStatBufL *pStatBuf, int nFlags )
448 : {
449 1068 : int ret = -1;
450 1068 : CPLString osFileInArchive;
451 :
452 1068 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
453 :
454 1068 : char* archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
455 1068 : if (archiveFilename == NULL)
456 2 : return -1;
457 :
458 1066 : if (strlen(osFileInArchive) != 0)
459 : {
460 : if (ENABLE_DEBUG) CPLDebug("VSIArchive", "Looking for %s %s\n",
461 : archiveFilename, osFileInArchive.c_str());
462 :
463 1055 : const VSIArchiveEntry* archiveEntry = NULL;
464 1055 : if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
465 : {
466 : /* Patching st_size with uncompressed file size */
467 1041 : pStatBuf->st_size = (long)archiveEntry->uncompressed_size;
468 1041 : pStatBuf->st_mtime = (time_t)archiveEntry->nModifiedTime;
469 1041 : if (archiveEntry->bIsDir)
470 1002 : pStatBuf->st_mode = S_IFDIR;
471 : else
472 39 : pStatBuf->st_mode = S_IFREG;
473 1041 : ret = 0;
474 : }
475 : }
476 : else
477 : {
478 11 : VSIArchiveReader* poReader = CreateReader(archiveFilename);
479 11 : CPLFree(archiveFilename);
480 11 : archiveFilename = NULL;
481 :
482 11 : if (poReader != NULL && poReader->GotoFirstFile())
483 : {
484 : /* Skip optionnal leading subdir */
485 11 : CPLString osFileName = poReader->GetFileName();
486 11 : const char* fileName = osFileName.c_str();
487 11 : if (fileName[strlen(fileName)-1] == '/' || fileName[strlen(fileName)-1] == '\\')
488 : {
489 1 : if (poReader->GotoNextFile() == FALSE)
490 : {
491 0 : delete(poReader);
492 0 : return -1;
493 : }
494 : }
495 :
496 11 : if (poReader->GotoNextFile())
497 : {
498 : /* Several files in archive --> treat as dir */
499 6 : pStatBuf->st_size = 0;
500 6 : pStatBuf->st_mode = S_IFDIR;
501 : }
502 : else
503 : {
504 : /* Patching st_size with uncompressed file size */
505 5 : pStatBuf->st_size = (long)poReader->GetFileSize();
506 5 : pStatBuf->st_mtime = (time_t)poReader->GetModifiedTime();
507 5 : pStatBuf->st_mode = S_IFREG;
508 : }
509 :
510 11 : ret = 0;
511 : }
512 :
513 11 : delete(poReader);
514 : }
515 :
516 1066 : CPLFree(archiveFilename);
517 1066 : 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 1570 : char** VSIArchiveFilesystemHandler::ReadDir( const char *pszDirname )
561 : {
562 1570 : CPLString osInArchiveSubDir;
563 1570 : char* archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
564 1570 : if (archiveFilename == NULL)
565 2 : return NULL;
566 1568 : int lenInArchiveSubDir = strlen(osInArchiveSubDir);
567 :
568 1568 : char **papszDir = NULL;
569 :
570 1568 : const VSIArchiveContent* content = GetContentOfArchive(archiveFilename);
571 1568 : 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 1015406 : for(i=0;i<content->nEntries;i++)
580 : {
581 1013838 : const char* fileName = content->entries[i].fileName;
582 : /* Only list entries at the same level of inArchiveSubDir */
583 2018942 : if (lenInArchiveSubDir != 0 &&
584 : strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
585 504059 : (fileName[lenInArchiveSubDir] == '/' || fileName[lenInArchiveSubDir] == '\\') &&
586 501045 : fileName[lenInArchiveSubDir + 1] != 0)
587 : {
588 501045 : const char* slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
589 501045 : if (slash == NULL)
590 1371 : slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
591 501045 : if (slash == NULL || slash[1] == 0)
592 : {
593 1371 : char* tmpFileName = CPLStrdup(fileName);
594 1371 : 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 1371 : papszDir = CSLAddString(papszDir, tmpFileName + lenInArchiveSubDir + 1);
602 1371 : CPLFree(tmpFileName);
603 : }
604 : }
605 512793 : 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 358 : papszDir = CSLAddString(papszDir, fileName);
611 : }
612 : }
613 :
614 1568 : CPLFree(archiveFilename);
615 1568 : return papszDir;
616 : }
|