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