Coverage Report

Created: 2026-07-25 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cpl_vsil_sparsefile.cpp
Line
Count
Source
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
    explicit VSISparseFileHandle(VSISparseFileFilesystemHandler *poFS)
65
0
        : m_poFS(poFS)
66
0
    {
67
0
    }
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
2
    VSISparseFileFilesystemHandler() = default;
99
0
    ~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
    int GetRecCounter()
116
0
    {
117
0
        return oRecOpenCount[CPLGetPID()];
118
0
    }
119
120
    void IncRecCounter()
121
0
    {
122
0
        oRecOpenCount[CPLGetPID()]++;
123
0
    }
124
125
    void DecRecCounter()
126
0
    {
127
0
        oRecOpenCount[CPLGetPID()]--;
128
0
    }
129
};
130
131
/************************************************************************/
132
/* ==================================================================== */
133
/*                             VSISparseFileHandle                      */
134
/* ==================================================================== */
135
/************************************************************************/
136
137
/************************************************************************/
138
/*                        ~VSISparseFileHandle()                        */
139
/************************************************************************/
140
141
VSISparseFileHandle::~VSISparseFileHandle()
142
0
{
143
0
    VSISparseFileHandle::Close();
144
0
}
145
146
/************************************************************************/
147
/*                               Close()                                */
148
/************************************************************************/
149
150
int VSISparseFileHandle::Close()
151
152
0
{
153
0
    for (unsigned int i = 0; i < aoRegions.size(); i++)
154
0
    {
155
0
        if (aoRegions[i].fp != nullptr)
156
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(aoRegions[i].fp));
157
0
    }
158
0
    aoRegions.clear();
159
160
0
    return 0;
161
0
}
162
163
/************************************************************************/
164
/*                                Seek()                                */
165
/************************************************************************/
166
167
int VSISparseFileHandle::Seek(vsi_l_offset nOffset, int nWhence)
168
169
0
{
170
0
    bEOF = false;
171
0
    if (nWhence == SEEK_SET)
172
0
        nCurOffset = nOffset;
173
0
    else if (nWhence == SEEK_CUR)
174
0
    {
175
0
        nCurOffset += nOffset;
176
0
    }
177
0
    else if (nWhence == SEEK_END)
178
0
    {
179
0
        nCurOffset = nOverallLength + nOffset;
180
0
    }
181
0
    else
182
0
    {
183
0
        errno = EINVAL;
184
0
        return -1;
185
0
    }
186
187
0
    return 0;
188
0
}
189
190
/************************************************************************/
191
/*                                Tell()                                */
192
/************************************************************************/
193
194
vsi_l_offset VSISparseFileHandle::Tell()
195
196
0
{
197
0
    return nCurOffset;
198
0
}
199
200
/************************************************************************/
201
/*                                Read()                                */
202
/************************************************************************/
203
204
size_t VSISparseFileHandle::Read(void *pBuffer, size_t nBytes)
205
206
0
{
207
0
    if (nCurOffset >= nOverallLength)
208
0
    {
209
0
        bEOF = true;
210
0
        return 0;
211
0
    }
212
213
    /* -------------------------------------------------------------------- */
214
    /*      Find what region we are in, searching linearly from the         */
215
    /*      start.                                                          */
216
    /* -------------------------------------------------------------------- */
217
0
    unsigned int iRegion = 0;  // Used after for.
218
219
0
    for (; iRegion < aoRegions.size(); iRegion++)
220
0
    {
221
0
        if (nCurOffset >= aoRegions[iRegion].nDstOffset &&
222
0
            nCurOffset <
223
0
                aoRegions[iRegion].nDstOffset + aoRegions[iRegion].nLength)
224
0
            break;
225
0
    }
226
227
0
    size_t nBytesRequested = nBytes;
228
0
    if (nBytesRequested == 0)
229
0
    {
230
0
        return 0;
231
0
    }
232
0
    if (nCurOffset + nBytesRequested > nOverallLength)
233
0
    {
234
0
        nBytesRequested = static_cast<size_t>(nOverallLength - nCurOffset);
235
0
        bEOF = true;
236
0
    }
237
238
    /* -------------------------------------------------------------------- */
239
    /*      Default to zeroing the buffer if no corresponding region was    */
240
    /*      found.                                                          */
241
    /* -------------------------------------------------------------------- */
242
0
    if (iRegion == aoRegions.size())
243
0
    {
244
0
        memset(pBuffer, 0, nBytesRequested);
245
0
        nCurOffset += nBytesRequested;
246
0
        return nBytesRequested;
247
0
    }
248
249
    /* -------------------------------------------------------------------- */
250
    /*      If this request crosses region boundaries, split it into two    */
251
    /*      requests.                                                       */
252
    /* -------------------------------------------------------------------- */
253
0
    size_t nBytesReturnCount = 0;
254
0
    const uint64_t nEndOffsetOfRegion =
255
0
        aoRegions[iRegion].nDstOffset + aoRegions[iRegion].nLength;
256
257
0
    if (nCurOffset + nBytesRequested > nEndOffsetOfRegion)
258
0
    {
259
0
        const size_t nExtraBytes = static_cast<size_t>(
260
0
            nCurOffset + nBytesRequested - nEndOffsetOfRegion);
261
        // Recurse to get the rest of the request.
262
263
0
        const uint64_t nCurOffsetSave = nCurOffset;
264
0
        nCurOffset += nBytesRequested - nExtraBytes;
265
0
        bool bEOFSave = bEOF;
266
0
        bEOF = false;
267
0
        const size_t nBytesRead = this->Read(static_cast<char *>(pBuffer) +
268
0
                                                 nBytesRequested - nExtraBytes,
269
0
                                             nExtraBytes);
270
0
        nCurOffset = nCurOffsetSave;
271
0
        bEOF = bEOFSave;
272
0
        if (nBytesRead < nExtraBytes)
273
0
        {
274
            // A short read in a region of a sparse file is always an error
275
0
            bError = true;
276
0
        }
277
278
0
        nBytesReturnCount += nBytesRead;
279
0
        nBytesRequested -= nExtraBytes;
280
0
    }
281
282
    /* -------------------------------------------------------------------- */
283
    /*      Handle a constant region.                                       */
284
    /* -------------------------------------------------------------------- */
285
0
    if (aoRegions[iRegion].osFilename.empty())
286
0
    {
287
0
        memset(pBuffer, aoRegions[iRegion].byValue,
288
0
               static_cast<size_t>(nBytesRequested));
289
290
0
        nBytesReturnCount += nBytesRequested;
291
0
    }
292
293
    /* -------------------------------------------------------------------- */
294
    /*      Otherwise handle as a file.                                     */
295
    /* -------------------------------------------------------------------- */
296
0
    else
297
0
    {
298
0
        if (aoRegions[iRegion].fp == nullptr)
299
0
        {
300
0
            if (!aoRegions[iRegion].bTriedOpen)
301
0
            {
302
0
                aoRegions[iRegion].fp =
303
0
                    VSIFOpenL(aoRegions[iRegion].osFilename, "r");
304
0
                if (aoRegions[iRegion].fp == nullptr)
305
0
                {
306
0
                    CPLDebug("/vsisparse/", "Failed to open '%s'.",
307
0
                             aoRegions[iRegion].osFilename.c_str());
308
0
                }
309
0
                aoRegions[iRegion].bTriedOpen = true;
310
0
            }
311
0
            if (aoRegions[iRegion].fp == nullptr)
312
0
            {
313
0
                bError = true;
314
0
                return 0;
315
0
            }
316
0
        }
317
318
0
        if (aoRegions[iRegion].fp->Seek(nCurOffset -
319
0
                                            aoRegions[iRegion].nDstOffset +
320
0
                                            aoRegions[iRegion].nSrcOffset,
321
0
                                        SEEK_SET) != 0)
322
0
        {
323
0
            bError = true;
324
0
            return 0;
325
0
        }
326
327
0
        m_poFS->IncRecCounter();
328
0
        const size_t nBytesRead =
329
0
            aoRegions[iRegion].fp->Read(pBuffer, nBytesRequested);
330
0
        m_poFS->DecRecCounter();
331
0
        if (nBytesRead < 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;
343
0
}
344
345
/************************************************************************/
346
/*                               Write()                                */
347
/************************************************************************/
348
349
size_t VSISparseFileHandle::Write(const void * /* pBuffer */,
350
                                  size_t /* nBytes */)
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
0
{
407
0
    if (!STARTS_WITH_CI(pszFilename, "/vsisparse/"))
408
0
        return nullptr;
409
410
0
    if (!EQUAL(pszAccess, "r") && !EQUAL(pszAccess, "rb"))
411
0
    {
412
0
        errno = EACCES;
413
0
        return nullptr;
414
0
    }
415
416
    // Arbitrary number.
417
0
    if (GetRecCounter() == 32)
418
0
        return nullptr;
419
420
0
    const CPLString osSparseFilePath = pszFilename + 11;
421
422
    /* -------------------------------------------------------------------- */
423
    /*      Does this file even exist?                                      */
424
    /* -------------------------------------------------------------------- */
425
0
    if (VSIFilesystemHandler::OpenStatic(osSparseFilePath, "rb") == nullptr)
426
0
        return nullptr;
427
428
    /* -------------------------------------------------------------------- */
429
    /*      Read the XML file.                                              */
430
    /* -------------------------------------------------------------------- */
431
0
    CPLXMLTreeCloser psXMLRoot(CPLParseXMLFile(osSparseFilePath));
432
433
0
    if (psXMLRoot == nullptr)
434
0
        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
0
        oRegion.nDstOffset = std::strtoull(
465
0
            CPLGetXMLValue(psRegion, "DestinationOffset", "0"), nullptr, 10);
466
467
0
        oRegion.nSrcOffset = std::strtoull(
468
0
            CPLGetXMLValue(psRegion, "SourceOffset", "0"), nullptr, 10);
469
470
0
        oRegion.nLength = std::strtoull(
471
0
            CPLGetXMLValue(psRegion, "RegionLength", "0"), nullptr, 10);
472
473
0
        oRegion.byValue =
474
0
            static_cast<GByte>(atoi(CPLGetXMLValue(psRegion, "Value", "0")));
475
476
0
        poHandle->aoRegions.push_back(std::move(oRegion));
477
0
    }
478
479
    /* -------------------------------------------------------------------- */
480
    /*      Get sparse file length, use maximum bound of regions if not     */
481
    /*      explicit in file.                                               */
482
    /* -------------------------------------------------------------------- */
483
0
    poHandle->nOverallLength = std::strtoull(
484
0
        CPLGetXMLValue(psXMLRoot.get(), "Length", "0"), nullptr, 10);
485
0
    if (poHandle->nOverallLength == 0)
486
0
    {
487
0
        for (unsigned int i = 0; i < poHandle->aoRegions.size(); i++)
488
0
        {
489
0
            if (poHandle->aoRegions[i].nDstOffset >
490
0
                std::numeric_limits<uint64_t>::max() -
491
0
                    poHandle->aoRegions[i].nLength)
492
0
            {
493
0
                return nullptr;
494
0
            }
495
0
            poHandle->nOverallLength = std::max(
496
0
                poHandle->nOverallLength, poHandle->aoRegions[i].nDstOffset +
497
0
                                              poHandle->aoRegions[i].nLength);
498
0
        }
499
0
    }
500
501
0
    return VSIVirtualHandleUniquePtr(poHandle.release());
502
0
}
503
504
/************************************************************************/
505
/*                                Stat()                                */
506
/************************************************************************/
507
508
int VSISparseFileFilesystemHandler::Stat(const char *pszFilename,
509
                                         VSIStatBufL *psStatBuf, int nFlags)
510
511
0
{
512
0
    auto poFile = Open(pszFilename, "rb", false, nullptr);
513
514
0
    memset(psStatBuf, 0, sizeof(VSIStatBufL));
515
516
0
    if (poFile == nullptr)
517
0
        return -1;
518
519
0
    poFile->Seek(0, SEEK_END);
520
0
    const vsi_l_offset nLength = poFile->Tell();
521
522
0
    const int nResult =
523
0
        VSIStatExL(pszFilename + strlen("/vsisparse/"), psStatBuf, nFlags);
524
525
0
    psStatBuf->st_size = nLength;
526
527
0
    return nResult;
528
0
}
529
530
/************************************************************************/
531
/*                               Unlink()                               */
532
/************************************************************************/
533
534
int VSISparseFileFilesystemHandler::Unlink(const char * /* pszFilename */)
535
0
{
536
0
    errno = EACCES;
537
0
    return -1;
538
0
}
539
540
/************************************************************************/
541
/*                               Mkdir()                                */
542
/************************************************************************/
543
544
int VSISparseFileFilesystemHandler::Mkdir(const char * /* pszPathname */,
545
                                          long /* nMode */)
546
0
{
547
0
    errno = EACCES;
548
0
    return -1;
549
0
}
550
551
/************************************************************************/
552
/*                               Rmdir()                                */
553
/************************************************************************/
554
555
int VSISparseFileFilesystemHandler::Rmdir(const char * /* pszPathname */)
556
0
{
557
0
    errno = EACCES;
558
0
    return -1;
559
0
}
560
561
/************************************************************************/
562
/*                             ReadDirEx()                              */
563
/************************************************************************/
564
565
char **VSISparseFileFilesystemHandler::ReadDirEx(const char * /* pszPath */,
566
                                                 int /* nMaxFiles */)
567
0
{
568
0
    errno = EACCES;
569
0
    return nullptr;
570
0
}
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
void VSIInstallSparseFileHandler()
585
2
{
586
2
    VSIFileManager::InstallHandler(
587
2
        "/vsisparse/", std::make_shared<VSISparseFileFilesystemHandler>());
588
2
}