Coverage Report

Created: 2026-07-14 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/port/cpl_vsil_stdin.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * Project:  CPL - Common Portability Library
4
 * Purpose:  Implement VSI large file api for stdin
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 **********************************************************************
8
 * Copyright (c) 2010-2012, 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 <cstddef>
19
#include <cstdio>
20
#include <cstdlib>
21
#include <cstring>
22
#include <fcntl.h>
23
24
#include <algorithm>
25
#include <limits>
26
27
#include "cpl_conv.h"
28
#include "cpl_error.h"
29
#include "cpl_vsi_virtual.h"
30
31
#ifdef _WIN32
32
#include <io.h>
33
#endif
34
35
static std::string gosStdinFilename{};
36
static FILE *gStdinFile = stdin;
37
static GByte *gpabyBuffer = nullptr;
38
static size_t gnBufferLimit = 0;  // maximum that can be allocated
39
static size_t gnBufferAlloc = 0;  // current allocation
40
static size_t gnBufferLen = 0;    // number of valid bytes in gpabyBuffer
41
static uint64_t gnRealPos = 0;    // current offset on stdin
42
static bool gbHasSoughtToEnd = false;
43
static bool gbHasErrored = false;
44
static uint64_t gnFileSize = 0;
45
46
/************************************************************************/
47
/*                            VSIStdinInit()                            */
48
/************************************************************************/
49
50
static void VSIStdinInit()
51
0
{
52
0
    if (gpabyBuffer == nullptr)
53
0
    {
54
#ifdef _WIN32
55
        setmode(fileno(stdin), O_BINARY);
56
#endif
57
0
        constexpr size_t MAX_INITIAL_ALLOC = 1024 * 1024;
58
0
        gnBufferAlloc = std::min(gnBufferAlloc, MAX_INITIAL_ALLOC);
59
0
        gpabyBuffer = static_cast<GByte *>(CPLMalloc(gnBufferAlloc));
60
0
    }
61
0
}
62
63
/************************************************************************/
64
/* ==================================================================== */
65
/*                       VSIStdinFilesystemHandler                     */
66
/* ==================================================================== */
67
/************************************************************************/
68
69
class VSIStdinFilesystemHandler final : public VSIFilesystemHandler
70
{
71
    CPL_DISALLOW_COPY_ASSIGN(VSIStdinFilesystemHandler)
72
73
  public:
74
    VSIStdinFilesystemHandler();
75
    ~VSIStdinFilesystemHandler() override;
76
77
    VSIVirtualHandleUniquePtr Open(const char *pszFilename,
78
                                   const char *pszAccess, bool bSetError,
79
                                   CSLConstList /* papszOptions */) override;
80
    int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
81
             int nFlags) override;
82
83
    bool SupportsSequentialWrite(const char * /* pszPath */,
84
                                 bool /* bAllowLocalTempFile */) override
85
0
    {
86
0
        return false;
87
0
    }
88
89
    bool SupportsRandomWrite(const char * /* pszPath */,
90
                             bool /* bAllowLocalTempFile */) override
91
0
    {
92
0
        return false;
93
0
    }
94
};
95
96
/************************************************************************/
97
/* ==================================================================== */
98
/*                        VSIStdinHandle                               */
99
/* ==================================================================== */
100
/************************************************************************/
101
102
class VSIStdinHandle final : public VSIVirtualHandle
103
{
104
  private:
105
    CPL_DISALLOW_COPY_ASSIGN(VSIStdinHandle)
106
107
    bool m_bEOF = false;
108
    bool m_bError = false;
109
    uint64_t m_nCurOff = 0;
110
    size_t ReadAndCache(void *pBuffer, size_t nToRead);
111
112
  public:
113
0
    VSIStdinHandle() = default;
114
115
    ~VSIStdinHandle() override
116
0
    {
117
0
        VSIStdinHandle::Close();
118
0
    }
119
120
    int Seek(vsi_l_offset nOffset, int nWhence) override;
121
    vsi_l_offset Tell() override;
122
    size_t Read(void *pBuffer, size_t nBytes) override;
123
    size_t Write(const void *pBuffer, size_t nBytes) override;
124
    void ClearErr() override;
125
    int Error() override;
126
    int Eof() override;
127
    int Close() override;
128
};
129
130
/************************************************************************/
131
/*                            ReadAndCache()                            */
132
/************************************************************************/
133
134
size_t VSIStdinHandle::ReadAndCache(void *pUserBuffer, size_t nToRead)
135
0
{
136
0
    CPLAssert(m_nCurOff == gnRealPos);
137
138
0
    const size_t nRead = fread(pUserBuffer, 1, nToRead, gStdinFile);
139
140
0
    if (gnRealPos < gnBufferLimit)
141
0
    {
142
0
        bool bCopyInBuffer = true;
143
0
        const size_t nToCopy = static_cast<size_t>(
144
0
            std::min(gnBufferLimit - gnRealPos, static_cast<uint64_t>(nRead)));
145
0
        if (gnRealPos + nToCopy > gnBufferAlloc)
146
0
        {
147
0
            auto newAlloc = gnRealPos + nToCopy;
148
0
            if (newAlloc < gnBufferLimit - newAlloc / 3)
149
0
                newAlloc += newAlloc / 3;
150
0
            else
151
0
                newAlloc = gnBufferLimit;
152
0
            GByte *newBuffer = static_cast<GByte *>(VSI_REALLOC_VERBOSE(
153
0
                gpabyBuffer, static_cast<size_t>(newAlloc)));
154
0
            if (newBuffer == nullptr)
155
0
            {
156
0
                bCopyInBuffer = false;
157
0
            }
158
0
            else
159
0
            {
160
0
                gpabyBuffer = newBuffer;
161
0
                gnBufferAlloc = static_cast<size_t>(newAlloc);
162
0
            }
163
0
        }
164
0
        if (bCopyInBuffer)
165
0
        {
166
0
            memcpy(gpabyBuffer + static_cast<size_t>(gnRealPos), pUserBuffer,
167
0
                   nToCopy);
168
0
            gnBufferLen += nToCopy;
169
0
        }
170
0
    }
171
172
0
    m_nCurOff += nRead;
173
0
    gnRealPos = m_nCurOff;
174
175
0
    if (nRead < nToRead)
176
0
    {
177
0
        gbHasSoughtToEnd = CPL_TO_BOOL(feof(gStdinFile));
178
0
        if (gbHasSoughtToEnd)
179
0
            gnFileSize = gnRealPos;
180
0
        gbHasErrored = CPL_TO_BOOL(ferror(gStdinFile));
181
0
    }
182
183
0
    return nRead;
184
0
}
185
186
/************************************************************************/
187
/*                                Seek()                                */
188
/************************************************************************/
189
190
int VSIStdinHandle::Seek(vsi_l_offset nOffset, int nWhence)
191
192
0
{
193
0
    m_bEOF = false;
194
195
0
    if (nWhence == SEEK_SET && nOffset == m_nCurOff)
196
0
        return 0;
197
198
0
    VSIStdinInit();
199
200
0
    if (nWhence == SEEK_END)
201
0
    {
202
0
        if (nOffset != 0)
203
0
        {
204
0
            CPLError(CE_Failure, CPLE_NotSupported,
205
0
                     "Seek(xx != 0, SEEK_END) unsupported on /vsistdin");
206
0
            return -1;
207
0
        }
208
209
0
        if (gbHasSoughtToEnd)
210
0
        {
211
0
            m_nCurOff = gnFileSize;
212
0
            return 0;
213
0
        }
214
215
0
        nOffset = static_cast<vsi_l_offset>(-1);
216
0
    }
217
0
    else if (nWhence == SEEK_CUR)
218
0
    {
219
0
        nOffset += m_nCurOff;
220
0
    }
221
222
0
    if (nWhence != SEEK_END && gnRealPos >= gnBufferLimit &&
223
0
        nOffset >= gnBufferLimit)
224
0
    {
225
0
        CPLError(CE_Failure, CPLE_NotSupported,
226
0
                 "Backward Seek() unsupported on /vsistdin beyond "
227
0
                 "maximum buffer limit (" CPL_FRMT_GUIB " bytes).\n"
228
0
                 "This limit can be extended by setting the "
229
0
                 "CPL_VSISTDIN_BUFFER_LIMIT "
230
0
                 "configuration option to a number of bytes, or by using the "
231
0
                 "'/vsistdin?buffer_limit=number_of_bytes' filename.\n"
232
0
                 "A limit of -1 means unlimited.",
233
0
                 static_cast<GUIntBig>(gnBufferLimit));
234
0
        return -1;
235
0
    }
236
237
0
    if (nOffset < gnBufferLen)
238
0
    {
239
0
        m_nCurOff = nOffset;
240
0
        return 0;
241
0
    }
242
243
0
    if (nOffset == m_nCurOff)
244
0
        return 0;
245
246
0
    CPLDebug("VSI", "Forward seek from " CPL_FRMT_GUIB " to " CPL_FRMT_GUIB,
247
0
             static_cast<GUIntBig>(m_nCurOff), nOffset);
248
249
0
    char abyTemp[8192] = {};
250
0
    m_nCurOff = gnRealPos;
251
0
    while (true)
252
0
    {
253
0
        const size_t nToRead = static_cast<size_t>(
254
0
            std::min(static_cast<uint64_t>(sizeof(abyTemp)),
255
0
                     static_cast<uint64_t>(nOffset - m_nCurOff)));
256
0
        const size_t nRead = ReadAndCache(abyTemp, nToRead);
257
258
0
        if (nRead < nToRead)
259
0
        {
260
0
            return nWhence == SEEK_END ? 0 : -1;
261
0
        }
262
0
        if (nToRead < sizeof(abyTemp))
263
0
            break;
264
0
    }
265
266
0
    return 0;
267
0
}
268
269
/************************************************************************/
270
/*                                Tell()                                */
271
/************************************************************************/
272
273
vsi_l_offset VSIStdinHandle::Tell()
274
0
{
275
0
    return m_nCurOff;
276
0
}
277
278
/************************************************************************/
279
/*                                Read()                                */
280
/************************************************************************/
281
282
size_t VSIStdinHandle::Read(void *pBuffer, size_t nBytesToRead)
283
284
0
{
285
0
    VSIStdinInit();
286
287
0
    if (nBytesToRead == 0)
288
0
        return 0;
289
290
0
    if (m_nCurOff < gnRealPos && gnRealPos >= gnBufferLimit &&
291
0
        m_nCurOff + nBytesToRead > gnBufferLimit)
292
0
    {
293
0
        CPLError(CE_Failure, CPLE_NotSupported,
294
0
                 "Backward Seek() unsupported on /vsistdin beyond "
295
0
                 "maximum buffer limit (" CPL_FRMT_GUIB " bytes).\n"
296
0
                 "This limit can be extended by setting the "
297
0
                 "CPL_VSISTDIN_BUFFER_LIMIT "
298
0
                 "configuration option to a number of bytes, or by using the "
299
0
                 "'/vsistdin?buffer_limit=number_of_bytes' filename.\n"
300
0
                 "A limit of -1 means unlimited.",
301
0
                 static_cast<GUIntBig>(gnBufferLimit));
302
0
        return 0;
303
0
    }
304
305
0
    if (m_nCurOff < gnBufferLen)
306
0
    {
307
0
        const size_t nAlreadyCached =
308
0
            static_cast<size_t>(gnBufferLen - m_nCurOff);
309
0
        if (nBytesToRead <= nAlreadyCached)
310
0
        {
311
0
            memcpy(pBuffer, gpabyBuffer + static_cast<size_t>(m_nCurOff),
312
0
                   nBytesToRead);
313
0
            m_nCurOff += nBytesToRead;
314
0
            return nBytesToRead;
315
0
        }
316
317
0
        memcpy(pBuffer, gpabyBuffer + static_cast<size_t>(m_nCurOff),
318
0
               nAlreadyCached);
319
0
        m_nCurOff += nAlreadyCached;
320
321
0
        const size_t nRead =
322
0
            ReadAndCache(static_cast<GByte *>(pBuffer) + nAlreadyCached,
323
0
                         nBytesToRead - nAlreadyCached);
324
0
        m_bEOF = gbHasSoughtToEnd;
325
0
        m_bError = gbHasErrored;
326
327
0
        return nRead + nAlreadyCached;
328
0
    }
329
330
0
    const size_t nRead = ReadAndCache(pBuffer, nBytesToRead);
331
0
    m_bEOF = gbHasSoughtToEnd;
332
0
    m_bError = gbHasErrored;
333
0
    return nRead;
334
0
}
335
336
/************************************************************************/
337
/*                               Write()                                */
338
/************************************************************************/
339
340
size_t VSIStdinHandle::Write(const void * /* pBuffer */, size_t /* nBytes */)
341
0
{
342
0
    CPLError(CE_Failure, CPLE_NotSupported, "Write() unsupported on /vsistdin");
343
0
    return 0;
344
0
}
345
346
/************************************************************************/
347
/*                              ClearErr()                              */
348
/************************************************************************/
349
350
void VSIStdinHandle::ClearErr()
351
352
0
{
353
0
    clearerr(gStdinFile);
354
0
    m_bEOF = false;
355
0
    m_bError = false;
356
0
}
357
358
/************************************************************************/
359
/*                               Error()                                */
360
/************************************************************************/
361
362
int VSIStdinHandle::Error()
363
364
0
{
365
0
    return m_bError;
366
0
}
367
368
/************************************************************************/
369
/*                                Eof()                                 */
370
/************************************************************************/
371
372
int VSIStdinHandle::Eof()
373
374
0
{
375
0
    return m_bEOF;
376
0
}
377
378
/************************************************************************/
379
/*                               Close()                                */
380
/************************************************************************/
381
382
int VSIStdinHandle::Close()
383
384
0
{
385
0
    if (!gosStdinFilename.empty() &&
386
0
        CPLTestBool(CPLGetConfigOption("CPL_VSISTDIN_FILE_CLOSE", "NO")))
387
0
    {
388
0
        if (gStdinFile != stdin)
389
0
            fclose(gStdinFile);
390
0
        gStdinFile = stdin;
391
0
        gosStdinFilename.clear();
392
0
        gnRealPos = ftell(stdin);
393
0
        gnBufferLen = 0;
394
0
        gbHasSoughtToEnd = false;
395
0
        gbHasErrored = false;
396
0
        gnFileSize = 0;
397
0
    }
398
0
    return 0;
399
0
}
400
401
/************************************************************************/
402
/* ==================================================================== */
403
/*                       VSIStdinFilesystemHandler                     */
404
/* ==================================================================== */
405
/************************************************************************/
406
407
/************************************************************************/
408
/*                     VSIStdinFilesystemHandler()                      */
409
/************************************************************************/
410
411
VSIStdinFilesystemHandler::VSIStdinFilesystemHandler()
412
2
{
413
2
}
414
415
/************************************************************************/
416
/*                     ~VSIStdinFilesystemHandler()                     */
417
/************************************************************************/
418
419
VSIStdinFilesystemHandler::~VSIStdinFilesystemHandler()
420
0
{
421
0
    if (gStdinFile != stdin)
422
0
        fclose(gStdinFile);
423
0
    gStdinFile = stdin;
424
0
    CPLFree(gpabyBuffer);
425
0
    gpabyBuffer = nullptr;
426
0
    gnBufferLimit = 0;
427
0
    gnBufferAlloc = 0;
428
0
    gnBufferLen = 0;
429
0
    gnRealPos = 0;
430
0
    gosStdinFilename.clear();
431
0
}
432
433
/************************************************************************/
434
/*                           GetBufferLimit()                           */
435
/************************************************************************/
436
437
static size_t GetBufferLimit(const char *pszBufferLimit)
438
0
{
439
0
    uint64_t nVal =
440
0
        static_cast<uint64_t>(std::strtoull(pszBufferLimit, nullptr, 10));
441
442
    // -1 because on 64-bit builds with size_t==uint64_t, a static analyzer
443
    // could complain that the ending nVal > MAX_BUFFER_LIMIT test is always
444
    // false
445
0
    constexpr size_t MAX_BUFFER_LIMIT = std::numeric_limits<size_t>::max() - 1;
446
0
    if (strstr(pszBufferLimit, "MB") != nullptr)
447
0
    {
448
0
        constexpr size_t ONE_MB = 1024 * 1024;
449
0
        if (nVal > MAX_BUFFER_LIMIT / ONE_MB)
450
0
        {
451
0
            nVal = MAX_BUFFER_LIMIT;
452
0
        }
453
0
        else
454
0
        {
455
0
            nVal *= ONE_MB;
456
0
        }
457
0
    }
458
0
    else if (strstr(pszBufferLimit, "GB") != nullptr)
459
0
    {
460
0
        constexpr size_t ONE_GB = 1024 * 1024 * 1024;
461
0
        if (nVal > MAX_BUFFER_LIMIT / ONE_GB)
462
0
        {
463
0
            nVal = MAX_BUFFER_LIMIT;
464
0
        }
465
0
        else
466
0
        {
467
0
            nVal *= ONE_GB;
468
0
        }
469
0
    }
470
0
    if (nVal > MAX_BUFFER_LIMIT)
471
0
    {
472
0
        nVal = MAX_BUFFER_LIMIT;
473
0
    }
474
0
    return static_cast<size_t>(nVal);
475
0
}
476
477
/************************************************************************/
478
/*                           ParseFilename()                            */
479
/************************************************************************/
480
481
static bool ParseFilename(const char *pszFilename)
482
0
{
483
0
    if (!(EQUAL(pszFilename, "/vsistdin/") ||
484
0
          ((STARTS_WITH(pszFilename, "/vsistdin/?") ||
485
0
            STARTS_WITH(pszFilename, "/vsistdin?")) &&
486
0
           strchr(pszFilename, '.') == nullptr)))
487
0
    {
488
0
        return false;
489
0
    }
490
491
0
    if (!CPLTestBool(CPLGetConfigOption("CPL_ALLOW_VSISTDIN", "YES")))
492
0
    {
493
0
        CPLError(CE_Failure, CPLE_NotSupported,
494
0
                 "/vsistdin/ disabled. Set CPL_ALLOW_VSISTDIN to YES to "
495
0
                 "enable it");
496
0
        return false;
497
0
    }
498
499
0
    const char *pszBufferLimit =
500
0
        CPLGetConfigOption("CPL_VSISTDIN_BUFFER_LIMIT", "1048576");
501
0
    size_t nBufferLimit = GetBufferLimit(pszBufferLimit);
502
503
0
    pszFilename += strlen("/vsistdin/");
504
0
    if (*pszFilename == '?')
505
0
        pszFilename++;
506
0
    char **papszTokens = CSLTokenizeString2(pszFilename, "&", 0);
507
0
    for (int i = 0; papszTokens[i] != nullptr; i++)
508
0
    {
509
0
        char *pszUnescaped =
510
0
            CPLUnescapeString(papszTokens[i], nullptr, CPLES_URL);
511
0
        CPLFree(papszTokens[i]);
512
0
        papszTokens[i] = pszUnescaped;
513
0
    }
514
515
0
    for (int i = 0; papszTokens[i]; i++)
516
0
    {
517
0
        char *pszKey = nullptr;
518
0
        const char *pszValue = CPLParseNameValue(papszTokens[i], &pszKey);
519
0
        if (pszKey && pszValue)
520
0
        {
521
0
            if (EQUAL(pszKey, "buffer_limit"))
522
0
            {
523
0
                nBufferLimit = GetBufferLimit(pszValue);
524
0
            }
525
0
            else
526
0
            {
527
0
                CPLError(CE_Warning, CPLE_NotSupported,
528
0
                         "Unsupported option: %s", pszKey);
529
0
            }
530
0
        }
531
0
        CPLFree(pszKey);
532
0
    }
533
534
0
    CSLDestroy(papszTokens);
535
536
    // For testing purposes
537
0
    const char *pszStdinFilename =
538
0
        CPLGetConfigOption("CPL_VSISTDIN_FILE", "stdin");
539
0
    if (EQUAL(pszStdinFilename, "stdin"))
540
0
    {
541
0
        if (!gosStdinFilename.empty())
542
0
        {
543
0
            if (gStdinFile != stdin)
544
0
                fclose(gStdinFile);
545
0
            gStdinFile = stdin;
546
0
            gosStdinFilename.clear();
547
0
            gnRealPos = ftell(stdin);
548
0
            gnBufferLen = 0;
549
0
            gbHasSoughtToEnd = false;
550
0
            gnFileSize = 0;
551
0
        }
552
0
    }
553
0
    else
554
0
    {
555
0
        bool bReset = false;
556
0
        if (gosStdinFilename != pszStdinFilename)
557
0
        {
558
0
            if (gStdinFile != stdin)
559
0
                fclose(gStdinFile);
560
0
            gStdinFile = fopen(pszStdinFilename, "rb");
561
0
            if (gStdinFile == nullptr)
562
0
            {
563
0
                gStdinFile = stdin;
564
0
                return false;
565
0
            }
566
0
            gosStdinFilename = pszStdinFilename;
567
0
            bReset = true;
568
0
        }
569
0
        else
570
0
        {
571
0
            bReset = CPLTestBool(
572
0
                CPLGetConfigOption("CPL_VSISTDIN_RESET_POSITION", "NO"));
573
0
        }
574
0
        if (bReset)
575
0
        {
576
0
            gnBufferLimit = 0;
577
0
            gnBufferLen = 0;
578
0
            gnRealPos = 0;
579
0
            gbHasSoughtToEnd = false;
580
0
            gnFileSize = 0;
581
0
        }
582
0
    }
583
584
0
    gnBufferLimit = std::max(gnBufferLimit, nBufferLimit);
585
586
0
    return true;
587
0
}
588
589
/************************************************************************/
590
/*                                Open()                                */
591
/************************************************************************/
592
593
VSIVirtualHandleUniquePtr
594
VSIStdinFilesystemHandler::Open(const char *pszFilename, const char *pszAccess,
595
                                bool /* bSetError */,
596
                                CSLConstList /* papszOptions */)
597
598
0
{
599
0
    if (!ParseFilename(pszFilename))
600
0
    {
601
0
        return nullptr;
602
0
    }
603
604
0
    if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
605
0
    {
606
0
        CPLError(CE_Failure, CPLE_NotSupported,
607
0
                 "Write or update mode not supported on /vsistdin");
608
0
        return nullptr;
609
0
    }
610
611
0
    return VSIVirtualHandleUniquePtr(
612
0
        std::make_unique<VSIStdinHandle>().release());
613
0
}
614
615
/************************************************************************/
616
/*                                Stat()                                */
617
/************************************************************************/
618
619
int VSIStdinFilesystemHandler::Stat(const char *pszFilename,
620
                                    VSIStatBufL *pStatBuf, int nFlags)
621
622
0
{
623
0
    memset(pStatBuf, 0, sizeof(VSIStatBufL));
624
625
0
    if (!ParseFilename(pszFilename))
626
0
    {
627
0
        return -1;
628
0
    }
629
630
0
    if (nFlags & VSI_STAT_SIZE_FLAG)
631
0
    {
632
0
        if (gbHasSoughtToEnd)
633
0
            pStatBuf->st_size = gnFileSize;
634
0
        else
635
0
        {
636
0
            auto handle = Open(pszFilename, "rb", false, nullptr);
637
0
            if (handle == nullptr)
638
0
                return -1;
639
0
            handle->Seek(0, SEEK_END);
640
0
            pStatBuf->st_size = handle->Tell();
641
0
        }
642
0
    }
643
644
0
    pStatBuf->st_mode = S_IFREG;
645
0
    return 0;
646
0
}
647
648
//! @endcond
649
650
/************************************************************************/
651
/*                       VSIInstallStdinHandler()                       */
652
/************************************************************************/
653
654
/*!
655
 \brief Install /vsistdin/ file system handler
656
657
 A special file handler is installed that allows reading from the standard
658
 input stream.
659
660
 The file operations available are of course limited to Read() and
661
 forward Seek() (full seek in the first MB of a file by default).
662
663
 Starting with GDAL 3.6, this limit can be configured either by setting
664
 the CPL_VSISTDIN_BUFFER_LIMIT configuration option to a number of bytes
665
 (can be -1 for unlimited), or using the "/vsistdin?buffer_limit=value"
666
 filename.
667
668
 \verbatim embed:rst
669
 See :ref:`/vsistdin/ documentation <vsistdin>`
670
 \endverbatim
671
672
 */
673
void VSIInstallStdinHandler()
674
675
2
{
676
2
    auto poHandler = std::make_shared<VSIStdinFilesystemHandler>();
677
2
    VSIFileManager::InstallHandler("/vsistdin/", poHandler);
678
2
    VSIFileManager::InstallHandler("/vsistdin?", poHandler);
679
2
}