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