Coverage Report

Created: 2026-02-14 06:52

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
700
    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 nBytes) override;
50
    size_t Write(const void *pBuffer, size_t nBytes) 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
700
{
94
700
    VSISubFileHandle::Close();
95
700
}
96
97
/************************************************************************/
98
/*                               Close()                                */
99
/************************************************************************/
100
101
int VSISubFileHandle::Close()
102
103
1.36k
{
104
1.36k
    if (fp == nullptr)
105
667
        return -1;
106
700
    int nRet = VSIFCloseL(fp);
107
700
    fp = nullptr;
108
109
700
    return nRet;
110
1.36k
}
111
112
/************************************************************************/
113
/*                                Seek()                                */
114
/************************************************************************/
115
116
int VSISubFileHandle::Seek(vsi_l_offset nOffset, int nWhence)
117
118
5.32k
{
119
5.32k
    bAtEOF = false;
120
121
5.32k
    if (nWhence == SEEK_SET)
122
3.29k
    {
123
3.29k
        if (nOffset >
124
3.29k
            std::numeric_limits<vsi_l_offset>::max() - nSubregionOffset)
125
0
            return -1;
126
3.29k
        nOffset += nSubregionOffset;
127
3.29k
    }
128
2.03k
    else if (nWhence == SEEK_CUR)
129
0
    {
130
        // handle normally.
131
0
    }
132
2.03k
    else if (nWhence == SEEK_END)
133
2.03k
    {
134
2.03k
        if (nSubregionSize != 0)
135
0
        {
136
0
            nOffset = nSubregionOffset + nSubregionSize;
137
0
            nWhence = SEEK_SET;
138
0
        }
139
2.03k
    }
140
0
    else
141
0
    {
142
0
        errno = EINVAL;
143
0
        return -1;
144
0
    }
145
146
5.32k
    return VSIFSeekL(fp, nOffset, nWhence);
147
5.32k
}
148
149
/************************************************************************/
150
/*                                Tell()                                */
151
/************************************************************************/
152
153
vsi_l_offset VSISubFileHandle::Tell()
154
155
2.03k
{
156
2.03k
    vsi_l_offset nBasePos = VSIFTellL(fp);
157
2.03k
    if (nBasePos >= nSubregionOffset)
158
2.03k
        return nBasePos - nSubregionOffset;
159
0
    return 0;
160
2.03k
}
161
162
/************************************************************************/
163
/*                                Read()                                */
164
/************************************************************************/
165
166
size_t VSISubFileHandle::Read(void *pBuffer, size_t nByteToRead)
167
168
1.28k
{
169
1.28k
    if (nByteToRead == 0)
170
0
        return 0;
171
172
1.28k
    size_t nRet = 0;
173
1.28k
    if (nSubregionSize == 0)
174
1.28k
    {
175
1.28k
        nRet = fp->Read(pBuffer, nByteToRead);
176
1.28k
    }
177
0
    else
178
0
    {
179
0
        const vsi_l_offset nCurOffset = fp->Tell();
180
0
        if (nCurOffset >= nSubregionOffset + nSubregionSize)
181
0
        {
182
0
            bAtEOF = true;
183
0
            return 0;
184
0
        }
185
186
0
        if (nCurOffset + nByteToRead > nSubregionOffset + nSubregionSize)
187
0
        {
188
0
            nRet = fp->Read(pBuffer,
189
0
                            static_cast<size_t>(nSubregionOffset +
190
0
                                                nSubregionSize - nCurOffset));
191
0
        }
192
0
        else
193
0
        {
194
0
            nRet = fp->Read(pBuffer, nByteToRead);
195
0
        }
196
0
    }
197
198
1.28k
    if (nRet < nByteToRead)
199
1.28k
    {
200
1.28k
        if (fp->Eof())
201
1.28k
            bAtEOF = true;
202
0
        else /* if (fp->Error()) */
203
0
            bError = true;
204
1.28k
    }
205
206
1.28k
    return nRet;
207
1.28k
}
208
209
/************************************************************************/
210
/*                               Write()                                */
211
/************************************************************************/
212
213
size_t VSISubFileHandle::Write(const void *pBuffer, size_t nBytes)
214
215
0
{
216
0
    bAtEOF = false;
217
218
0
    if (nBytes == 0)
219
0
        return 0;
220
221
0
    if (nSubregionSize == 0)
222
0
        return fp->Write(pBuffer, nBytes);
223
224
0
    const vsi_l_offset nCurOffset = VSIFTellL(fp);
225
0
    if (nCurOffset >= nSubregionOffset + nSubregionSize)
226
0
        return 0;
227
228
0
    if (nCurOffset + nBytes > nSubregionOffset + nSubregionSize)
229
0
    {
230
0
        return fp->Write(pBuffer,
231
0
                         static_cast<size_t>(nSubregionOffset + nSubregionSize -
232
0
                                             nCurOffset));
233
0
    }
234
235
0
    return fp->Write(pBuffer, nBytes);
236
0
}
237
238
/************************************************************************/
239
/*                              ClearErr()                              */
240
/************************************************************************/
241
242
void VSISubFileHandle::ClearErr()
243
244
0
{
245
0
    fp->ClearErr();
246
0
    bAtEOF = false;
247
0
    bError = false;
248
0
}
249
250
/************************************************************************/
251
/*                               Error()                                */
252
/************************************************************************/
253
254
int VSISubFileHandle::Error()
255
256
0
{
257
0
    return bError;
258
0
}
259
260
/************************************************************************/
261
/*                                Eof()                                 */
262
/************************************************************************/
263
264
int VSISubFileHandle::Eof()
265
266
1.03k
{
267
1.03k
    return bAtEOF;
268
1.03k
}
269
270
/************************************************************************/
271
/* ==================================================================== */
272
/*                       VSISubFileFilesystemHandler                    */
273
/* ==================================================================== */
274
/************************************************************************/
275
276
/************************************************************************/
277
/*                           DecomposePath()                            */
278
/*                                                                      */
279
/*      Parse a path like /vsisubfile/1000_2000,data/abc.tif into an    */
280
/*      offset (1000), a size (2000) and a path (data/abc.tif).         */
281
/************************************************************************/
282
283
int VSISubFileFilesystemHandler::DecomposePath(const char *pszPath,
284
                                               CPLString &osFilename,
285
                                               vsi_l_offset &nSubFileOffset,
286
                                               vsi_l_offset &nSubFileSize)
287
288
3.20k
{
289
3.20k
    if (!STARTS_WITH(pszPath, "/vsisubfile/"))
290
0
        return FALSE;
291
292
3.20k
    osFilename = "";
293
3.20k
    nSubFileOffset = 0;
294
3.20k
    nSubFileSize = 0;
295
296
3.20k
    nSubFileOffset =
297
3.20k
        CPLScanUIntBig(pszPath + 12, static_cast<int>(strlen(pszPath + 12)));
298
222k
    for (int i = 12; pszPath[i] != '\0'; i++)
299
221k
    {
300
221k
        if (pszPath[i] == '_' && nSubFileSize == 0)
301
4.45k
        {
302
            // -1 is sometimes passed to mean that we don't know the file size
303
            // for example when creating a JPEG2000 datastream in a NITF file
304
            // Transform it into 0 for correct behavior of Read(), Write() and
305
            // Eof().
306
4.45k
            if (pszPath[i + 1] == '-')
307
253
                nSubFileSize = 0;
308
4.19k
            else
309
4.19k
                nSubFileSize = CPLScanUIntBig(
310
4.19k
                    pszPath + i + 1, static_cast<int>(strlen(pszPath + i + 1)));
311
4.45k
        }
312
217k
        else if (pszPath[i] == ',')
313
2.78k
        {
314
2.78k
            osFilename = pszPath + i + 1;
315
2.78k
            return TRUE;
316
2.78k
        }
317
214k
        else if (pszPath[i] == '/')
318
80
        {
319
            // Missing comma!
320
80
            return FALSE;
321
80
        }
322
221k
    }
323
324
337
    return FALSE;
325
3.20k
}
326
327
/************************************************************************/
328
/*                                Open()                                */
329
/************************************************************************/
330
331
VSIVirtualHandleUniquePtr
332
VSISubFileFilesystemHandler::Open(const char *pszFilename,
333
                                  const char *pszAccess, bool bSetError,
334
                                  CSLConstList papszOptions)
335
336
1.68k
{
337
1.68k
    if (!STARTS_WITH_CI(pszFilename, "/vsisubfile/"))
338
2
        return nullptr;
339
340
1.68k
    CPLString osSubFilePath;
341
1.68k
    vsi_l_offset nOff = 0;
342
1.68k
    vsi_l_offset nSize = 0;
343
344
1.68k
    if (!DecomposePath(pszFilename, osSubFilePath, nOff, nSize))
345
326
    {
346
326
        errno = ENOENT;
347
326
        return nullptr;
348
326
    }
349
1.36k
    if (nOff > std::numeric_limits<vsi_l_offset>::max() - nSize)
350
73
    {
351
73
        return nullptr;
352
73
    }
353
354
    /* -------------------------------------------------------------------- */
355
    /*      We can't open the containing file with "w" access, so if        */
356
    /*      that is requested use "r+" instead to update in place.          */
357
    /* -------------------------------------------------------------------- */
358
1.28k
    if (pszAccess[0] == 'w')
359
0
        pszAccess = "r+";
360
361
    /* -------------------------------------------------------------------- */
362
    /*      Open the underlying file.                                       */
363
    /* -------------------------------------------------------------------- */
364
1.28k
    auto fp = VSIFilesystemHandler::OpenStatic(osSubFilePath, pszAccess,
365
1.28k
                                               bSetError, papszOptions);
366
367
1.28k
    if (fp == nullptr)
368
588
        return nullptr;
369
370
    /* -------------------------------------------------------------------- */
371
    /*      Setup the file handle on this file.                             */
372
    /* -------------------------------------------------------------------- */
373
700
    auto poHandle = std::make_unique<VSISubFileHandle>();
374
375
700
    poHandle->fp = fp.release();
376
700
    poHandle->nSubregionOffset = nOff;
377
700
    poHandle->nSubregionSize = nSize;
378
379
    // In read-only mode validate (offset, size) against underlying file size
380
700
    if (strchr(pszAccess, 'r') != nullptr && strchr(pszAccess, '+') == nullptr)
381
700
    {
382
700
        if (VSIFSeekL(poHandle->fp, 0, SEEK_END) != 0)
383
0
        {
384
0
            return nullptr;
385
0
        }
386
700
        vsi_l_offset nFpSize = VSIFTellL(poHandle->fp);
387
        // For a directory, the size will be max(vsi_l_offset) / 2
388
700
        if (nFpSize == ~(static_cast<vsi_l_offset>(0)) / 2 || nOff > nFpSize)
389
33
        {
390
33
            return nullptr;
391
33
        }
392
667
        if (nOff + nSize > nFpSize)
393
131
        {
394
131
            nSize = nFpSize - nOff;
395
131
            poHandle->nSubregionSize = nSize;
396
131
        }
397
667
    }
398
399
667
    if (VSIFSeekL(poHandle->fp, nOff, SEEK_SET) != 0)
400
0
    {
401
0
        poHandle.reset();
402
0
    }
403
404
667
    return VSIVirtualHandleUniquePtr(poHandle.release());
405
700
}
406
407
/************************************************************************/
408
/*                                Stat()                                */
409
/************************************************************************/
410
411
int VSISubFileFilesystemHandler::Stat(const char *pszFilename,
412
                                      VSIStatBufL *psStatBuf, int nFlags)
413
414
1.62k
{
415
1.62k
    if (!STARTS_WITH_CI(pszFilename, "/vsisubfile/"))
416
109
        return -1;
417
418
1.51k
    CPLString osSubFilePath;
419
1.51k
    vsi_l_offset nOff = 0;
420
1.51k
    vsi_l_offset nSize = 0;
421
422
1.51k
    memset(psStatBuf, 0, sizeof(VSIStatBufL));
423
424
1.51k
    if (!DecomposePath(pszFilename, osSubFilePath, nOff, nSize))
425
91
    {
426
91
        errno = ENOENT;
427
91
        return -1;
428
91
    }
429
430
1.42k
    const int nResult = VSIStatExL(osSubFilePath, psStatBuf, nFlags);
431
432
1.42k
    if (nResult == 0)
433
693
    {
434
693
        if (nSize != 0)
435
133
            psStatBuf->st_size = nSize;
436
560
        else if (static_cast<vsi_l_offset>(psStatBuf->st_size) >= nOff)
437
543
            psStatBuf->st_size -= nOff;
438
17
        else
439
17
            psStatBuf->st_size = 0;
440
693
    }
441
442
1.42k
    return nResult;
443
1.51k
}
444
445
/************************************************************************/
446
/*                               Unlink()                               */
447
/************************************************************************/
448
449
int VSISubFileFilesystemHandler::Unlink(const char * /* pszFilename */)
450
0
{
451
0
    errno = EACCES;
452
0
    return -1;
453
0
}
454
455
/************************************************************************/
456
/*                               Mkdir()                                */
457
/************************************************************************/
458
459
int VSISubFileFilesystemHandler::Mkdir(const char * /* pszPathname */,
460
                                       long /* nMode */)
461
0
{
462
0
    errno = EACCES;
463
0
    return -1;
464
0
}
465
466
/************************************************************************/
467
/*                               Rmdir()                                */
468
/************************************************************************/
469
470
int VSISubFileFilesystemHandler::Rmdir(const char * /* pszPathname */)
471
472
0
{
473
0
    errno = EACCES;
474
0
    return -1;
475
0
}
476
477
/************************************************************************/
478
/*                             ReadDirEx()                              */
479
/************************************************************************/
480
481
char **VSISubFileFilesystemHandler::ReadDirEx(const char * /* pszPath */,
482
                                              int /* nMaxFiles */)
483
0
{
484
0
    errno = EACCES;
485
0
    return nullptr;
486
0
}
487
488
/************************************************************************/
489
/*                 VSIInstallSubFileFilesystemHandler()                 */
490
/************************************************************************/
491
492
/*!
493
 \brief Install /vsisubfile/ virtual file handler.
494
495
 \verbatim embed:rst
496
 See :ref:`/vsisubfile/ documentation <vsisubfile>`
497
 \endverbatim
498
 */
499
500
void VSIInstallSubFileHandler()
501
3
{
502
3
    VSIFileManager::InstallHandler(
503
3
        "/vsisubfile/", std::make_shared<VSISubFileFilesystemHandler>());
504
3
}