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