Coverage Report

Created: 2025-11-16 06:25

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
303
    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
303
{
94
303
    VSISubFileHandle::Close();
95
303
}
96
97
/************************************************************************/
98
/*                               Close()                                */
99
/************************************************************************/
100
101
int VSISubFileHandle::Close()
102
103
453
{
104
453
    if (fp == nullptr)
105
150
        return -1;
106
303
    int nRet = VSIFCloseL(fp);
107
303
    fp = nullptr;
108
109
303
    return nRet;
110
453
}
111
112
/************************************************************************/
113
/*                                Seek()                                */
114
/************************************************************************/
115
116
int VSISubFileHandle::Seek(vsi_l_offset nOffset, int nWhence)
117
118
565
{
119
565
    bAtEOF = false;
120
121
565
    if (nWhence == SEEK_SET)
122
401
    {
123
401
        if (nOffset >
124
401
            std::numeric_limits<vsi_l_offset>::max() - nSubregionOffset)
125
0
            return -1;
126
401
        nOffset += nSubregionOffset;
127
401
    }
128
164
    else if (nWhence == SEEK_CUR)
129
0
    {
130
        // handle normally.
131
0
    }
132
164
    else if (nWhence == SEEK_END)
133
164
    {
134
164
        if (nSubregionSize != 0)
135
0
        {
136
0
            nOffset = nSubregionOffset + nSubregionSize;
137
0
            nWhence = SEEK_SET;
138
0
        }
139
164
    }
140
0
    else
141
0
    {
142
0
        errno = EINVAL;
143
0
        return -1;
144
0
    }
145
146
565
    return VSIFSeekL(fp, nOffset, nWhence);
147
565
}
148
149
/************************************************************************/
150
/*                                Tell()                                */
151
/************************************************************************/
152
153
vsi_l_offset VSISubFileHandle::Tell()
154
155
164
{
156
164
    vsi_l_offset nBasePos = VSIFTellL(fp);
157
164
    if (nBasePos >= nSubregionOffset)
158
164
        return nBasePos - nSubregionOffset;
159
0
    return 0;
160
164
}
161
162
/************************************************************************/
163
/*                                Read()                                */
164
/************************************************************************/
165
166
size_t VSISubFileHandle::Read(void *pBuffer, size_t nSize, size_t nCount)
167
168
258
{
169
258
    size_t nRet = 0;
170
258
    if (nSubregionSize == 0)
171
258
    {
172
258
        nRet = VSIFReadL(pBuffer, nSize, nCount, fp);
173
258
    }
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
258
    if (nRet < nCount)
203
258
    {
204
258
        if (fp->Eof())
205
258
            bAtEOF = true;
206
0
        else /* if (fp->Error()) */
207
0
            bError = true;
208
258
    }
209
210
258
    return nRet;
211
258
}
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
136
{
274
136
    return bAtEOF;
275
136
}
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
1.95k
{
296
1.95k
    if (!STARTS_WITH(pszPath, "/vsisubfile/"))
297
0
        return FALSE;
298
299
1.95k
    osFilename = "";
300
1.95k
    nSubFileOffset = 0;
301
1.95k
    nSubFileSize = 0;
302
303
1.95k
    nSubFileOffset =
304
1.95k
        CPLScanUIntBig(pszPath + 12, static_cast<int>(strlen(pszPath + 12)));
305
128k
    for (int i = 12; pszPath[i] != '\0'; i++)
306
128k
    {
307
128k
        if (pszPath[i] == '_' && nSubFileSize == 0)
308
2.61k
        {
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
2.61k
            if (pszPath[i + 1] == '-')
314
63
                nSubFileSize = 0;
315
2.55k
            else
316
2.55k
                nSubFileSize = CPLScanUIntBig(
317
2.55k
                    pszPath + i + 1, static_cast<int>(strlen(pszPath + i + 1)));
318
2.61k
        }
319
125k
        else if (pszPath[i] == ',')
320
1.62k
        {
321
1.62k
            osFilename = pszPath + i + 1;
322
1.62k
            return TRUE;
323
1.62k
        }
324
123k
        else if (pszPath[i] == '/')
325
101
        {
326
            // Missing comma!
327
101
            return FALSE;
328
101
        }
329
128k
    }
330
331
226
    return FALSE;
332
1.95k
}
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.19k
{
344
1.19k
    if (!STARTS_WITH_CI(pszFilename, "/vsisubfile/"))
345
1
        return nullptr;
346
347
1.19k
    CPLString osSubFilePath;
348
1.19k
    vsi_l_offset nOff = 0;
349
1.19k
    vsi_l_offset nSize = 0;
350
351
1.19k
    if (!DecomposePath(pszFilename, osSubFilePath, nOff, nSize))
352
268
    {
353
268
        errno = ENOENT;
354
268
        return nullptr;
355
268
    }
356
928
    if (nOff > std::numeric_limits<vsi_l_offset>::max() - nSize)
357
5
    {
358
5
        return nullptr;
359
5
    }
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
923
    if (pszAccess[0] == 'w')
366
0
        pszAccess = "r+";
367
368
    /* -------------------------------------------------------------------- */
369
    /*      Open the underlying file.                                       */
370
    /* -------------------------------------------------------------------- */
371
923
    auto fp = VSIFilesystemHandler::OpenStatic(osSubFilePath, pszAccess,
372
923
                                               bSetError, papszOptions);
373
374
923
    if (fp == nullptr)
375
620
        return nullptr;
376
377
    /* -------------------------------------------------------------------- */
378
    /*      Setup the file handle on this file.                             */
379
    /* -------------------------------------------------------------------- */
380
303
    auto poHandle = std::make_unique<VSISubFileHandle>();
381
382
303
    poHandle->fp = fp.release();
383
303
    poHandle->nSubregionOffset = nOff;
384
303
    poHandle->nSubregionSize = nSize;
385
386
    // In read-only mode validate (offset, size) against underlying file size
387
303
    if (strchr(pszAccess, 'r') != nullptr && strchr(pszAccess, '+') == nullptr)
388
303
    {
389
303
        if (VSIFSeekL(poHandle->fp, 0, SEEK_END) != 0)
390
99
        {
391
99
            return nullptr;
392
99
        }
393
204
        vsi_l_offset nFpSize = VSIFTellL(poHandle->fp);
394
        // For a directory, the size will be max(vsi_l_offset) / 2
395
204
        if (nFpSize == ~(static_cast<vsi_l_offset>(0)) / 2 || nOff > nFpSize)
396
54
        {
397
54
            return nullptr;
398
54
        }
399
150
        if (nOff + nSize > nFpSize)
400
44
        {
401
44
            nSize = nFpSize - nOff;
402
44
            poHandle->nSubregionSize = nSize;
403
44
        }
404
150
    }
405
406
150
    if (VSIFSeekL(poHandle->fp, nOff, SEEK_SET) != 0)
407
0
    {
408
0
        poHandle.reset();
409
0
    }
410
411
150
    return VSIVirtualHandleUniquePtr(poHandle.release());
412
303
}
413
414
/************************************************************************/
415
/*                                Stat()                                */
416
/************************************************************************/
417
418
int VSISubFileFilesystemHandler::Stat(const char *pszFilename,
419
                                      VSIStatBufL *psStatBuf, int nFlags)
420
421
828
{
422
828
    if (!STARTS_WITH_CI(pszFilename, "/vsisubfile/"))
423
73
        return -1;
424
425
755
    CPLString osSubFilePath;
426
755
    vsi_l_offset nOff = 0;
427
755
    vsi_l_offset nSize = 0;
428
429
755
    memset(psStatBuf, 0, sizeof(VSIStatBufL));
430
431
755
    if (!DecomposePath(pszFilename, osSubFilePath, nOff, nSize))
432
59
    {
433
59
        errno = ENOENT;
434
59
        return -1;
435
59
    }
436
437
696
    const int nResult = VSIStatExL(osSubFilePath, psStatBuf, nFlags);
438
439
696
    if (nResult == 0)
440
223
    {
441
223
        if (nSize != 0)
442
48
            psStatBuf->st_size = nSize;
443
175
        else if (static_cast<vsi_l_offset>(psStatBuf->st_size) >= nOff)
444
139
            psStatBuf->st_size -= nOff;
445
36
        else
446
36
            psStatBuf->st_size = 0;
447
223
    }
448
449
696
    return nResult;
450
755
}
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("/vsisubfile/",
510
3
                                   new VSISubFileFilesystemHandler);
511
3
}