Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: VSI Virtual File System
4 : * Purpose: Implementation of sparse file virtual io driver.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2010, Frank Warmerdam <warmerdam@pobox.com>
9 : * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
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_port.h"
31 : #include "cpl_vsi.h"
32 :
33 : #include <cerrno>
34 : #include <cstddef>
35 : #include <cstdlib>
36 : #include <cstring>
37 :
38 : #if HAVE_FCNTL_H
39 : #include <fcntl.h>
40 : #endif
41 :
42 : #include <algorithm>
43 : #include <map>
44 : #include <memory>
45 : #include <vector>
46 :
47 : #include "cpl_conv.h"
48 : #include "cpl_error.h"
49 : #include "cpl_minixml.h"
50 : #include "cpl_multiproc.h"
51 : #include "cpl_string.h"
52 : #include "cpl_vsi_virtual.h"
53 :
54 : class SFRegion
55 : {
56 : public:
57 : CPLString osFilename{};
58 : VSILFILE *fp = nullptr;
59 : GUIntBig nDstOffset = 0;
60 : GUIntBig nSrcOffset = 0;
61 : GUIntBig nLength = 0;
62 : GByte byValue = 0;
63 : bool bTriedOpen = false;
64 : };
65 :
66 : /************************************************************************/
67 : /* ==================================================================== */
68 : /* VSISparseFileHandle */
69 : /* ==================================================================== */
70 : /************************************************************************/
71 :
72 : class VSISparseFileFilesystemHandler;
73 :
74 : class VSISparseFileHandle : public VSIVirtualHandle
75 : {
76 : CPL_DISALLOW_COPY_ASSIGN(VSISparseFileHandle)
77 :
78 : VSISparseFileFilesystemHandler *m_poFS = nullptr;
79 : bool bEOF = false;
80 :
81 : public:
82 72 : explicit VSISparseFileHandle(VSISparseFileFilesystemHandler *poFS)
83 72 : : m_poFS(poFS)
84 : {
85 72 : }
86 :
87 : GUIntBig nOverallLength = 0;
88 : GUIntBig nCurOffset = 0;
89 :
90 : std::vector<SFRegion> aoRegions{};
91 :
92 : int Seek(vsi_l_offset nOffset, int nWhence) override;
93 : vsi_l_offset Tell() override;
94 : size_t Read(void *pBuffer, size_t nSize, size_t nMemb) override;
95 : size_t Write(const void *pBuffer, size_t nSize, size_t nMemb) override;
96 : int Eof() override;
97 : int Close() override;
98 : };
99 :
100 : /************************************************************************/
101 : /* ==================================================================== */
102 : /* VSISparseFileFilesystemHandler */
103 : /* ==================================================================== */
104 : /************************************************************************/
105 :
106 : class VSISparseFileFilesystemHandler : public VSIFilesystemHandler
107 : {
108 : std::map<GIntBig, int> oRecOpenCount{};
109 : CPL_DISALLOW_COPY_ASSIGN(VSISparseFileFilesystemHandler)
110 :
111 : public:
112 1228 : VSISparseFileFilesystemHandler() = default;
113 1704 : ~VSISparseFileFilesystemHandler() override = default;
114 :
115 : int DecomposePath(const char *pszPath, CPLString &osFilename,
116 : vsi_l_offset &nSparseFileOffset,
117 : vsi_l_offset &nSparseFileSize);
118 :
119 : // TODO(schwehr): Fix VSISparseFileFilesystemHandler::Stat to not need
120 : // using.
121 : using VSIFilesystemHandler::Open;
122 :
123 : VSIVirtualHandle *Open(const char *pszFilename, const char *pszAccess,
124 : bool bSetError,
125 : CSLConstList /* papszOptions */) override;
126 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
127 : int nFlags) override;
128 : int Unlink(const char *pszFilename) override;
129 : int Mkdir(const char *pszDirname, long nMode) override;
130 : int Rmdir(const char *pszDirname) override;
131 : char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
132 :
133 233 : int GetRecCounter()
134 : {
135 233 : return oRecOpenCount[CPLGetPID()];
136 : }
137 :
138 7124 : void IncRecCounter()
139 : {
140 7124 : oRecOpenCount[CPLGetPID()]++;
141 7124 : }
142 :
143 7124 : void DecRecCounter()
144 : {
145 7124 : oRecOpenCount[CPLGetPID()]--;
146 7124 : }
147 : };
148 :
149 : /************************************************************************/
150 : /* ==================================================================== */
151 : /* VSISparseFileHandle */
152 : /* ==================================================================== */
153 : /************************************************************************/
154 :
155 : /************************************************************************/
156 : /* Close() */
157 : /************************************************************************/
158 :
159 65 : int VSISparseFileHandle::Close()
160 :
161 : {
162 212 : for (unsigned int i = 0; i < aoRegions.size(); i++)
163 : {
164 147 : if (aoRegions[i].fp != nullptr)
165 121 : CPL_IGNORE_RET_VAL(VSIFCloseL(aoRegions[i].fp));
166 : }
167 :
168 65 : return 0;
169 : }
170 :
171 : /************************************************************************/
172 : /* Seek() */
173 : /************************************************************************/
174 :
175 336 : int VSISparseFileHandle::Seek(vsi_l_offset nOffset, int nWhence)
176 :
177 : {
178 336 : bEOF = false;
179 336 : if (nWhence == SEEK_SET)
180 265 : nCurOffset = nOffset;
181 71 : else if (nWhence == SEEK_CUR)
182 : {
183 60 : nCurOffset += nOffset;
184 : }
185 11 : else if (nWhence == SEEK_END)
186 : {
187 11 : nCurOffset = nOverallLength + nOffset;
188 : }
189 : else
190 : {
191 0 : errno = EINVAL;
192 0 : return -1;
193 : }
194 :
195 336 : return 0;
196 : }
197 :
198 : /************************************************************************/
199 : /* Tell() */
200 : /************************************************************************/
201 :
202 79 : vsi_l_offset VSISparseFileHandle::Tell()
203 :
204 : {
205 79 : return nCurOffset;
206 : }
207 :
208 : /************************************************************************/
209 : /* Read() */
210 : /************************************************************************/
211 :
212 7156 : size_t VSISparseFileHandle::Read(void *pBuffer, size_t nSize, size_t nCount)
213 :
214 : {
215 7156 : if (nCurOffset >= nOverallLength)
216 : {
217 5 : bEOF = true;
218 5 : return 0;
219 : }
220 :
221 : /* -------------------------------------------------------------------- */
222 : /* Find what region we are in, searching linearly from the */
223 : /* start. */
224 : /* -------------------------------------------------------------------- */
225 7151 : unsigned int iRegion = 0; // Used after for.
226 :
227 22233 : for (; iRegion < aoRegions.size(); iRegion++)
228 : {
229 44461 : if (nCurOffset >= aoRegions[iRegion].nDstOffset &&
230 22230 : nCurOffset <
231 22230 : aoRegions[iRegion].nDstOffset + aoRegions[iRegion].nLength)
232 7149 : break;
233 : }
234 :
235 7151 : size_t nBytesRequested = nSize * nCount;
236 7151 : if (nBytesRequested == 0)
237 : {
238 0 : return 0;
239 : }
240 7151 : if (nCurOffset + nBytesRequested > nOverallLength)
241 : {
242 12 : nBytesRequested = static_cast<size_t>(nOverallLength - nCurOffset);
243 12 : bEOF = true;
244 : }
245 :
246 : /* -------------------------------------------------------------------- */
247 : /* Default to zeroing the buffer if no corresponding region was */
248 : /* found. */
249 : /* -------------------------------------------------------------------- */
250 7151 : if (iRegion == aoRegions.size())
251 : {
252 2 : memset(pBuffer, 0, nBytesRequested);
253 2 : nCurOffset += nBytesRequested;
254 2 : return nBytesRequested / nSize;
255 : }
256 :
257 : /* -------------------------------------------------------------------- */
258 : /* If this request crosses region boundaries, split it into two */
259 : /* requests. */
260 : /* -------------------------------------------------------------------- */
261 7149 : size_t nBytesReturnCount = 0;
262 : const GUIntBig nEndOffsetOfRegion =
263 7149 : aoRegions[iRegion].nDstOffset + aoRegions[iRegion].nLength;
264 :
265 7149 : if (nCurOffset + nBytesRequested > nEndOffsetOfRegion)
266 : {
267 86 : const size_t nExtraBytes = static_cast<size_t>(
268 86 : nCurOffset + nBytesRequested - nEndOffsetOfRegion);
269 : // Recurse to get the rest of the request.
270 :
271 86 : const GUIntBig nCurOffsetSave = nCurOffset;
272 86 : nCurOffset += nBytesRequested - nExtraBytes;
273 86 : bool bEOFSave = bEOF;
274 86 : bEOF = false;
275 172 : const size_t nBytesRead = this->Read(static_cast<char *>(pBuffer) +
276 86 : nBytesRequested - nExtraBytes,
277 86 : 1, nExtraBytes);
278 86 : nCurOffset = nCurOffsetSave;
279 86 : bEOF = bEOFSave;
280 :
281 86 : nBytesReturnCount += nBytesRead;
282 86 : nBytesRequested -= nExtraBytes;
283 : }
284 :
285 : /* -------------------------------------------------------------------- */
286 : /* Handle a constant region. */
287 : /* -------------------------------------------------------------------- */
288 7149 : if (aoRegions[iRegion].osFilename.empty())
289 : {
290 25 : memset(pBuffer, aoRegions[iRegion].byValue,
291 : static_cast<size_t>(nBytesRequested));
292 :
293 25 : nBytesReturnCount += nBytesRequested;
294 : }
295 :
296 : /* -------------------------------------------------------------------- */
297 : /* Otherwise handle as a file. */
298 : /* -------------------------------------------------------------------- */
299 : else
300 : {
301 7124 : if (aoRegions[iRegion].fp == nullptr)
302 : {
303 121 : if (!aoRegions[iRegion].bTriedOpen)
304 : {
305 242 : aoRegions[iRegion].fp =
306 121 : VSIFOpenL(aoRegions[iRegion].osFilename, "r");
307 121 : if (aoRegions[iRegion].fp == nullptr)
308 : {
309 0 : CPLDebug("/vsisparse/", "Failed to open '%s'.",
310 0 : aoRegions[iRegion].osFilename.c_str());
311 : }
312 121 : aoRegions[iRegion].bTriedOpen = true;
313 : }
314 121 : if (aoRegions[iRegion].fp == nullptr)
315 : {
316 0 : return 0;
317 : }
318 : }
319 :
320 7124 : if (VSIFSeekL(aoRegions[iRegion].fp,
321 7124 : nCurOffset - aoRegions[iRegion].nDstOffset +
322 7124 : aoRegions[iRegion].nSrcOffset,
323 7124 : SEEK_SET) != 0)
324 0 : return 0;
325 :
326 7124 : m_poFS->IncRecCounter();
327 : const size_t nBytesRead =
328 7124 : VSIFReadL(pBuffer, 1, static_cast<size_t>(nBytesRequested),
329 7124 : aoRegions[iRegion].fp);
330 7124 : m_poFS->DecRecCounter();
331 :
332 7124 : nBytesReturnCount += nBytesRead;
333 : }
334 :
335 7149 : nCurOffset += nBytesReturnCount;
336 :
337 7149 : return nBytesReturnCount / nSize;
338 : }
339 :
340 : /************************************************************************/
341 : /* Write() */
342 : /************************************************************************/
343 :
344 0 : size_t VSISparseFileHandle::Write(const void * /* pBuffer */,
345 : size_t /* nSize */, size_t /* nCount */)
346 : {
347 0 : errno = EBADF;
348 0 : return 0;
349 : }
350 :
351 : /************************************************************************/
352 : /* Eof() */
353 : /************************************************************************/
354 :
355 0 : int VSISparseFileHandle::Eof()
356 :
357 : {
358 0 : return bEOF ? 1 : 0;
359 : }
360 :
361 : /************************************************************************/
362 : /* ==================================================================== */
363 : /* VSISparseFileFilesystemHandler */
364 : /* ==================================================================== */
365 : /************************************************************************/
366 :
367 : /************************************************************************/
368 : /* Open() */
369 : /************************************************************************/
370 :
371 235 : VSIVirtualHandle *VSISparseFileFilesystemHandler::Open(
372 : const char *pszFilename, const char *pszAccess, bool /* bSetError */,
373 : CSLConstList /* papszOptions */)
374 :
375 : {
376 235 : if (!STARTS_WITH_CI(pszFilename, "/vsisparse/"))
377 2 : return nullptr;
378 :
379 233 : if (!EQUAL(pszAccess, "r") && !EQUAL(pszAccess, "rb"))
380 : {
381 0 : errno = EACCES;
382 0 : return nullptr;
383 : }
384 :
385 : // Arbitrary number.
386 233 : if (GetRecCounter() == 32)
387 0 : return nullptr;
388 :
389 466 : const CPLString osSparseFilePath = pszFilename + 11;
390 :
391 : /* -------------------------------------------------------------------- */
392 : /* Does this file even exist? */
393 : /* -------------------------------------------------------------------- */
394 233 : VSILFILE *fp = VSIFOpenL(osSparseFilePath, "r");
395 233 : if (fp == nullptr)
396 161 : return nullptr;
397 72 : CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
398 :
399 : /* -------------------------------------------------------------------- */
400 : /* Read the XML file. */
401 : /* -------------------------------------------------------------------- */
402 72 : CPLXMLNode *psXMLRoot = CPLParseXMLFile(osSparseFilePath);
403 :
404 72 : if (psXMLRoot == nullptr)
405 0 : return nullptr;
406 :
407 : /* -------------------------------------------------------------------- */
408 : /* Setup the file handle on this file. */
409 : /* -------------------------------------------------------------------- */
410 72 : VSISparseFileHandle *poHandle = new VSISparseFileHandle(this);
411 :
412 : /* -------------------------------------------------------------------- */
413 : /* Translate the desired fields out of the XML tree. */
414 : /* -------------------------------------------------------------------- */
415 273 : for (CPLXMLNode *psRegion = psXMLRoot->psChild; psRegion != nullptr;
416 201 : psRegion = psRegion->psNext)
417 : {
418 201 : if (psRegion->eType != CXT_Element)
419 29 : continue;
420 :
421 200 : if (!EQUAL(psRegion->pszValue, "SubfileRegion") &&
422 60 : !EQUAL(psRegion->pszValue, "ConstantRegion"))
423 28 : continue;
424 :
425 344 : SFRegion oRegion;
426 :
427 172 : oRegion.osFilename = CPLGetXMLValue(psRegion, "Filename", "");
428 172 : if (atoi(CPLGetXMLValue(psRegion, "Filename.relative", "0")) != 0)
429 : {
430 34 : const CPLString osSFPath = CPLGetPath(osSparseFilePath);
431 : oRegion.osFilename =
432 34 : CPLFormFilename(osSFPath, oRegion.osFilename, nullptr);
433 : }
434 :
435 : // TODO(schwehr): Symbolic constant and an explanation for 32.
436 172 : oRegion.nDstOffset = CPLScanUIntBig(
437 : CPLGetXMLValue(psRegion, "DestinationOffset", "0"), 32);
438 :
439 172 : oRegion.nSrcOffset =
440 172 : CPLScanUIntBig(CPLGetXMLValue(psRegion, "SourceOffset", "0"), 32);
441 :
442 172 : oRegion.nLength =
443 172 : CPLScanUIntBig(CPLGetXMLValue(psRegion, "RegionLength", "0"), 32);
444 :
445 172 : oRegion.byValue =
446 172 : static_cast<GByte>(atoi(CPLGetXMLValue(psRegion, "Value", "0")));
447 :
448 172 : poHandle->aoRegions.push_back(oRegion);
449 : }
450 :
451 : /* -------------------------------------------------------------------- */
452 : /* Get sparse file length, use maximum bound of regions if not */
453 : /* explicit in file. */
454 : /* -------------------------------------------------------------------- */
455 72 : poHandle->nOverallLength =
456 72 : CPLScanUIntBig(CPLGetXMLValue(psXMLRoot, "Length", "0"), 32);
457 72 : if (poHandle->nOverallLength == 0)
458 : {
459 132 : for (unsigned int i = 0; i < poHandle->aoRegions.size(); i++)
460 : {
461 88 : poHandle->nOverallLength = std::max(
462 176 : poHandle->nOverallLength, poHandle->aoRegions[i].nDstOffset +
463 88 : poHandle->aoRegions[i].nLength);
464 : }
465 : }
466 :
467 72 : CPLDestroyXMLNode(psXMLRoot);
468 :
469 72 : return poHandle;
470 : }
471 :
472 : /************************************************************************/
473 : /* Stat() */
474 : /************************************************************************/
475 :
476 53 : int VSISparseFileFilesystemHandler::Stat(const char *pszFilename,
477 : VSIStatBufL *psStatBuf, int nFlags)
478 :
479 : {
480 : // TODO(schwehr): Fix this so that the using statement is not needed.
481 : // Will just adding the bool for bSetError be okay?
482 53 : VSIVirtualHandle *poFile = Open(pszFilename, "r");
483 :
484 53 : memset(psStatBuf, 0, sizeof(VSIStatBufL));
485 :
486 53 : if (poFile == nullptr)
487 46 : return -1;
488 :
489 7 : poFile->Seek(0, SEEK_END);
490 7 : const vsi_l_offset nLength = poFile->Tell();
491 7 : delete poFile;
492 :
493 : const int nResult =
494 7 : VSIStatExL(pszFilename + strlen("/vsisparse/"), psStatBuf, nFlags);
495 :
496 7 : psStatBuf->st_size = nLength;
497 :
498 7 : return nResult;
499 : }
500 :
501 : /************************************************************************/
502 : /* Unlink() */
503 : /************************************************************************/
504 :
505 0 : int VSISparseFileFilesystemHandler::Unlink(const char * /* pszFilename */)
506 : {
507 0 : errno = EACCES;
508 0 : return -1;
509 : }
510 :
511 : /************************************************************************/
512 : /* Mkdir() */
513 : /************************************************************************/
514 :
515 0 : int VSISparseFileFilesystemHandler::Mkdir(const char * /* pszPathname */,
516 : long /* nMode */)
517 : {
518 0 : errno = EACCES;
519 0 : return -1;
520 : }
521 :
522 : /************************************************************************/
523 : /* Rmdir() */
524 : /************************************************************************/
525 :
526 0 : int VSISparseFileFilesystemHandler::Rmdir(const char * /* pszPathname */)
527 : {
528 0 : errno = EACCES;
529 0 : return -1;
530 : }
531 :
532 : /************************************************************************/
533 : /* ReadDirEx() */
534 : /************************************************************************/
535 :
536 15 : char **VSISparseFileFilesystemHandler::ReadDirEx(const char * /* pszPath */,
537 : int /* nMaxFiles */)
538 : {
539 15 : errno = EACCES;
540 15 : return nullptr;
541 : }
542 :
543 : /************************************************************************/
544 : /* VSIInstallSparseFileFilesystemHandler() */
545 : /************************************************************************/
546 :
547 : /*!
548 : \brief Install /vsisparse/ virtual file handler.
549 :
550 : \verbatim embed:rst
551 : See :ref:`/vsisparse/ documentation <vsisparse>`
552 : \endverbatim
553 : */
554 :
555 1228 : void VSIInstallSparseFileHandler()
556 : {
557 1228 : VSIFileManager::InstallHandler("/vsisparse/",
558 1228 : new VSISparseFileFilesystemHandler);
559 1228 : }
|