1 : /******************************************************************************
2 : * $Id: cpl_vsil_abstract_archive.cpp 22980 2011-08-25 22:29:12Z 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 22980 2011-08-25 22:29:12Z rouault $");
39 :
40 : /************************************************************************/
41 : /* ~VSIArchiveEntryFileOffset() */
42 : /************************************************************************/
43 :
44 206 : VSIArchiveEntryFileOffset::~VSIArchiveEntryFileOffset()
45 : {
46 206 : }
47 :
48 : /************************************************************************/
49 : /* ~VSIArchiveReader() */
50 : /************************************************************************/
51 :
52 118 : VSIArchiveReader::~VSIArchiveReader()
53 : {
54 118 : }
55 :
56 : /************************************************************************/
57 : /* VSIArchiveFilesystemHandler() */
58 : /************************************************************************/
59 :
60 1294 : VSIArchiveFilesystemHandler::VSIArchiveFilesystemHandler()
61 : {
62 1294 : hMutex = NULL;
63 1294 : }
64 :
65 : /************************************************************************/
66 : /* ~VSIArchiveFilesystemHandler() */
67 : /************************************************************************/
68 :
69 1256 : VSIArchiveFilesystemHandler::~VSIArchiveFilesystemHandler()
70 :
71 : {
72 1256 : std::map<CPLString,VSIArchiveContent*>::const_iterator iter;
73 :
74 1276 : for( iter = oFileList.begin(); iter != oFileList.end(); ++iter )
75 : {
76 20 : VSIArchiveContent* content = iter->second;
77 : int i;
78 215 : for(i=0;i<content->nEntries;i++)
79 : {
80 195 : delete content->entries[i].file_pos;
81 195 : CPLFree(content->entries[i].fileName);
82 : }
83 20 : CPLFree(content->entries);
84 20 : delete content;
85 : }
86 :
87 1256 : if( hMutex != NULL )
88 2 : CPLDestroyMutex( hMutex );
89 1256 : hMutex = NULL;
90 1256 : }
91 :
92 : /************************************************************************/
93 : /* GetContentOfArchive() */
94 : /************************************************************************/
95 :
96 150 : const VSIArchiveContent* VSIArchiveFilesystemHandler::GetContentOfArchive
97 : (const char* archiveFilename, VSIArchiveReader* poReader)
98 : {
99 150 : CPLMutexHolder oHolder( &hMutex );
100 :
101 150 : if (oFileList.find(archiveFilename) != oFileList.end() )
102 : {
103 128 : return oFileList[archiveFilename];
104 : }
105 :
106 22 : int bMustClose = (poReader == NULL);
107 22 : if (poReader == NULL)
108 : {
109 22 : poReader = CreateReader(archiveFilename);
110 22 : if (!poReader)
111 0 : return NULL;
112 : }
113 :
114 22 : if (poReader->GotoFirstFile() == FALSE)
115 : {
116 0 : if (bMustClose)
117 0 : delete(poReader);
118 0 : return NULL;
119 : }
120 :
121 22 : VSIArchiveContent* content = new VSIArchiveContent;
122 22 : content->nEntries = 0;
123 22 : content->entries = NULL;
124 22 : oFileList[archiveFilename] = content;
125 :
126 22 : std::set<CPLString> oSet;
127 :
128 194 : do
129 : {
130 194 : CPLString osFileName = poReader->GetFileName();
131 194 : const char* fileName = osFileName.c_str();
132 :
133 : /* Remove ./ pattern at the beginning of a filename */
134 194 : if (fileName[0] == '.' && fileName[1] == '/')
135 : {
136 0 : fileName += 2;
137 0 : if (fileName[0] == '\0')
138 0 : continue;
139 : }
140 :
141 194 : char* pszStrippedFileName = CPLStrdup(fileName);
142 : int bIsDir = strlen(fileName) > 0 &&
143 194 : (fileName[strlen(fileName)-1] == '/' || fileName[strlen(fileName)-1] == '\\');
144 194 : if (bIsDir)
145 : {
146 : /* Remove trailing slash */
147 5 : pszStrippedFileName[strlen(fileName)-1] = 0;
148 : }
149 :
150 194 : if (oSet.find(pszStrippedFileName) == oSet.end())
151 : {
152 194 : oSet.insert(pszStrippedFileName);
153 :
154 : /* Add intermediate directory structure */
155 : char* pszIter;
156 2418 : for(pszIter = pszStrippedFileName;*pszIter;pszIter++)
157 : {
158 2224 : if (*pszIter == '/' || *pszIter == '\\')
159 : {
160 41 : char* pszStrippedFileName2 = CPLStrdup(pszStrippedFileName);
161 41 : pszStrippedFileName2[pszIter - pszStrippedFileName] = 0;
162 41 : if (oSet.find(pszStrippedFileName2) == oSet.end())
163 : {
164 4 : oSet.insert(pszStrippedFileName2);
165 :
166 : content->entries = (VSIArchiveEntry*)CPLRealloc(content->entries,
167 4 : sizeof(VSIArchiveEntry) * (content->nEntries + 1));
168 4 : content->entries[content->nEntries].fileName = pszStrippedFileName2;
169 4 : content->entries[content->nEntries].nModifiedTime = poReader->GetModifiedTime();
170 4 : content->entries[content->nEntries].uncompressed_size = 0;
171 4 : content->entries[content->nEntries].bIsDir = TRUE;
172 4 : content->entries[content->nEntries].file_pos = NULL;
173 : if (ENABLE_DEBUG)
174 : CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes", content->nEntries+1,
175 : content->entries[content->nEntries].fileName,
176 : content->entries[content->nEntries].uncompressed_size);
177 4 : content->nEntries++;
178 : }
179 : else
180 : {
181 37 : CPLFree(pszStrippedFileName2);
182 : }
183 : }
184 : }
185 :
186 : content->entries = (VSIArchiveEntry*)CPLRealloc(content->entries,
187 194 : sizeof(VSIArchiveEntry) * (content->nEntries + 1));
188 194 : content->entries[content->nEntries].fileName = pszStrippedFileName;
189 194 : content->entries[content->nEntries].nModifiedTime = poReader->GetModifiedTime();
190 194 : content->entries[content->nEntries].uncompressed_size = poReader->GetFileSize();
191 194 : content->entries[content->nEntries].bIsDir = bIsDir;
192 194 : content->entries[content->nEntries].file_pos = poReader->GetFileOffset();
193 : if (ENABLE_DEBUG)
194 : CPLDebug("VSIArchive", "[%d] %s : " CPL_FRMT_GUIB " bytes", content->nEntries+1,
195 : content->entries[content->nEntries].fileName,
196 : content->entries[content->nEntries].uncompressed_size);
197 194 : content->nEntries++;
198 : }
199 : else
200 : {
201 0 : CPLFree(pszStrippedFileName);
202 0 : }
203 194 : } while(poReader->GotoNextFile());
204 :
205 22 : if (bMustClose)
206 22 : delete(poReader);
207 :
208 22 : return content;
209 : }
210 :
211 : /************************************************************************/
212 : /* FindFileInArchive() */
213 : /************************************************************************/
214 :
215 120 : int VSIArchiveFilesystemHandler::FindFileInArchive(const char* archiveFilename,
216 : const char* fileInArchiveName,
217 : const VSIArchiveEntry** archiveEntry)
218 : {
219 120 : if (fileInArchiveName == NULL)
220 0 : return FALSE;
221 :
222 120 : const VSIArchiveContent* content = GetContentOfArchive(archiveFilename);
223 120 : if (content)
224 : {
225 : int i;
226 1180 : for(i=0;i<content->nEntries;i++)
227 : {
228 1145 : if (strcmp(fileInArchiveName, content->entries[i].fileName) == 0)
229 : {
230 85 : if (archiveEntry)
231 85 : *archiveEntry = &content->entries[i];
232 85 : return TRUE;
233 : }
234 : }
235 : }
236 35 : return FALSE;
237 : }
238 :
239 : /************************************************************************/
240 : /* SplitFilename() */
241 : /************************************************************************/
242 :
243 304 : char* VSIArchiveFilesystemHandler::SplitFilename(const char *pszFilename,
244 : CPLString &osFileInArchive,
245 : int bCheckMainFileExists)
246 : {
247 304 : int i = 0;
248 :
249 304 : if (strcmp(pszFilename, GetPrefix()) == 0)
250 0 : return NULL;
251 :
252 : /* Allow natural chaining of VSI drivers without requiring double slash */
253 :
254 304 : CPLString osDoubleVsi(GetPrefix());
255 304 : osDoubleVsi += "/vsi";
256 :
257 304 : if (strncmp(pszFilename, osDoubleVsi.c_str(), osDoubleVsi.size()) == 0)
258 53 : pszFilename += strlen(GetPrefix());
259 : else
260 251 : pszFilename += strlen(GetPrefix()) + 1;
261 :
262 304 : while(pszFilename[i])
263 : {
264 8486 : std::vector<CPLString> oExtensions = GetExtensions();
265 8486 : std::vector<CPLString>::const_iterator iter;
266 8486 : int nToSkip = 0;
267 :
268 25343 : for( iter = oExtensions.begin(); iter != oExtensions.end(); ++iter )
269 : {
270 17144 : const CPLString& osExtension = *iter;
271 17144 : if (EQUALN(pszFilename + i, osExtension.c_str(), strlen(osExtension.c_str())))
272 : {
273 287 : nToSkip = strlen(osExtension.c_str());
274 287 : break;
275 : }
276 : }
277 :
278 8486 : if (nToSkip != 0)
279 : {
280 : VSIStatBufL statBuf;
281 287 : char* archiveFilename = CPLStrdup(pszFilename);
282 287 : int bArchiveFileExists = FALSE;
283 :
284 375 : if (archiveFilename[i + nToSkip] == '/' ||
285 88 : archiveFilename[i + nToSkip] == '\\')
286 : {
287 199 : archiveFilename[i + nToSkip] = 0;
288 : }
289 :
290 287 : if (!bCheckMainFileExists)
291 : {
292 15 : bArchiveFileExists = TRUE;
293 : }
294 : else
295 : {
296 272 : CPLMutexHolder oHolder( &hMutex );
297 :
298 272 : if (oFileList.find(archiveFilename) != oFileList.end() )
299 : {
300 196 : bArchiveFileExists = TRUE;
301 272 : }
302 : }
303 :
304 287 : if (!bArchiveFileExists)
305 : {
306 : VSIFilesystemHandler *poFSHandler =
307 76 : VSIFileManager::GetHandler( archiveFilename );
308 76 : if (poFSHandler->Stat(archiveFilename, &statBuf,
309 76 : VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
310 : !VSI_ISDIR(statBuf.st_mode))
311 : {
312 55 : bArchiveFileExists = TRUE;
313 : }
314 : }
315 :
316 287 : if (bArchiveFileExists)
317 : {
318 532 : if (pszFilename[i + nToSkip] == '/' ||
319 67 : pszFilename[i + nToSkip] == '\\')
320 : {
321 199 : char* pszArchiveInFileName = CPLStrdup(pszFilename + i + nToSkip + 1);
322 :
323 : /* Replace a/../b by b and foo/a/../b by foo/b */
324 0 : while(TRUE)
325 : {
326 199 : char* pszPrevDir = strstr(pszArchiveInFileName, "/../");
327 199 : if (pszPrevDir == NULL || pszPrevDir == pszArchiveInFileName)
328 : break;
329 :
330 0 : char* pszPrevSlash = pszPrevDir - 1;
331 0 : while(pszPrevSlash != pszArchiveInFileName &&
332 : *pszPrevSlash != '/')
333 0 : pszPrevSlash --;
334 0 : if (pszPrevSlash == pszArchiveInFileName)
335 0 : memmove(pszArchiveInFileName, pszPrevDir + nToSkip, strlen(pszPrevDir + nToSkip) + 1);
336 : else
337 0 : memmove(pszPrevSlash + 1, pszPrevDir + nToSkip, strlen(pszPrevDir + nToSkip) + 1);
338 : }
339 :
340 398 : osFileInArchive = pszArchiveInFileName;
341 199 : CPLFree(pszArchiveInFileName);
342 : }
343 : else
344 67 : osFileInArchive = "";
345 :
346 : /* Remove trailing slash */
347 266 : if (strlen(osFileInArchive))
348 : {
349 199 : char lastC = osFileInArchive[strlen(osFileInArchive) - 1];
350 199 : if (lastC == '\\' || lastC == '/')
351 2 : osFileInArchive[strlen(osFileInArchive) - 1] = 0;
352 : }
353 :
354 266 : return archiveFilename;
355 : }
356 21 : CPLFree(archiveFilename);
357 : }
358 8220 : i++;
359 : }
360 38 : return NULL;
361 : }
362 :
363 : /************************************************************************/
364 : /* OpenArchiveFile() */
365 : /************************************************************************/
366 :
367 85 : VSIArchiveReader* VSIArchiveFilesystemHandler::OpenArchiveFile(const char* archiveFilename,
368 : const char* fileInArchiveName)
369 : {
370 85 : VSIArchiveReader* poReader = CreateReader(archiveFilename);
371 :
372 85 : if (poReader == NULL)
373 : {
374 0 : return NULL;
375 : }
376 :
377 95 : if (fileInArchiveName == NULL || strlen(fileInArchiveName) == 0)
378 : {
379 10 : if (poReader->GotoFirstFile() == FALSE)
380 : {
381 0 : delete(poReader);
382 0 : return NULL;
383 : }
384 :
385 : /* Skip optionnal leading subdir */
386 10 : CPLString osFileName = poReader->GetFileName();
387 10 : const char* fileName = osFileName.c_str();
388 10 : if (fileName[strlen(fileName)-1] == '/' || fileName[strlen(fileName)-1] == '\\')
389 : {
390 2 : if (poReader->GotoNextFile() == FALSE)
391 : {
392 0 : delete(poReader);
393 0 : return NULL;
394 : }
395 : }
396 :
397 10 : if (poReader->GotoNextFile())
398 : {
399 0 : CPLString msg;
400 : msg.Printf("Support only 1 file in archive file %s when no explicit in-archive filename is specified",
401 0 : archiveFilename);
402 0 : const VSIArchiveContent* content = GetContentOfArchive(archiveFilename, poReader);
403 0 : if (content)
404 : {
405 : int i;
406 0 : msg += "\nYou could try one of the following :\n";
407 0 : for(i=0;i<content->nEntries;i++)
408 : {
409 0 : msg += CPLString().Printf(" %s/%s/%s\n", GetPrefix(), archiveFilename, content->entries[i].fileName);
410 : }
411 : }
412 :
413 0 : CPLError(CE_Failure, CPLE_NotSupported, "%s", msg.c_str());
414 :
415 0 : delete(poReader);
416 0 : return NULL;
417 0 : }
418 : }
419 : else
420 : {
421 75 : const VSIArchiveEntry* archiveEntry = NULL;
422 75 : if (FindFileInArchive(archiveFilename, fileInArchiveName, &archiveEntry) == FALSE ||
423 : archiveEntry->bIsDir)
424 : {
425 23 : delete(poReader);
426 23 : return NULL;
427 : }
428 52 : if (!poReader->GotoFileOffset(archiveEntry->file_pos))
429 : {
430 0 : delete poReader;
431 0 : return NULL;
432 : }
433 : }
434 62 : return poReader;
435 : }
436 :
437 : /************************************************************************/
438 : /* Stat() */
439 : /************************************************************************/
440 :
441 58 : int VSIArchiveFilesystemHandler::Stat( const char *pszFilename, VSIStatBufL *pStatBuf, int nFlags )
442 : {
443 58 : int ret = -1;
444 58 : CPLString osFileInArchive;
445 :
446 58 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
447 :
448 58 : char* archiveFilename = SplitFilename(pszFilename, osFileInArchive, TRUE);
449 58 : if (archiveFilename == NULL)
450 2 : return -1;
451 :
452 56 : if (strlen(osFileInArchive) != 0)
453 : {
454 : if (ENABLE_DEBUG) CPLDebug("VSIArchive", "Looking for %s %s\n",
455 : archiveFilename, osFileInArchive.c_str());
456 :
457 45 : const VSIArchiveEntry* archiveEntry = NULL;
458 45 : if (FindFileInArchive(archiveFilename, osFileInArchive, &archiveEntry))
459 : {
460 : /* Patching st_size with uncompressed file size */
461 33 : pStatBuf->st_size = (long)archiveEntry->uncompressed_size;
462 33 : pStatBuf->st_mtime = (time_t)archiveEntry->nModifiedTime;
463 33 : if (archiveEntry->bIsDir)
464 0 : pStatBuf->st_mode = S_IFDIR;
465 : else
466 33 : pStatBuf->st_mode = S_IFREG;
467 33 : ret = 0;
468 : }
469 : }
470 : else
471 : {
472 11 : VSIArchiveReader* poReader = CreateReader(archiveFilename);
473 11 : CPLFree(archiveFilename);
474 11 : archiveFilename = NULL;
475 :
476 11 : if (poReader != NULL && poReader->GotoFirstFile())
477 : {
478 : /* Skip optionnal leading subdir */
479 11 : CPLString osFileName = poReader->GetFileName();
480 11 : const char* fileName = osFileName.c_str();
481 11 : if (fileName[strlen(fileName)-1] == '/' || fileName[strlen(fileName)-1] == '\\')
482 : {
483 1 : if (poReader->GotoNextFile() == FALSE)
484 : {
485 0 : delete(poReader);
486 0 : return -1;
487 : }
488 : }
489 :
490 11 : if (poReader->GotoNextFile())
491 : {
492 : /* Several files in archive --> treat as dir */
493 6 : pStatBuf->st_size = 0;
494 6 : pStatBuf->st_mode = S_IFDIR;
495 : }
496 : else
497 : {
498 : /* Patching st_size with uncompressed file size */
499 5 : pStatBuf->st_size = (long)poReader->GetFileSize();
500 5 : pStatBuf->st_mtime = (time_t)poReader->GetModifiedTime();
501 5 : pStatBuf->st_mode = S_IFREG;
502 : }
503 :
504 11 : ret = 0;
505 : }
506 :
507 11 : delete(poReader);
508 : }
509 :
510 56 : CPLFree(archiveFilename);
511 56 : return ret;
512 : }
513 :
514 : /************************************************************************/
515 : /* Unlink() */
516 : /************************************************************************/
517 :
518 0 : int VSIArchiveFilesystemHandler::Unlink( const char *pszFilename )
519 : {
520 0 : return -1;
521 : }
522 :
523 : /************************************************************************/
524 : /* Rename() */
525 : /************************************************************************/
526 :
527 0 : int VSIArchiveFilesystemHandler::Rename( const char *oldpath, const char *newpath )
528 : {
529 0 : return -1;
530 : }
531 :
532 : /************************************************************************/
533 : /* Mkdir() */
534 : /************************************************************************/
535 :
536 0 : int VSIArchiveFilesystemHandler::Mkdir( const char *pszDirname, long nMode )
537 : {
538 0 : return -1;
539 : }
540 :
541 : /************************************************************************/
542 : /* Rmdir() */
543 : /************************************************************************/
544 :
545 0 : int VSIArchiveFilesystemHandler::Rmdir( const char *pszDirname )
546 : {
547 0 : return -1;
548 : }
549 :
550 : /************************************************************************/
551 : /* ReadDir() */
552 : /************************************************************************/
553 :
554 32 : char** VSIArchiveFilesystemHandler::ReadDir( const char *pszDirname )
555 : {
556 32 : CPLString osInArchiveSubDir;
557 32 : char* archiveFilename = SplitFilename(pszDirname, osInArchiveSubDir, TRUE);
558 32 : if (archiveFilename == NULL)
559 2 : return NULL;
560 30 : int lenInArchiveSubDir = strlen(osInArchiveSubDir);
561 :
562 30 : char **papszDir = NULL;
563 :
564 30 : const VSIArchiveContent* content = GetContentOfArchive(archiveFilename);
565 30 : if (!content)
566 : {
567 0 : CPLFree(archiveFilename);
568 0 : return NULL;
569 : }
570 :
571 : if (ENABLE_DEBUG) CPLDebug("VSIArchive", "Read dir %s", pszDirname);
572 : int i;
573 264 : for(i=0;i<content->nEntries;i++)
574 : {
575 234 : const char* fileName = content->entries[i].fileName;
576 : /* Only list entries at the same level of inArchiveSubDir */
577 296 : if (lenInArchiveSubDir != 0 &&
578 : strncmp(fileName, osInArchiveSubDir, lenInArchiveSubDir) == 0 &&
579 42 : (fileName[lenInArchiveSubDir] == '/' || fileName[lenInArchiveSubDir] == '\\') &&
580 20 : fileName[lenInArchiveSubDir + 1] != 0)
581 : {
582 20 : const char* slash = strchr(fileName + lenInArchiveSubDir + 1, '/');
583 20 : if (slash == NULL)
584 13 : slash = strchr(fileName + lenInArchiveSubDir + 1, '\\');
585 20 : if (slash == NULL || slash[1] == 0)
586 : {
587 13 : char* tmpFileName = CPLStrdup(fileName);
588 13 : if (slash != NULL)
589 : {
590 0 : tmpFileName[strlen(tmpFileName)-1] = 0;
591 : }
592 : if (ENABLE_DEBUG)
593 : CPLDebug("VSIArchive", "Add %s as in directory %s\n",
594 : tmpFileName + lenInArchiveSubDir + 1, pszDirname);
595 13 : papszDir = CSLAddString(papszDir, tmpFileName + lenInArchiveSubDir + 1);
596 13 : CPLFree(tmpFileName);
597 : }
598 : }
599 214 : else if (lenInArchiveSubDir == 0 &&
600 : strchr(fileName, '/') == NULL && strchr(fileName, '\\') == NULL)
601 : {
602 : /* Only list toplevel files and directories */
603 : if (ENABLE_DEBUG) CPLDebug("VSIArchive", "Add %s as in directory %s\n", fileName, pszDirname);
604 182 : papszDir = CSLAddString(papszDir, fileName);
605 : }
606 : }
607 :
608 30 : CPLFree(archiveFilename);
609 30 : return papszDir;
610 : }
|