Coverage Report

Created: 2025-06-13 06:29

/src/gdal/port/cpl_vsil_tar.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  CPL - Common Portability Library
4
 * Purpose:  Implement VSI large file api for tar files (.tar).
5
 * Author:   Even Rouault, even.rouault at spatialys.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
//! @cond Doxygen_Suppress
14
15
#include "cpl_port.h"
16
#include "cpl_vsi.h"
17
18
#include <cstring>
19
20
#if HAVE_FCNTL_H
21
#include <fcntl.h>
22
#endif
23
24
#include <string>
25
#include <string_view>
26
#include <vector>
27
28
#include "cpl_conv.h"
29
#include "cpl_error.h"
30
#include "cpl_string.h"
31
#include "cpl_vsi_virtual.h"
32
33
#if (defined(DEBUG) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)) &&   \
34
    !defined(HAVE_FUZZER_FRIENDLY_ARCHIVE)
35
/* This is a completely custom archive format that is rather inefficient */
36
/* but supports random insertions or deletions, since it doesn't record */
37
/* explicit file size or rely on files starting on a particular boundary */
38
#define HAVE_FUZZER_FRIENDLY_ARCHIVE 1
39
#endif
40
41
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
42
constexpr int HALF_BUFFER_SIZE = 1024;
43
constexpr int BUFFER_SIZE = 2 * HALF_BUFFER_SIZE;
44
#endif
45
46
/************************************************************************/
47
/* ==================================================================== */
48
/*                       VSITarEntryFileOffset                          */
49
/* ==================================================================== */
50
/************************************************************************/
51
52
class VSITarEntryFileOffset final : public VSIArchiveEntryFileOffset
53
{
54
  public:
55
    GUIntBig m_nOffset = 0;
56
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
57
    GUIntBig m_nFileSize = 0;
58
    CPLString m_osFileName{};
59
#endif
60
61
0
    explicit VSITarEntryFileOffset(GUIntBig nOffset) : m_nOffset(nOffset)
62
0
    {
63
0
    }
64
65
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
66
    VSITarEntryFileOffset(GUIntBig nOffset, GUIntBig nFileSize,
67
                          const CPLString &osFileName)
68
0
        : m_nOffset(nOffset), m_nFileSize(nFileSize), m_osFileName(osFileName)
69
0
    {
70
0
    }
71
#endif
72
73
    ~VSITarEntryFileOffset() override;
74
};
75
76
0
VSITarEntryFileOffset::~VSITarEntryFileOffset() = default;
77
78
/************************************************************************/
79
/* ==================================================================== */
80
/*                             VSITarReader                             */
81
/* ==================================================================== */
82
/************************************************************************/
83
84
class VSITarReader final : public VSIArchiveReader
85
{
86
  private:
87
    CPL_DISALLOW_COPY_ASSIGN(VSITarReader)
88
89
    VSILFILE *fp = nullptr;
90
    GUIntBig nCurOffset = 0;
91
    GUIntBig nNextFileSize = 0;
92
    CPLString osNextFileName{};
93
    GIntBig nModifiedTime = 0;
94
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
95
    bool m_bIsFuzzerFriendly = false;
96
    GByte m_abyBuffer[BUFFER_SIZE + 1] = {};
97
    int m_abyBufferIdx = 0;
98
    int m_abyBufferSize = 0;
99
    GUIntBig m_nCurOffsetOld = 0;
100
#endif
101
102
  public:
103
    explicit VSITarReader(const char *pszTarFileName);
104
    ~VSITarReader() override;
105
106
    int IsValid()
107
634
    {
108
634
        return fp != nullptr;
109
634
    }
110
111
    int GotoFirstFile() override;
112
    int GotoNextFile() override;
113
    VSIArchiveEntryFileOffset *GetFileOffset() override;
114
115
    GUIntBig GetFileSize() override
116
0
    {
117
0
        return nNextFileSize;
118
0
    }
119
120
    CPLString GetFileName() override
121
0
    {
122
0
        return osNextFileName;
123
0
    }
124
125
    GIntBig GetModifiedTime() override
126
0
    {
127
0
        return nModifiedTime;
128
0
    }
129
130
    int GotoFileOffset(VSIArchiveEntryFileOffset *pOffset) override;
131
};
132
133
/************************************************************************/
134
/*                               VSIIsTGZ()                             */
135
/************************************************************************/
136
137
static bool VSIIsTGZ(const char *pszFilename)
138
634
{
139
634
    return (
140
634
        !STARTS_WITH_CI(pszFilename, "/vsigzip/") &&
141
634
        ((strlen(pszFilename) > 4 &&
142
526
          STARTS_WITH_CI(pszFilename + strlen(pszFilename) - 4, ".tgz")) ||
143
526
         (strlen(pszFilename) > 7 &&
144
526
          STARTS_WITH_CI(pszFilename + strlen(pszFilename) - 7, ".tar.gz"))));
145
634
}
146
147
/************************************************************************/
148
/*                           VSITarReader()                             */
149
/************************************************************************/
150
151
// TODO(schwehr): What is this ***NEWFILE*** thing?
152
// And make it a symbolic constant.
153
154
VSITarReader::VSITarReader(const char *pszTarFileName)
155
634
    : fp(VSIFOpenL(pszTarFileName, "rb"))
156
634
{
157
634
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
158
634
    if (fp != nullptr)
159
485
    {
160
485
        GByte abySignature[24] = {};
161
485
        m_bIsFuzzerFriendly =
162
485
            (VSIFReadL(abySignature, 1, 24, fp) == 24) &&
163
485
            (memcmp(abySignature, "FUZZER_FRIENDLY_ARCHIVE\n", 24) == 0 ||
164
0
             memcmp(abySignature, "***NEWFILE***:", strlen("***NEWFILE***:")) ==
165
0
                 0);
166
485
        CPL_IGNORE_RET_VAL(VSIFSeekL(fp, 0, SEEK_SET));
167
485
    }
168
634
#endif
169
634
}
170
171
/************************************************************************/
172
/*                          ~VSITarReader()                             */
173
/************************************************************************/
174
175
VSITarReader::~VSITarReader()
176
634
{
177
634
    if (fp)
178
485
        CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
179
634
}
180
181
/************************************************************************/
182
/*                          GetFileOffset()                             */
183
/************************************************************************/
184
185
VSIArchiveEntryFileOffset *VSITarReader::GetFileOffset()
186
0
{
187
0
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
188
0
    if (m_bIsFuzzerFriendly)
189
0
    {
190
0
        return new VSITarEntryFileOffset(nCurOffset, nNextFileSize,
191
0
                                         osNextFileName);
192
0
    }
193
0
#endif
194
0
    return new VSITarEntryFileOffset(nCurOffset);
195
0
}
196
197
/************************************************************************/
198
/*                       IsNumericFieldTerminator()                     */
199
/************************************************************************/
200
201
static bool IsNumericFieldTerminator(GByte byVal)
202
0
{
203
    // See https://github.com/Keruspe/tar-parser.rs/blob/master/tar.specs#L202
204
0
    return byVal == '\0' || byVal == ' ';
205
0
}
206
207
/************************************************************************/
208
/*                           GotoNextFile()                             */
209
/************************************************************************/
210
211
int VSITarReader::GotoNextFile()
212
485
{
213
485
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
214
485
    if (m_bIsFuzzerFriendly)
215
0
    {
216
0
        const int nNewFileMarkerSize =
217
0
            static_cast<int>(strlen("***NEWFILE***:"));
218
0
        while (true)
219
0
        {
220
0
            if (m_abyBufferIdx >= m_abyBufferSize)
221
0
            {
222
0
                if (m_abyBufferSize == 0)
223
0
                {
224
0
                    m_abyBufferSize = static_cast<int>(
225
0
                        VSIFReadL(m_abyBuffer, 1, BUFFER_SIZE, fp));
226
0
                    if (m_abyBufferSize == 0)
227
0
                        return FALSE;
228
0
                    m_abyBuffer[m_abyBufferSize] = '\0';
229
0
                }
230
0
                else
231
0
                {
232
0
                    if (m_abyBufferSize < BUFFER_SIZE)
233
0
                    {
234
0
                        if (nCurOffset > 0 && nCurOffset != m_nCurOffsetOld)
235
0
                        {
236
0
                            nNextFileSize = VSIFTellL(fp);
237
0
                            if (nNextFileSize >= nCurOffset)
238
0
                            {
239
0
                                nNextFileSize -= nCurOffset;
240
0
                                m_nCurOffsetOld = nCurOffset;
241
0
                                return TRUE;
242
0
                            }
243
0
                        }
244
0
                        return FALSE;
245
0
                    }
246
0
                    memcpy(m_abyBuffer, m_abyBuffer + HALF_BUFFER_SIZE,
247
0
                           HALF_BUFFER_SIZE);
248
0
                    m_abyBufferSize = static_cast<int>(
249
0
                        VSIFReadL(m_abyBuffer + HALF_BUFFER_SIZE, 1,
250
0
                                  HALF_BUFFER_SIZE, fp));
251
0
                    if (m_abyBufferSize == 0)
252
0
                        return FALSE;
253
0
                    m_abyBufferIdx = 0;
254
0
                    m_abyBufferSize += HALF_BUFFER_SIZE;
255
0
                    m_abyBuffer[m_abyBufferSize] = '\0';
256
0
                }
257
0
            }
258
259
0
            std::string_view abyBuffer(reinterpret_cast<char *>(m_abyBuffer),
260
0
                                       m_abyBufferSize);
261
0
            const auto posNewFile = abyBuffer.find(
262
0
                std::string_view("***NEWFILE***:", nNewFileMarkerSize),
263
0
                m_abyBufferIdx);
264
0
            if (posNewFile == std::string::npos)
265
0
            {
266
0
                m_abyBufferIdx = m_abyBufferSize;
267
0
            }
268
0
            else
269
0
            {
270
0
                m_abyBufferIdx = static_cast<int>(posNewFile);
271
                // 2: space for at least one-char filename and '\n'
272
0
                if (m_abyBufferIdx < m_abyBufferSize - (nNewFileMarkerSize + 2))
273
0
                {
274
0
                    if (nCurOffset > 0 && nCurOffset != m_nCurOffsetOld)
275
0
                    {
276
0
                        nNextFileSize = VSIFTellL(fp);
277
0
                        nNextFileSize -= m_abyBufferSize;
278
0
                        nNextFileSize += m_abyBufferIdx;
279
0
                        if (nNextFileSize >= nCurOffset)
280
0
                        {
281
0
                            nNextFileSize -= nCurOffset;
282
0
                            m_nCurOffsetOld = nCurOffset;
283
0
                            return TRUE;
284
0
                        }
285
0
                    }
286
0
                    m_abyBufferIdx += nNewFileMarkerSize;
287
0
                    const int nFilenameStartIdx = m_abyBufferIdx;
288
0
                    for (; m_abyBufferIdx < m_abyBufferSize &&
289
0
                           m_abyBuffer[m_abyBufferIdx] != '\n';
290
0
                         ++m_abyBufferIdx)
291
0
                    {
292
                        // Do nothing.
293
0
                    }
294
0
                    if (m_abyBufferIdx < m_abyBufferSize)
295
0
                    {
296
0
                        osNextFileName.assign(
297
0
                            reinterpret_cast<const char *>(m_abyBuffer +
298
0
                                                           nFilenameStartIdx),
299
0
                            m_abyBufferIdx - nFilenameStartIdx);
300
0
                        nCurOffset = VSIFTellL(fp);
301
0
                        nCurOffset -= m_abyBufferSize;
302
0
                        nCurOffset += m_abyBufferIdx + 1;
303
0
                    }
304
0
                }
305
0
                else
306
0
                {
307
0
                    m_abyBufferIdx = m_abyBufferSize;
308
0
                }
309
0
            }
310
0
        }
311
0
    }
312
485
#endif
313
314
485
    osNextFileName.clear();
315
485
    while (true)
316
485
    {
317
485
        GByte abyHeader[512] = {};
318
485
        if (VSIFReadL(abyHeader, 512, 1, fp) != 1)
319
485
            return FALSE;
320
321
0
        if (!(abyHeader[100] == 0x80 ||
322
0
              IsNumericFieldTerminator(
323
0
                  abyHeader[107])) || /* start/end of filemode */
324
0
            !(abyHeader[108] == 0x80 ||
325
0
              IsNumericFieldTerminator(
326
0
                  abyHeader[115])) || /* start/end of owner ID */
327
0
            !(abyHeader[116] == 0x80 ||
328
0
              IsNumericFieldTerminator(
329
0
                  abyHeader[123])) || /* start/end of group ID */
330
0
            !IsNumericFieldTerminator(abyHeader[135]) || /* end of file size */
331
0
            !IsNumericFieldTerminator(abyHeader[147]))   /* end of mtime */
332
0
        {
333
0
            return FALSE;
334
0
        }
335
0
        if (!(abyHeader[124] == ' ' ||
336
0
              (abyHeader[124] >= '0' && abyHeader[124] <= '7')))
337
0
            return FALSE;
338
339
0
        if (osNextFileName.empty())
340
0
        {
341
0
            osNextFileName.assign(
342
0
                reinterpret_cast<const char *>(abyHeader),
343
0
                CPLStrnlen(reinterpret_cast<const char *>(abyHeader), 100));
344
0
        }
345
346
0
        nNextFileSize = 0;
347
0
        for (int i = 0; i < 11; i++)
348
0
        {
349
0
            if (abyHeader[124 + i] != ' ')
350
0
            {
351
0
                if (nNextFileSize > static_cast<GUIntBig>(GINTBIG_MAX / 8) ||
352
0
                    abyHeader[124 + i] < '0' || abyHeader[124 + i] >= '8')
353
0
                {
354
0
                    CPLError(CE_Failure, CPLE_AppDefined,
355
0
                             "Invalid file size for %s",
356
0
                             osNextFileName.c_str());
357
0
                    return FALSE;
358
0
                }
359
0
                nNextFileSize = nNextFileSize * 8 + (abyHeader[124 + i] - '0');
360
0
            }
361
0
        }
362
0
        if (nNextFileSize > GINTBIG_MAX)
363
0
        {
364
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid file size for %s",
365
0
                     osNextFileName.c_str());
366
0
            return FALSE;
367
0
        }
368
369
0
        nModifiedTime = 0;
370
0
        for (int i = 0; i < 11; i++)
371
0
        {
372
0
            if (abyHeader[136 + i] != ' ')
373
0
            {
374
0
                if (nModifiedTime > GINTBIG_MAX / 8 ||
375
0
                    abyHeader[136 + i] < '0' || abyHeader[136 + i] >= '8' ||
376
0
                    nModifiedTime * 8 >
377
0
                        GINTBIG_MAX - (abyHeader[136 + i] - '0'))
378
0
                {
379
0
                    CPLError(CE_Failure, CPLE_AppDefined,
380
0
                             "Invalid mtime for %s", osNextFileName.c_str());
381
0
                    return FALSE;
382
0
                }
383
0
                nModifiedTime = nModifiedTime * 8 + (abyHeader[136 + i] - '0');
384
0
            }
385
0
        }
386
387
0
        if (abyHeader[156] == 'L' && nNextFileSize > 0 && nNextFileSize < 32768)
388
0
        {
389
            // If this is a large filename record, then read the filename
390
0
            osNextFileName.clear();
391
0
            osNextFileName.resize(
392
0
                static_cast<size_t>(((nNextFileSize + 511) / 512) * 512));
393
0
            if (VSIFReadL(&osNextFileName[0], osNextFileName.size(), 1, fp) !=
394
0
                1)
395
0
                return FALSE;
396
0
            osNextFileName.resize(static_cast<size_t>(nNextFileSize));
397
0
            if (osNextFileName.back() == '\0')
398
0
                osNextFileName.pop_back();
399
0
        }
400
0
        else
401
0
        {
402
            // Is it a ustar extension ?
403
            // Cf https://en.wikipedia.org/wiki/Tar_(computing)#UStar_format
404
0
            if (memcmp(abyHeader + 257, "ustar\0", 6) == 0 &&
405
0
                abyHeader[345] != '\0')
406
0
            {
407
0
                std::string osFilenamePrefix;
408
0
                osFilenamePrefix.assign(
409
0
                    reinterpret_cast<const char *>(abyHeader + 345),
410
0
                    CPLStrnlen(reinterpret_cast<const char *>(abyHeader + 345),
411
0
                               155));
412
0
                osNextFileName = osFilenamePrefix + '/' + osNextFileName;
413
0
            }
414
415
0
            break;
416
0
        }
417
0
    }
418
419
0
    nCurOffset = VSIFTellL(fp);
420
421
0
    const GUIntBig nBytesToSkip = ((nNextFileSize + 511) / 512) * 512;
422
0
    if (nBytesToSkip > (~(static_cast<GUIntBig>(0))) - nCurOffset)
423
0
    {
424
0
        CPLError(CE_Failure, CPLE_AppDefined, "Bad .tar structure");
425
0
        return FALSE;
426
0
    }
427
428
0
    if (VSIFSeekL(fp, nBytesToSkip, SEEK_CUR) < 0)
429
0
        return FALSE;
430
431
0
    return TRUE;
432
0
}
433
434
/************************************************************************/
435
/*                          GotoFirstFile()                             */
436
/************************************************************************/
437
438
int VSITarReader::GotoFirstFile()
439
485
{
440
485
    if (VSIFSeekL(fp, 0, SEEK_SET) < 0)
441
0
        return FALSE;
442
485
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
443
485
    m_abyBufferIdx = 0;
444
485
    m_abyBufferSize = 0;
445
485
    nCurOffset = 0;
446
485
    m_nCurOffsetOld = 0;
447
485
    osNextFileName = "";
448
485
    nNextFileSize = 0;
449
485
#endif
450
485
    return GotoNextFile();
451
485
}
452
453
/************************************************************************/
454
/*                         GotoFileOffset()                             */
455
/************************************************************************/
456
457
int VSITarReader::GotoFileOffset(VSIArchiveEntryFileOffset *pOffset)
458
0
{
459
0
    VSITarEntryFileOffset *pTarEntryOffset =
460
0
        static_cast<VSITarEntryFileOffset *>(pOffset);
461
0
#ifdef HAVE_FUZZER_FRIENDLY_ARCHIVE
462
0
    if (m_bIsFuzzerFriendly)
463
0
    {
464
0
        if (VSIFSeekL(fp,
465
0
                      pTarEntryOffset->m_nOffset + pTarEntryOffset->m_nFileSize,
466
0
                      SEEK_SET) < 0)
467
0
            return FALSE;
468
0
        m_abyBufferIdx = 0;
469
0
        m_abyBufferSize = 0;
470
0
        nCurOffset = pTarEntryOffset->m_nOffset;
471
0
        m_nCurOffsetOld = pTarEntryOffset->m_nOffset;
472
0
        osNextFileName = pTarEntryOffset->m_osFileName;
473
0
        nNextFileSize = pTarEntryOffset->m_nFileSize;
474
0
        return TRUE;
475
0
    }
476
0
#endif
477
0
    if (pTarEntryOffset->m_nOffset < 512 ||
478
0
        VSIFSeekL(fp, pTarEntryOffset->m_nOffset - 512, SEEK_SET) < 0)
479
0
        return FALSE;
480
0
    return GotoNextFile();
481
0
}
482
483
/************************************************************************/
484
/* ==================================================================== */
485
/*                        VSITarFilesystemHandler                      */
486
/* ==================================================================== */
487
/************************************************************************/
488
489
class VSITarFilesystemHandler final : public VSIArchiveFilesystemHandler
490
{
491
  public:
492
    const char *GetPrefix() override
493
21.4k
    {
494
21.4k
        return "/vsitar";
495
21.4k
    }
496
497
    std::vector<CPLString> GetExtensions() override;
498
    VSIArchiveReader *CreateReader(const char *pszTarFileName) override;
499
500
    VSIVirtualHandle *Open(const char *pszFilename, const char *pszAccess,
501
                           bool bSetError,
502
                           CSLConstList /* papszOptions */) override;
503
};
504
505
/************************************************************************/
506
/*                          GetExtensions()                             */
507
/************************************************************************/
508
509
std::vector<CPLString> VSITarFilesystemHandler::GetExtensions()
510
4.06k
{
511
4.06k
    std::vector<CPLString> oList;
512
4.06k
    oList.push_back(".tar.gz");
513
4.06k
    oList.push_back(".tar");
514
4.06k
    oList.push_back(".tgz");
515
4.06k
    return oList;
516
4.06k
}
517
518
/************************************************************************/
519
/*                           CreateReader()                             */
520
/************************************************************************/
521
522
VSIArchiveReader *
523
VSITarFilesystemHandler::CreateReader(const char *pszTarFileName)
524
634
{
525
634
    CPLString osTarInFileName;
526
527
634
    if (VSIIsTGZ(pszTarFileName))
528
9
    {
529
9
        osTarInFileName = "/vsigzip/";
530
9
        osTarInFileName += pszTarFileName;
531
9
    }
532
625
    else
533
625
        osTarInFileName = pszTarFileName;
534
535
634
    VSITarReader *poReader = new VSITarReader(osTarInFileName);
536
537
634
    if (!poReader->IsValid())
538
149
    {
539
149
        delete poReader;
540
149
        return nullptr;
541
149
    }
542
543
485
    if (!poReader->GotoFirstFile())
544
485
    {
545
485
        delete poReader;
546
485
        return nullptr;
547
485
    }
548
549
0
    return poReader;
550
485
}
551
552
/************************************************************************/
553
/*                                 Open()                               */
554
/************************************************************************/
555
556
VSIVirtualHandle *VSITarFilesystemHandler::Open(const char *pszFilename,
557
                                                const char *pszAccess,
558
                                                bool /* bSetError */,
559
                                                CSLConstList /* papszOptions */)
560
1.30k
{
561
562
1.30k
    if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
563
0
    {
564
0
        CPLError(CE_Failure, CPLE_AppDefined,
565
0
                 "Only read-only mode is supported for /vsitar");
566
0
        return nullptr;
567
0
    }
568
569
1.30k
    CPLString osTarInFileName;
570
1.30k
    char *tarFilename = SplitFilename(pszFilename, osTarInFileName, TRUE);
571
1.30k
    if (tarFilename == nullptr)
572
1.09k
        return nullptr;
573
574
207
    VSIArchiveReader *poReader = OpenArchiveFile(tarFilename, osTarInFileName);
575
207
    if (poReader == nullptr)
576
207
    {
577
207
        CPLFree(tarFilename);
578
207
        return nullptr;
579
207
    }
580
581
0
    CPLString osSubFileName("/vsisubfile/");
582
0
    VSITarEntryFileOffset *pOffset =
583
0
        reinterpret_cast<VSITarEntryFileOffset *>(poReader->GetFileOffset());
584
0
    osSubFileName += CPLString().Printf(CPL_FRMT_GUIB, pOffset->m_nOffset);
585
0
    osSubFileName += "_";
586
0
    osSubFileName += CPLString().Printf(CPL_FRMT_GUIB, poReader->GetFileSize());
587
0
    osSubFileName += ",";
588
0
    delete pOffset;
589
590
0
    if (VSIIsTGZ(tarFilename))
591
0
    {
592
0
        osSubFileName += "/vsigzip/";
593
0
        osSubFileName += tarFilename;
594
0
    }
595
0
    else
596
0
        osSubFileName += tarFilename;
597
598
0
    delete (poReader);
599
600
0
    CPLFree(tarFilename);
601
0
    tarFilename = nullptr;
602
603
0
    return reinterpret_cast<VSIVirtualHandle *>(VSIFOpenL(osSubFileName, "rb"));
604
207
}
605
606
//! @endcond
607
608
/************************************************************************/
609
/*                    VSIInstallTarFileHandler()                        */
610
/************************************************************************/
611
612
/*!
613
 \brief Install /vsitar/ file system handler.
614
615
 A special file handler is installed that allows reading on-the-fly in TAR
616
 (regular .tar, or compressed .tar.gz/.tgz) archives.
617
618
 All portions of the file system underneath the base path "/vsitar/" will be
619
 handled by this driver.
620
621
 \verbatim embed:rst
622
 See :ref:`/vsitar/ documentation <vsitar>`
623
 \endverbatim
624
625
 @since GDAL 1.8.0
626
 */
627
628
void VSIInstallTarFileHandler(void)
629
1
{
630
1
    VSIFileManager::InstallHandler("/vsitar/", new VSITarFilesystemHandler());
631
1
}