Coverage Report

Created: 2025-12-31 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cpl_vsil_subfile.cpp
Line
Count
Source
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
394
    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
394
{
94
394
    VSISubFileHandle::Close();
95
394
}
96
97
/************************************************************************/
98
/*                               Close()                                */
99
/************************************************************************/
100
101
int VSISubFileHandle::Close()
102
103
744
{
104
744
    if (fp == nullptr)
105
350
        return -1;
106
394
    int nRet = VSIFCloseL(fp);
107
394
    fp = nullptr;
108
109
394
    return nRet;
110
744
}
111
112
/************************************************************************/
113
/*                                Seek()                                */
114
/************************************************************************/
115
116
int VSISubFileHandle::Seek(vsi_l_offset nOffset, int nWhence)
117
118
1.50k
{
119
1.50k
    bAtEOF = false;
120
121
1.50k
    if (nWhence == SEEK_SET)
122
1.05k
    {
123
1.05k
        if (nOffset >
124
1.05k
            std::numeric_limits<vsi_l_offset>::max() - nSubregionOffset)
125
0
            return -1;
126
1.05k
        nOffset += nSubregionOffset;
127
1.05k
    }
128
445
    else if (nWhence == SEEK_CUR)
129
0
    {
130
        // handle normally.
131
0
    }
132
445
    else if (nWhence == SEEK_END)
133
445
    {
134
445
        if (nSubregionSize != 0)
135
0
        {
136
0
            nOffset = nSubregionOffset + nSubregionSize;
137
0
            nWhence = SEEK_SET;
138
0
        }
139
445
    }
140
0
    else
141
0
    {
142
0
        errno = EINVAL;
143
0
        return -1;
144
0
    }
145
146
1.50k
    return VSIFSeekL(fp, nOffset, nWhence);
147
1.50k
}
148
149
/************************************************************************/
150
/*                                Tell()                                */
151
/************************************************************************/
152
153
vsi_l_offset VSISubFileHandle::Tell()
154
155
445
{
156
445
    vsi_l_offset nBasePos = VSIFTellL(fp);
157
445
    if (nBasePos >= nSubregionOffset)
158
445
        return nBasePos - nSubregionOffset;
159
0
    return 0;
160
445
}
161
162
/************************************************************************/
163
/*                                Read()                                */
164
/************************************************************************/
165
166
size_t VSISubFileHandle::Read(void *pBuffer, size_t nSize, size_t nCount)
167
168
642
{
169
642
    size_t nRet = 0;
170
642
    if (nSubregionSize == 0)
171
642
    {
172
642
        nRet = VSIFReadL(pBuffer, nSize, nCount, fp);
173
642
    }
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
642
    if (nRet < nCount)
203
642
    {
204
642
        if (fp->Eof())
205
642
            bAtEOF = true;
206
0
        else /* if (fp->Error()) */
207
0
            bError = true;
208
642
    }
209
210
642
    return nRet;
211
642
}
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
404
{
274
404
    return bAtEOF;
275
404
}
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
3.42k
{
296
3.42k
    if (!STARTS_WITH(pszPath, "/vsisubfile/"))
297
0
        return FALSE;
298
299
3.42k
    osFilename = "";
300
3.42k
    nSubFileOffset = 0;
301
3.42k
    nSubFileSize = 0;
302
303
3.42k
    nSubFileOffset =
304
3.42k
        CPLScanUIntBig(pszPath + 12, static_cast<int>(strlen(pszPath + 12)));
305
196k
    for (int i = 12; pszPath[i] != '\0'; i++)
306
196k
    {
307
196k
        if (pszPath[i] == '_' && nSubFileSize == 0)
308
3.92k
        {
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
3.92k
            if (pszPath[i + 1] == '-')
314
108
                nSubFileSize = 0;
315
3.81k
            else
316
3.81k
                nSubFileSize = CPLScanUIntBig(
317
3.81k
                    pszPath + i + 1, static_cast<int>(strlen(pszPath + i + 1)));
318
3.92k
        }
319
192k
        else if (pszPath[i] == ',')
320
2.80k
        {
321
2.80k
            osFilename = pszPath + i + 1;
322
2.80k
            return TRUE;
323
2.80k
        }
324
189k
        else if (pszPath[i] == '/')
325
304
        {
326
            // Missing comma!
327
304
            return FALSE;
328
304
        }
329
196k
    }
330
331
313
    return FALSE;
332
3.42k
}
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.50k
{
344
1.50k
    if (!STARTS_WITH_CI(pszFilename, "/vsisubfile/"))
345
1
        return nullptr;
346
347
1.50k
    CPLString osSubFilePath;
348
1.50k
    vsi_l_offset nOff = 0;
349
1.50k
    vsi_l_offset nSize = 0;
350
351
1.50k
    if (!DecomposePath(pszFilename, osSubFilePath, nOff, nSize))
352
351
    {
353
351
        errno = ENOENT;
354
351
        return nullptr;
355
351
    }
356
1.15k
    if (nOff > std::numeric_limits<vsi_l_offset>::max() - nSize)
357
39
    {
358
39
        return nullptr;
359
39
    }
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.11k
    if (pszAccess[0] == 'w')
366
0
        pszAccess = "r+";
367
368
    /* -------------------------------------------------------------------- */
369
    /*      Open the underlying file.                                       */
370
    /* -------------------------------------------------------------------- */
371
1.11k
    auto fp = VSIFilesystemHandler::OpenStatic(osSubFilePath, pszAccess,
372
1.11k
                                               bSetError, papszOptions);
373
374
1.11k
    if (fp == nullptr)
375
721
        return nullptr;
376
377
    /* -------------------------------------------------------------------- */
378
    /*      Setup the file handle on this file.                             */
379
    /* -------------------------------------------------------------------- */
380
394
    auto poHandle = std::make_unique<VSISubFileHandle>();
381
382
394
    poHandle->fp = fp.release();
383
394
    poHandle->nSubregionOffset = nOff;
384
394
    poHandle->nSubregionSize = nSize;
385
386
    // In read-only mode validate (offset, size) against underlying file size
387
394
    if (strchr(pszAccess, 'r') != nullptr && strchr(pszAccess, '+') == nullptr)
388
394
    {
389
394
        if (VSIFSeekL(poHandle->fp, 0, SEEK_END) != 0)
390
0
        {
391
0
            return nullptr;
392
0
        }
393
394
        vsi_l_offset nFpSize = VSIFTellL(poHandle->fp);
394
        // For a directory, the size will be max(vsi_l_offset) / 2
395
394
        if (nFpSize == ~(static_cast<vsi_l_offset>(0)) / 2 || nOff > nFpSize)
396
44
        {
397
44
            return nullptr;
398
44
        }
399
350
        if (nOff + nSize > nFpSize)
400
94
        {
401
94
            nSize = nFpSize - nOff;
402
94
            poHandle->nSubregionSize = nSize;
403
94
        }
404
350
    }
405
406
350
    if (VSIFSeekL(poHandle->fp, nOff, SEEK_SET) != 0)
407
0
    {
408
0
        poHandle.reset();
409
0
    }
410
411
350
    return VSIVirtualHandleUniquePtr(poHandle.release());
412
394
}
413
414
/************************************************************************/
415
/*                                Stat()                                */
416
/************************************************************************/
417
418
int VSISubFileFilesystemHandler::Stat(const char *pszFilename,
419
                                      VSIStatBufL *psStatBuf, int nFlags)
420
421
2.02k
{
422
2.02k
    if (!STARTS_WITH_CI(pszFilename, "/vsisubfile/"))
423
100
        return -1;
424
425
1.92k
    CPLString osSubFilePath;
426
1.92k
    vsi_l_offset nOff = 0;
427
1.92k
    vsi_l_offset nSize = 0;
428
429
1.92k
    memset(psStatBuf, 0, sizeof(VSIStatBufL));
430
431
1.92k
    if (!DecomposePath(pszFilename, osSubFilePath, nOff, nSize))
432
266
    {
433
266
        errno = ENOENT;
434
266
        return -1;
435
266
    }
436
437
1.65k
    const int nResult = VSIStatExL(osSubFilePath, psStatBuf, nFlags);
438
439
1.65k
    if (nResult == 0)
440
379
    {
441
379
        if (nSize != 0)
442
95
            psStatBuf->st_size = nSize;
443
284
        else if (static_cast<vsi_l_offset>(psStatBuf->st_size) >= nOff)
444
262
            psStatBuf->st_size -= nOff;
445
22
        else
446
22
            psStatBuf->st_size = 0;
447
379
    }
448
449
1.65k
    return nResult;
450
1.92k
}
451
452
/************************************************************************/
453
/*                               Unlink()                               */
454
/************************************************************************/
455
456
int VSISubFileFilesystemHandler::Unlink(const char * /* pszFilename */)
457
0
{
458
0
    errno = EACCES;
459
0
    return -1;
460
0
}
461
462
/************************************************************************/
463
/*                               Mkdir()                                */
464
/************************************************************************/
465
466
int VSISubFileFilesystemHandler::Mkdir(const char * /* pszPathname */,
467
                                       long /* nMode */)
468
0
{
469
0
    errno = EACCES;
470
0
    return -1;
471
0
}
472
473
/************************************************************************/
474
/*                               Rmdir()                                */
475
/************************************************************************/
476
477
int VSISubFileFilesystemHandler::Rmdir(const char * /* pszPathname */)
478
479
0
{
480
0
    errno = EACCES;
481
0
    return -1;
482
0
}
483
484
/************************************************************************/
485
/*                             ReadDirEx()                              */
486
/************************************************************************/
487
488
char **VSISubFileFilesystemHandler::ReadDirEx(const char * /* pszPath */,
489
                                              int /* nMaxFiles */)
490
0
{
491
0
    errno = EACCES;
492
0
    return nullptr;
493
0
}
494
495
/************************************************************************/
496
/*                 VSIInstallSubFileFilesystemHandler()                 */
497
/************************************************************************/
498
499
/*!
500
 \brief Install /vsisubfile/ virtual file handler.
501
502
 \verbatim embed:rst
503
 See :ref:`/vsisubfile/ documentation <vsisubfile>`
504
 \endverbatim
505
 */
506
507
void VSIInstallSubFileHandler()
508
3
{
509
3
    VSIFileManager::InstallHandler(
510
3
        "/vsisubfile/", std::make_shared<VSISubFileFilesystemHandler>());
511
3
}