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