Coverage Report

Created: 2025-08-28 06:57

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