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