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