Coverage Report

Created: 2025-08-28 06:57

/src/gdal/port/cpl_vsil_subfile.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  VSI Virtual File System
4
 * Purpose:  Implementation of subfile virtual IO functions.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2009-2014, 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 <cstring>
20
#include <limits>
21
22
#include "cpl_conv.h"
23
#include "cpl_multiproc.h"
24
#include "cpl_string.h"
25
#include "cpl_vsi_virtual.h"
26
27
/************************************************************************/
28
/* ==================================================================== */
29
/*                           VSISubFileHandle                           */
30
/* ==================================================================== */
31
/************************************************************************/
32
33
class VSISubFileHandle final : public VSIVirtualHandle
34
{
35
    CPL_DISALLOW_COPY_ASSIGN(VSISubFileHandle)
36
37
  public:
38
    VSILFILE *fp = nullptr;
39
    vsi_l_offset nSubregionOffset = 0;
40
    vsi_l_offset nSubregionSize = 0;
41
    bool bAtEOF = false;
42
    bool bError = false;
43
44
619
    VSISubFileHandle() = default;
45
    ~VSISubFileHandle() override;
46
47
    int Seek(vsi_l_offset nOffset, int nWhence) override;
48
    vsi_l_offset Tell() override;
49
    size_t Read(void *pBuffer, size_t nSize, size_t nMemb) override;
50
    size_t Write(const void *pBuffer, size_t nSize, size_t nMemb) override;
51
    void ClearErr() override;
52
    int Eof() override;
53
    int Error() override;
54
    int Close() override;
55
};
56
57
/************************************************************************/
58
/* ==================================================================== */
59
/*                   VSISubFileFilesystemHandler                        */
60
/* ==================================================================== */
61
/************************************************************************/
62
63
class VSISubFileFilesystemHandler final : public VSIFilesystemHandler
64
{
65
    CPL_DISALLOW_COPY_ASSIGN(VSISubFileFilesystemHandler)
66
67
  public:
68
3
    VSISubFileFilesystemHandler() = default;
69
    ~VSISubFileFilesystemHandler() override = default;
70
71
    static int DecomposePath(const char *pszPath, CPLString &osFilename,
72
                             vsi_l_offset &nSubFileOffset,
73
                             vsi_l_offset &nSubFileSize);
74
75
    VSIVirtualHandleUniquePtr Open(const char *pszFilename,
76
                                   const char *pszAccess, bool bSetError,
77
                                   CSLConstList /* papszOptions */) override;
78
    int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
79
             int nFlags) override;
80
    int Unlink(const char *pszFilename) override;
81
    int Mkdir(const char *pszDirname, long nMode) override;
82
    int Rmdir(const char *pszDirname) override;
83
    char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
84
};
85
86
/************************************************************************/
87
/* ==================================================================== */
88
/*                             VSISubFileHandle                         */
89
/* ==================================================================== */
90
/************************************************************************/
91
92
VSISubFileHandle::~VSISubFileHandle()
93
619
{
94
619
    VSISubFileHandle::Close();
95
619
}
96
97
/************************************************************************/
98
/*                               Close()                                */
99
/************************************************************************/
100
101
int VSISubFileHandle::Close()
102
103
1.04k
{
104
1.04k
    if (fp == nullptr)
105
430
        return -1;
106
619
    int nRet = VSIFCloseL(fp);
107
619
    fp = nullptr;
108
109
619
    return nRet;
110
1.04k
}
111
112
/************************************************************************/
113
/*                                Seek()                                */
114
/************************************************************************/
115
116
int VSISubFileHandle::Seek(vsi_l_offset nOffset, int nWhence)
117
118
2.09k
{
119
2.09k
    bAtEOF = false;
120
121
2.09k
    if (nWhence == SEEK_SET)
122
1.36k
    {
123
1.36k
        if (nOffset >
124
1.36k
            std::numeric_limits<vsi_l_offset>::max() - nSubregionOffset)
125
0
            return -1;
126
1.36k
        nOffset += nSubregionOffset;
127
1.36k
    }
128
732
    else if (nWhence == SEEK_CUR)
129
0
    {
130
        // handle normally.
131
0
    }
132
732
    else if (nWhence == SEEK_END)
133
732
    {
134
732
        if (nSubregionSize != 0)
135
0
        {
136
0
            nOffset = nSubregionOffset + nSubregionSize;
137
0
            nWhence = SEEK_SET;
138
0
        }
139
732
    }
140
0
    else
141
0
    {
142
0
        errno = EINVAL;
143
0
        return -1;
144
0
    }
145
146
2.09k
    return VSIFSeekL(fp, nOffset, nWhence);
147
2.09k
}
148
149
/************************************************************************/
150
/*                                Tell()                                */
151
/************************************************************************/
152
153
vsi_l_offset VSISubFileHandle::Tell()
154
155
732
{
156
732
    vsi_l_offset nBasePos = VSIFTellL(fp);
157
732
    if (nBasePos >= nSubregionOffset)
158
732
        return nBasePos - nSubregionOffset;
159
0
    return 0;
160
732
}
161
162
/************************************************************************/
163
/*                                Read()                                */
164
/************************************************************************/
165
166
size_t VSISubFileHandle::Read(void *pBuffer, size_t nSize, size_t nCount)
167
168
707
{
169
707
    size_t nRet = 0;
170
707
    if (nSubregionSize == 0)
171
707
    {
172
707
        nRet = VSIFReadL(pBuffer, nSize, nCount, fp);
173
707
    }
174
0
    else
175
0
    {
176
0
        if (nSize == 0)
177
0
            return 0;
178
179
0
        const vsi_l_offset nCurOffset = VSIFTellL(fp);
180
0
        if (nCurOffset >= nSubregionOffset + nSubregionSize)
181
0
        {
182
0
            bAtEOF = true;
183
0
            return 0;
184
0
        }
185
186
0
        const size_t nByteToRead = nSize * nCount;
187
0
        if (nCurOffset + nByteToRead > nSubregionOffset + nSubregionSize)
188
0
        {
189
0
            const int nRead = static_cast<int>(
190
0
                VSIFReadL(pBuffer, 1,
191
0
                          static_cast<size_t>(nSubregionOffset +
192
0
                                              nSubregionSize - nCurOffset),
193
0
                          fp));
194
0
            nRet = nRead / nSize;
195
0
        }
196
0
        else
197
0
        {
198
0
            nRet = VSIFReadL(pBuffer, nSize, nCount, fp);
199
0
        }
200
0
    }
201
202
707
    if (nRet < nCount)
203
707
    {
204
707
        if (fp->Eof())
205
707
            bAtEOF = true;
206
0
        else /* if (fp->Error()) */
207
0
            bError = true;
208
707
    }
209
210
707
    return nRet;
211
707
}
212
213
/************************************************************************/
214
/*                               Write()                                */
215
/************************************************************************/
216
217
size_t VSISubFileHandle::Write(const void *pBuffer, size_t nSize, size_t nCount)
218
219
0
{
220
0
    bAtEOF = false;
221
222
0
    if (nSubregionSize == 0)
223
0
        return VSIFWriteL(pBuffer, nSize, nCount, fp);
224
225
0
    if (nSize == 0)
226
0
        return 0;
227
228
0
    const vsi_l_offset nCurOffset = VSIFTellL(fp);
229
0
    if (nCurOffset >= nSubregionOffset + nSubregionSize)
230
0
        return 0;
231
232
0
    const size_t nByteToWrite = nSize * nCount;
233
0
    if (nCurOffset + nByteToWrite > nSubregionOffset + nSubregionSize)
234
0
    {
235
0
        const int nWritten = static_cast<int>(VSIFWriteL(
236
0
            pBuffer, 1,
237
0
            static_cast<size_t>(nSubregionOffset + nSubregionSize - nCurOffset),
238
0
            fp));
239
0
        return nWritten / nSize;
240
0
    }
241
242
0
    return VSIFWriteL(pBuffer, nSize, nCount, fp);
243
0
}
244
245
/************************************************************************/
246
/*                             ClearErr()                               */
247
/************************************************************************/
248
249
void VSISubFileHandle::ClearErr()
250
251
0
{
252
0
    fp->ClearErr();
253
0
    bAtEOF = false;
254
0
    bError = false;
255
0
}
256
257
/************************************************************************/
258
/*                              Error()                                 */
259
/************************************************************************/
260
261
int VSISubFileHandle::Error()
262
263
0
{
264
0
    return bError;
265
0
}
266
267
/************************************************************************/
268
/*                                Eof()                                 */
269
/************************************************************************/
270
271
int VSISubFileHandle::Eof()
272
273
532
{
274
532
    return bAtEOF;
275
532
}
276
277
/************************************************************************/
278
/* ==================================================================== */
279
/*                       VSISubFileFilesystemHandler                    */
280
/* ==================================================================== */
281
/************************************************************************/
282
283
/************************************************************************/
284
/*                           DecomposePath()                            */
285
/*                                                                      */
286
/*      Parse a path like /vsisubfile/1000_2000,data/abc.tif into an    */
287
/*      offset (1000), a size (2000) and a path (data/abc.tif).         */
288
/************************************************************************/
289
290
int VSISubFileFilesystemHandler::DecomposePath(const char *pszPath,
291
                                               CPLString &osFilename,
292
                                               vsi_l_offset &nSubFileOffset,
293
                                               vsi_l_offset &nSubFileSize)
294
295
4.20k
{
296
4.20k
    if (!STARTS_WITH(pszPath, "/vsisubfile/"))
297
0
        return FALSE;
298
299
4.20k
    osFilename = "";
300
4.20k
    nSubFileOffset = 0;
301
4.20k
    nSubFileSize = 0;
302
303
4.20k
    nSubFileOffset =
304
4.20k
        CPLScanUIntBig(pszPath + 12, static_cast<int>(strlen(pszPath + 12)));
305
230k
    for (int i = 12; pszPath[i] != '\0'; i++)
306
229k
    {
307
229k
        if (pszPath[i] == '_' && nSubFileSize == 0)
308
5.48k
        {
309
            // -1 is sometimes passed to mean that we don't know the file size
310
            // for example when creating a JPEG2000 datastream in a NITF file
311
            // Transform it into 0 for correct behavior of Read(), Write() and
312
            // Eof().
313
5.48k
            if (pszPath[i + 1] == '-')
314
334
                nSubFileSize = 0;
315
5.15k
            else
316
5.15k
                nSubFileSize = CPLScanUIntBig(
317
5.15k
                    pszPath + i + 1, static_cast<int>(strlen(pszPath + i + 1)));
318
5.48k
        }
319
224k
        else if (pszPath[i] == ',')
320
3.64k
        {
321
3.64k
            osFilename = pszPath + i + 1;
322
3.64k
            return TRUE;
323
3.64k
        }
324
220k
        else if (pszPath[i] == '/')
325
173
        {
326
            // Missing comma!
327
173
            return FALSE;
328
173
        }
329
229k
    }
330
331
394
    return FALSE;
332
4.20k
}
333
334
/************************************************************************/
335
/*                                Open()                                */
336
/************************************************************************/
337
338
VSIVirtualHandleUniquePtr
339
VSISubFileFilesystemHandler::Open(const char *pszFilename,
340
                                  const char *pszAccess, bool /* bSetError */,
341
                                  CSLConstList /* papszOptions */)
342
343
1.58k
{
344
1.58k
    if (!STARTS_WITH_CI(pszFilename, "/vsisubfile/"))
345
1
        return nullptr;
346
347
1.58k
    CPLString osSubFilePath;
348
1.58k
    vsi_l_offset nOff = 0;
349
1.58k
    vsi_l_offset nSize = 0;
350
351
1.58k
    if (!DecomposePath(pszFilename, osSubFilePath, nOff, nSize))
352
311
    {
353
311
        errno = ENOENT;
354
311
        return nullptr;
355
311
    }
356
1.27k
    if (nOff > std::numeric_limits<vsi_l_offset>::max() - nSize)
357
1
    {
358
1
        return nullptr;
359
1
    }
360
361
    /* -------------------------------------------------------------------- */
362
    /*      We can't open the containing file with "w" access, so if        */
363
    /*      that is requested use "r+" instead to update in place.          */
364
    /* -------------------------------------------------------------------- */
365
1.27k
    if (pszAccess[0] == 'w')
366
0
        pszAccess = "r+";
367
368
    /* -------------------------------------------------------------------- */
369
    /*      Open the underlying file.                                       */
370
    /* -------------------------------------------------------------------- */
371
1.27k
    auto fp = VSIFilesystemHandler::OpenStatic(osSubFilePath, pszAccess);
372
373
1.27k
    if (fp == nullptr)
374
655
        return nullptr;
375
376
    /* -------------------------------------------------------------------- */
377
    /*      Setup the file handle on this file.                             */
378
    /* -------------------------------------------------------------------- */
379
619
    auto poHandle = std::make_unique<VSISubFileHandle>();
380
381
619
    poHandle->fp = fp.release();
382
619
    poHandle->nSubregionOffset = nOff;
383
619
    poHandle->nSubregionSize = nSize;
384
385
    // In read-only mode validate (offset, size) against underlying file size
386
619
    if (strchr(pszAccess, 'r') != nullptr && strchr(pszAccess, '+') == nullptr)
387
619
    {
388
619
        if (VSIFSeekL(poHandle->fp, 0, SEEK_END) != 0)
389
100
        {
390
100
            return nullptr;
391
100
        }
392
519
        vsi_l_offset nFpSize = VSIFTellL(poHandle->fp);
393
        // For a directory, the size will be max(vsi_l_offset) / 2
394
519
        if (nFpSize == ~(static_cast<vsi_l_offset>(0)) / 2 || nOff > nFpSize)
395
89
        {
396
89
            return nullptr;
397
89
        }
398
430
        if (nOff + nSize > nFpSize)
399
140
        {
400
140
            nSize = nFpSize - nOff;
401
140
            poHandle->nSubregionSize = nSize;
402
140
        }
403
430
    }
404
405
430
    if (VSIFSeekL(poHandle->fp, nOff, SEEK_SET) != 0)
406
0
    {
407
0
        poHandle.reset();
408
0
    }
409
410
430
    return VSIVirtualHandleUniquePtr(poHandle.release());
411
619
}
412
413
/************************************************************************/
414
/*                                Stat()                                */
415
/************************************************************************/
416
417
int VSISubFileFilesystemHandler::Stat(const char *pszFilename,
418
                                      VSIStatBufL *psStatBuf, int nFlags)
419
420
2.63k
{
421
2.63k
    if (!STARTS_WITH_CI(pszFilename, "/vsisubfile/"))
422
12
        return -1;
423
424
2.62k
    CPLString osSubFilePath;
425
2.62k
    vsi_l_offset nOff = 0;
426
2.62k
    vsi_l_offset nSize = 0;
427
428
2.62k
    memset(psStatBuf, 0, sizeof(VSIStatBufL));
429
430
2.62k
    if (!DecomposePath(pszFilename, osSubFilePath, nOff, nSize))
431
256
    {
432
256
        errno = ENOENT;
433
256
        return -1;
434
256
    }
435
436
2.36k
    const int nResult = VSIStatExL(osSubFilePath, psStatBuf, nFlags);
437
438
2.36k
    if (nResult == 0)
439
702
    {
440
702
        if (nSize != 0)
441
149
            psStatBuf->st_size = nSize;
442
553
        else if (static_cast<vsi_l_offset>(psStatBuf->st_size) >= nOff)
443
446
            psStatBuf->st_size -= nOff;
444
107
        else
445
107
            psStatBuf->st_size = 0;
446
702
    }
447
448
2.36k
    return nResult;
449
2.62k
}
450
451
/************************************************************************/
452
/*                               Unlink()                               */
453
/************************************************************************/
454
455
int VSISubFileFilesystemHandler::Unlink(const char * /* pszFilename */)
456
0
{
457
0
    errno = EACCES;
458
0
    return -1;
459
0
}
460
461
/************************************************************************/
462
/*                               Mkdir()                                */
463
/************************************************************************/
464
465
int VSISubFileFilesystemHandler::Mkdir(const char * /* pszPathname */,
466
                                       long /* nMode */)
467
0
{
468
0
    errno = EACCES;
469
0
    return -1;
470
0
}
471
472
/************************************************************************/
473
/*                               Rmdir()                                */
474
/************************************************************************/
475
476
int VSISubFileFilesystemHandler::Rmdir(const char * /* pszPathname */)
477
478
0
{
479
0
    errno = EACCES;
480
0
    return -1;
481
0
}
482
483
/************************************************************************/
484
/*                             ReadDirEx()                              */
485
/************************************************************************/
486
487
char **VSISubFileFilesystemHandler::ReadDirEx(const char * /* pszPath */,
488
                                              int /* nMaxFiles */)
489
0
{
490
0
    errno = EACCES;
491
0
    return nullptr;
492
0
}
493
494
/************************************************************************/
495
/*                 VSIInstallSubFileFilesystemHandler()                 */
496
/************************************************************************/
497
498
/*!
499
 \brief Install /vsisubfile/ virtual file handler.
500
501
 \verbatim embed:rst
502
 See :ref:`/vsisubfile/ documentation <vsisubfile>`
503
 \endverbatim
504
 */
505
506
void VSIInstallSubFileHandler()
507
3
{
508
3
    VSIFileManager::InstallHandler("/vsisubfile/",
509
3
                                   new VSISubFileFilesystemHandler);
510
3
}