Coverage Report

Created: 2025-06-13 06:18

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