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 39 : explicit VSISparseFileHandle(VSISparseFileFilesystemHandler *poFS)
64 39 : : m_poFS(poFS)
65 : {
66 39 : }
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 nBytes) override;
78 : size_t Write(const void *pBuffer, size_t nBytes) 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 1789 : VSISparseFileFilesystemHandler() = default;
98 1126 : ~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 200 : int GetRecCounter()
115 : {
116 200 : 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 78 : VSISparseFileHandle::~VSISparseFileHandle()
141 : {
142 39 : VSISparseFileHandle::Close();
143 78 : }
144 :
145 : /************************************************************************/
146 : /* Close() */
147 : /************************************************************************/
148 :
149 80 : int VSISparseFileHandle::Close()
150 :
151 : {
152 186 : for (unsigned int i = 0; i < aoRegions.size(); i++)
153 : {
154 106 : if (aoRegions[i].fp != nullptr)
155 55 : CPL_IGNORE_RET_VAL(VSIFCloseL(aoRegions[i].fp));
156 : }
157 80 : aoRegions.clear();
158 :
159 80 : return 0;
160 : }
161 :
162 : /************************************************************************/
163 : /* Seek() */
164 : /************************************************************************/
165 :
166 378 : int VSISparseFileHandle::Seek(vsi_l_offset nOffset, int nWhence)
167 :
168 : {
169 378 : bEOF = false;
170 378 : if (nWhence == SEEK_SET)
171 306 : nCurOffset = nOffset;
172 72 : else if (nWhence == SEEK_CUR)
173 : {
174 60 : nCurOffset += nOffset;
175 : }
176 12 : else if (nWhence == SEEK_END)
177 : {
178 12 : nCurOffset = nOverallLength + nOffset;
179 : }
180 : else
181 : {
182 0 : errno = EINVAL;
183 0 : return -1;
184 : }
185 :
186 378 : return 0;
187 : }
188 :
189 : /************************************************************************/
190 : /* Tell() */
191 : /************************************************************************/
192 :
193 318 : vsi_l_offset VSISparseFileHandle::Tell()
194 :
195 : {
196 318 : return nCurOffset;
197 : }
198 :
199 : /************************************************************************/
200 : /* Read() */
201 : /************************************************************************/
202 :
203 7156 : size_t VSISparseFileHandle::Read(void *pBuffer, size_t nBytes)
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 = nBytes;
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;
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 : 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 55 : if (!aoRegions[iRegion].bTriedOpen)
300 : {
301 110 : aoRegions[iRegion].fp =
302 55 : VSIFOpenL(aoRegions[iRegion].osFilename, "r");
303 55 : if (aoRegions[iRegion].fp == nullptr)
304 : {
305 0 : CPLDebug("/vsisparse/", "Failed to open '%s'.",
306 0 : aoRegions[iRegion].osFilename.c_str());
307 : }
308 55 : aoRegions[iRegion].bTriedOpen = true;
309 : }
310 55 : if (aoRegions[iRegion].fp == nullptr)
311 : {
312 0 : bError = true;
313 0 : return 0;
314 : }
315 : }
316 :
317 7124 : if (aoRegions[iRegion].fp->Seek(nCurOffset -
318 14248 : aoRegions[iRegion].nDstOffset +
319 7124 : aoRegions[iRegion].nSrcOffset,
320 14248 : 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 : aoRegions[iRegion].fp->Read(pBuffer, nBytesRequested);
329 7124 : m_poFS->DecRecCounter();
330 7124 : if (nBytesRead < nBytesRequested)
331 : {
332 : // A short read in a region of a sparse file is always an error
333 0 : bError = true;
334 : }
335 :
336 7124 : nBytesReturnCount += nBytesRead;
337 : }
338 :
339 7149 : nCurOffset += nBytesReturnCount;
340 :
341 7149 : return nBytesReturnCount;
342 : }
343 :
344 : /************************************************************************/
345 : /* Write() */
346 : /************************************************************************/
347 :
348 0 : size_t VSISparseFileHandle::Write(const void * /* pBuffer */,
349 : size_t /* nBytes */)
350 : {
351 0 : errno = EBADF;
352 0 : return 0;
353 : }
354 :
355 : /************************************************************************/
356 : /* Eof() */
357 : /************************************************************************/
358 :
359 249 : int VSISparseFileHandle::Eof()
360 :
361 : {
362 249 : return bEOF ? 1 : 0;
363 : }
364 :
365 : /************************************************************************/
366 : /* Error() */
367 : /************************************************************************/
368 :
369 249 : int VSISparseFileHandle::Error()
370 :
371 : {
372 249 : return bError ? 1 : 0;
373 : }
374 :
375 : /************************************************************************/
376 : /* ClearErr() */
377 : /************************************************************************/
378 :
379 249 : void VSISparseFileHandle::ClearErr()
380 :
381 : {
382 747 : for (const auto ®ion : aoRegions)
383 : {
384 498 : if (region.fp)
385 498 : region.fp->ClearErr();
386 : }
387 249 : bEOF = false;
388 249 : bError = false;
389 249 : }
390 :
391 : /************************************************************************/
392 : /* ==================================================================== */
393 : /* VSISparseFileFilesystemHandler */
394 : /* ==================================================================== */
395 : /************************************************************************/
396 :
397 : /************************************************************************/
398 : /* Open() */
399 : /************************************************************************/
400 :
401 202 : VSIVirtualHandleUniquePtr VSISparseFileFilesystemHandler::Open(
402 : const char *pszFilename, const char *pszAccess, bool /* bSetError */,
403 : CSLConstList /* papszOptions */)
404 :
405 : {
406 202 : if (!STARTS_WITH_CI(pszFilename, "/vsisparse/"))
407 2 : return nullptr;
408 :
409 200 : if (!EQUAL(pszAccess, "r") && !EQUAL(pszAccess, "rb"))
410 : {
411 0 : errno = EACCES;
412 0 : return nullptr;
413 : }
414 :
415 : // Arbitrary number.
416 200 : if (GetRecCounter() == 32)
417 0 : return nullptr;
418 :
419 400 : const CPLString osSparseFilePath = pszFilename + 11;
420 :
421 : /* -------------------------------------------------------------------- */
422 : /* Does this file even exist? */
423 : /* -------------------------------------------------------------------- */
424 200 : if (VSIFilesystemHandler::OpenStatic(osSparseFilePath, "rb") == nullptr)
425 161 : return nullptr;
426 :
427 : /* -------------------------------------------------------------------- */
428 : /* Read the XML file. */
429 : /* -------------------------------------------------------------------- */
430 39 : CPLXMLNode *psXMLRoot = CPLParseXMLFile(osSparseFilePath);
431 :
432 39 : if (psXMLRoot == nullptr)
433 0 : return nullptr;
434 :
435 : /* -------------------------------------------------------------------- */
436 : /* Setup the file handle on this file. */
437 : /* -------------------------------------------------------------------- */
438 78 : auto poHandle = std::make_unique<VSISparseFileHandle>(this);
439 :
440 : /* -------------------------------------------------------------------- */
441 : /* Translate the desired fields out of the XML tree. */
442 : /* -------------------------------------------------------------------- */
443 174 : for (CPLXMLNode *psRegion = psXMLRoot->psChild; psRegion != nullptr;
444 135 : psRegion = psRegion->psNext)
445 : {
446 135 : if (psRegion->eType != CXT_Element)
447 29 : continue;
448 :
449 134 : if (!EQUAL(psRegion->pszValue, "SubfileRegion") &&
450 60 : !EQUAL(psRegion->pszValue, "ConstantRegion"))
451 28 : continue;
452 :
453 212 : SFRegion oRegion;
454 :
455 106 : oRegion.osFilename = CPLGetXMLValue(psRegion, "Filename", "");
456 106 : if (atoi(CPLGetXMLValue(psRegion, "Filename.relative", "0")) != 0)
457 : {
458 34 : const std::string osSFPath = CPLGetPathSafe(osSparseFilePath);
459 68 : oRegion.osFilename = CPLFormFilenameSafe(
460 34 : osSFPath.c_str(), oRegion.osFilename, nullptr);
461 : }
462 :
463 : // TODO(schwehr): Symbolic constant and an explanation for 32.
464 106 : oRegion.nDstOffset = CPLScanUIntBig(
465 : CPLGetXMLValue(psRegion, "DestinationOffset", "0"), 32);
466 :
467 106 : oRegion.nSrcOffset =
468 106 : CPLScanUIntBig(CPLGetXMLValue(psRegion, "SourceOffset", "0"), 32);
469 :
470 106 : oRegion.nLength =
471 106 : CPLScanUIntBig(CPLGetXMLValue(psRegion, "RegionLength", "0"), 32);
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 78 : poHandle->nOverallLength =
484 39 : CPLScanUIntBig(CPLGetXMLValue(psXMLRoot, "Length", "0"), 32);
485 39 : if (poHandle->nOverallLength == 0)
486 : {
487 33 : for (unsigned int i = 0; i < poHandle->aoRegions.size(); i++)
488 : {
489 22 : poHandle->nOverallLength = std::max(
490 44 : poHandle->nOverallLength, poHandle->aoRegions[i].nDstOffset +
491 22 : poHandle->aoRegions[i].nLength);
492 : }
493 : }
494 :
495 39 : CPLDestroyXMLNode(psXMLRoot);
496 :
497 39 : return VSIVirtualHandleUniquePtr(poHandle.release());
498 : }
499 :
500 : /************************************************************************/
501 : /* Stat() */
502 : /************************************************************************/
503 :
504 53 : int VSISparseFileFilesystemHandler::Stat(const char *pszFilename,
505 : VSIStatBufL *psStatBuf, int nFlags)
506 :
507 : {
508 106 : auto poFile = Open(pszFilename, "rb", false, nullptr);
509 :
510 53 : memset(psStatBuf, 0, sizeof(VSIStatBufL));
511 :
512 53 : if (poFile == nullptr)
513 46 : return -1;
514 :
515 7 : poFile->Seek(0, SEEK_END);
516 7 : const vsi_l_offset nLength = poFile->Tell();
517 :
518 : const int nResult =
519 7 : VSIStatExL(pszFilename + strlen("/vsisparse/"), psStatBuf, nFlags);
520 :
521 7 : psStatBuf->st_size = nLength;
522 :
523 7 : return nResult;
524 : }
525 :
526 : /************************************************************************/
527 : /* Unlink() */
528 : /************************************************************************/
529 :
530 0 : int VSISparseFileFilesystemHandler::Unlink(const char * /* pszFilename */)
531 : {
532 0 : errno = EACCES;
533 0 : return -1;
534 : }
535 :
536 : /************************************************************************/
537 : /* Mkdir() */
538 : /************************************************************************/
539 :
540 0 : int VSISparseFileFilesystemHandler::Mkdir(const char * /* pszPathname */,
541 : long /* nMode */)
542 : {
543 0 : errno = EACCES;
544 0 : return -1;
545 : }
546 :
547 : /************************************************************************/
548 : /* Rmdir() */
549 : /************************************************************************/
550 :
551 0 : int VSISparseFileFilesystemHandler::Rmdir(const char * /* pszPathname */)
552 : {
553 0 : errno = EACCES;
554 0 : return -1;
555 : }
556 :
557 : /************************************************************************/
558 : /* ReadDirEx() */
559 : /************************************************************************/
560 :
561 15 : char **VSISparseFileFilesystemHandler::ReadDirEx(const char * /* pszPath */,
562 : int /* nMaxFiles */)
563 : {
564 15 : errno = EACCES;
565 15 : return nullptr;
566 : }
567 :
568 : /************************************************************************/
569 : /* VSIInstallSparseFileFilesystemHandler() */
570 : /************************************************************************/
571 :
572 : /*!
573 : \brief Install /vsisparse/ virtual file handler.
574 :
575 : \verbatim embed:rst
576 : See :ref:`/vsisparse/ documentation <vsisparse>`
577 : \endverbatim
578 : */
579 :
580 1789 : void VSIInstallSparseFileHandler()
581 : {
582 1789 : VSIFileManager::InstallHandler(
583 3578 : "/vsisparse/", std::make_shared<VSISparseFileFilesystemHandler>());
584 1789 : }
|