Line data Source code
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 811 : static void VSIStdinInit()
57 : {
58 811 : if (gpabyBuffer == nullptr)
59 : {
60 : #ifdef _WIN32
61 : setmode(fileno(stdin), O_BINARY);
62 : #endif
63 5 : constexpr size_t MAX_INITIAL_ALLOC = 1024 * 1024;
64 5 : gnBufferAlloc = std::min(gnBufferAlloc, MAX_INITIAL_ALLOC);
65 5 : gpabyBuffer = static_cast<GByte *>(CPLMalloc(gnBufferAlloc));
66 : }
67 811 : }
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 0 : bool SupportsSequentialWrite(const char * /* pszPath */,
90 : bool /* bAllowLocalTempFile */) override
91 : {
92 0 : return false;
93 : }
94 :
95 0 : bool SupportsRandomWrite(const char * /* pszPath */,
96 : bool /* bAllowLocalTempFile */) override
97 : {
98 0 : return false;
99 : }
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 11 : VSIStdinHandle() = default;
120 :
121 22 : ~VSIStdinHandle() override
122 11 : {
123 11 : VSIStdinHandle::Close();
124 22 : }
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 8472 : size_t VSIStdinHandle::ReadAndCache(void *pUserBuffer, size_t nToRead)
141 : {
142 8472 : CPLAssert(m_nCurOff == gnRealPos);
143 :
144 8472 : const size_t nRead = fread(pUserBuffer, 1, nToRead, gStdinFile);
145 :
146 8472 : if (gnRealPos < gnBufferLimit)
147 : {
148 6673 : bool bCopyInBuffer = true;
149 : const size_t nToCopy = static_cast<size_t>(
150 6673 : std::min(gnBufferLimit - gnRealPos, static_cast<uint64_t>(nRead)));
151 6673 : if (gnRealPos + nToCopy > gnBufferAlloc)
152 : {
153 58 : auto newAlloc = gnRealPos + nToCopy;
154 58 : if (newAlloc < gnBufferLimit - newAlloc / 3)
155 54 : newAlloc += newAlloc / 3;
156 : else
157 4 : newAlloc = gnBufferLimit;
158 58 : GByte *newBuffer = static_cast<GByte *>(VSI_REALLOC_VERBOSE(
159 : gpabyBuffer, static_cast<size_t>(newAlloc)));
160 58 : if (newBuffer == nullptr)
161 : {
162 0 : bCopyInBuffer = false;
163 : }
164 : else
165 : {
166 58 : gpabyBuffer = newBuffer;
167 58 : gnBufferAlloc = static_cast<size_t>(newAlloc);
168 : }
169 : }
170 6673 : if (bCopyInBuffer)
171 : {
172 6673 : memcpy(gpabyBuffer + static_cast<size_t>(gnRealPos), pUserBuffer,
173 : nToCopy);
174 6673 : gnBufferLen += nToCopy;
175 : }
176 : }
177 :
178 8472 : m_nCurOff += nRead;
179 8472 : gnRealPos = m_nCurOff;
180 :
181 8472 : if (nRead < nToRead)
182 : {
183 10 : gbHasSoughtToEnd = feof(gStdinFile);
184 10 : if (gbHasSoughtToEnd)
185 10 : gnFileSize = gnRealPos;
186 10 : gbHasErrored = ferror(gStdinFile);
187 : }
188 :
189 8472 : return nRead;
190 : }
191 :
192 : /************************************************************************/
193 : /* Seek() */
194 : /************************************************************************/
195 :
196 22 : int VSIStdinHandle::Seek(vsi_l_offset nOffset, int nWhence)
197 :
198 : {
199 22 : m_bEOF = false;
200 :
201 22 : if (nWhence == SEEK_SET && nOffset == m_nCurOff)
202 4 : return 0;
203 :
204 18 : VSIStdinInit();
205 :
206 18 : if (nWhence == SEEK_END)
207 : {
208 6 : if (nOffset != 0)
209 : {
210 0 : CPLError(CE_Failure, CPLE_NotSupported,
211 : "Seek(xx != 0, SEEK_END) unsupported on /vsistdin");
212 0 : return -1;
213 : }
214 :
215 6 : if (gbHasSoughtToEnd)
216 : {
217 0 : m_nCurOff = gnFileSize;
218 0 : return 0;
219 : }
220 :
221 6 : nOffset = static_cast<vsi_l_offset>(-1);
222 : }
223 12 : else if (nWhence == SEEK_CUR)
224 : {
225 0 : nOffset += m_nCurOff;
226 : }
227 :
228 18 : if (nWhence != SEEK_END && gnRealPos >= gnBufferLimit &&
229 3 : nOffset >= gnBufferLimit)
230 : {
231 0 : CPLError(CE_Failure, CPLE_NotSupported,
232 : "Backward Seek() unsupported on /vsistdin beyond "
233 : "maximum buffer limit (" CPL_FRMT_GUIB " bytes).\n"
234 : "This limit can be extended by setting the "
235 : "CPL_VSISTDIN_BUFFER_LIMIT "
236 : "configuration option to a number of bytes, or by using the "
237 : "'/vsistdin?buffer_limit=number_of_bytes' filename.\n"
238 : "A limit of -1 means unlimited.",
239 : static_cast<GUIntBig>(gnBufferLimit));
240 0 : return -1;
241 : }
242 :
243 18 : if (nOffset < gnBufferLen)
244 : {
245 10 : m_nCurOff = nOffset;
246 10 : return 0;
247 : }
248 :
249 8 : if (nOffset == m_nCurOff)
250 0 : return 0;
251 :
252 8 : CPLDebug("VSI", "Forward seek from " CPL_FRMT_GUIB " to " CPL_FRMT_GUIB,
253 8 : static_cast<GUIntBig>(m_nCurOff), nOffset);
254 :
255 8 : char abyTemp[8192] = {};
256 8 : m_nCurOff = gnRealPos;
257 : while (true)
258 : {
259 : const size_t nToRead = static_cast<size_t>(
260 15374 : std::min(static_cast<uint64_t>(sizeof(abyTemp)),
261 7687 : static_cast<uint64_t>(nOffset - m_nCurOff)));
262 7687 : const size_t nRead = ReadAndCache(abyTemp, nToRead);
263 :
264 7687 : if (nRead < nToRead)
265 : {
266 6 : return nWhence == SEEK_END ? 0 : -1;
267 : }
268 7681 : if (nToRead < sizeof(abyTemp))
269 2 : break;
270 7679 : }
271 :
272 2 : return 0;
273 : }
274 :
275 : /************************************************************************/
276 : /* Tell() */
277 : /************************************************************************/
278 :
279 23 : vsi_l_offset VSIStdinHandle::Tell()
280 : {
281 23 : return m_nCurOff;
282 : }
283 :
284 : /************************************************************************/
285 : /* Read() */
286 : /************************************************************************/
287 :
288 793 : size_t VSIStdinHandle::Read(void *pBuffer, size_t nSize, size_t nCount)
289 :
290 : {
291 793 : VSIStdinInit();
292 :
293 793 : const size_t nBytesToRead = nSize * nCount;
294 793 : if (nBytesToRead == 0)
295 0 : return 0;
296 :
297 793 : if (m_nCurOff < gnRealPos && gnRealPos >= gnBufferLimit &&
298 5 : m_nCurOff + nBytesToRead > gnBufferLimit)
299 : {
300 1 : CPLError(CE_Failure, CPLE_NotSupported,
301 : "Backward Seek() unsupported on /vsistdin beyond "
302 : "maximum buffer limit (" CPL_FRMT_GUIB " bytes).\n"
303 : "This limit can be extended by setting the "
304 : "CPL_VSISTDIN_BUFFER_LIMIT "
305 : "configuration option to a number of bytes, or by using the "
306 : "'/vsistdin?buffer_limit=number_of_bytes' filename.\n"
307 : "A limit of -1 means unlimited.",
308 : static_cast<GUIntBig>(gnBufferLimit));
309 1 : return 0;
310 : }
311 :
312 792 : if (m_nCurOff < gnBufferLen)
313 : {
314 11 : const size_t nAlreadyCached =
315 11 : static_cast<size_t>(gnBufferLen - m_nCurOff);
316 11 : if (nBytesToRead <= nAlreadyCached)
317 : {
318 7 : memcpy(pBuffer, gpabyBuffer + static_cast<size_t>(m_nCurOff),
319 : nBytesToRead);
320 7 : m_nCurOff += nBytesToRead;
321 7 : return nCount;
322 : }
323 :
324 4 : memcpy(pBuffer, gpabyBuffer + static_cast<size_t>(m_nCurOff),
325 : nAlreadyCached);
326 4 : m_nCurOff += nAlreadyCached;
327 :
328 : const size_t nRead =
329 4 : ReadAndCache(static_cast<GByte *>(pBuffer) + nAlreadyCached,
330 : nBytesToRead - nAlreadyCached);
331 4 : m_bEOF = gbHasSoughtToEnd;
332 4 : m_bError = gbHasErrored;
333 :
334 4 : return (nRead + nAlreadyCached) / nSize;
335 : }
336 :
337 781 : const size_t nRead = ReadAndCache(pBuffer, nBytesToRead);
338 781 : m_bEOF = gbHasSoughtToEnd;
339 781 : m_bError = gbHasErrored;
340 781 : return nRead / nSize;
341 : }
342 :
343 : /************************************************************************/
344 : /* Write() */
345 : /************************************************************************/
346 :
347 0 : size_t VSIStdinHandle::Write(const void * /* pBuffer */, size_t /* nSize */,
348 : size_t /* nCount */)
349 : {
350 0 : CPLError(CE_Failure, CPLE_NotSupported, "Write() unsupported on /vsistdin");
351 0 : return 0;
352 : }
353 :
354 : /************************************************************************/
355 : /* ClearErr() */
356 : /************************************************************************/
357 :
358 0 : void VSIStdinHandle::ClearErr()
359 :
360 : {
361 0 : clearerr(gStdinFile);
362 0 : m_bEOF = false;
363 0 : m_bError = false;
364 0 : }
365 :
366 : /************************************************************************/
367 : /* Error() */
368 : /************************************************************************/
369 :
370 0 : int VSIStdinHandle::Error()
371 :
372 : {
373 0 : return m_bError;
374 : }
375 :
376 : /************************************************************************/
377 : /* Eof() */
378 : /************************************************************************/
379 :
380 2 : int VSIStdinHandle::Eof()
381 :
382 : {
383 2 : return m_bEOF;
384 : }
385 :
386 : /************************************************************************/
387 : /* Close() */
388 : /************************************************************************/
389 :
390 20 : int VSIStdinHandle::Close()
391 :
392 : {
393 29 : if (!gosStdinFilename.empty() &&
394 9 : CPLTestBool(CPLGetConfigOption("CPL_VSISTDIN_FILE_CLOSE", "NO")))
395 : {
396 7 : if (gStdinFile != stdin)
397 7 : fclose(gStdinFile);
398 7 : gStdinFile = stdin;
399 7 : gosStdinFilename.clear();
400 7 : gnRealPos = ftell(stdin);
401 7 : gnBufferLen = 0;
402 7 : gbHasSoughtToEnd = false;
403 7 : gbHasErrored = false;
404 7 : gnFileSize = 0;
405 : }
406 20 : return 0;
407 : }
408 :
409 : /************************************************************************/
410 : /* ==================================================================== */
411 : /* VSIStdinFilesystemHandler */
412 : /* ==================================================================== */
413 : /************************************************************************/
414 :
415 : /************************************************************************/
416 : /* VSIStdinFilesystemHandler() */
417 : /************************************************************************/
418 :
419 1304 : VSIStdinFilesystemHandler::VSIStdinFilesystemHandler()
420 : {
421 1304 : }
422 :
423 : /************************************************************************/
424 : /* ~VSIStdinFilesystemHandler() */
425 : /************************************************************************/
426 :
427 1866 : VSIStdinFilesystemHandler::~VSIStdinFilesystemHandler()
428 : {
429 933 : if (gStdinFile != stdin)
430 0 : fclose(gStdinFile);
431 933 : gStdinFile = stdin;
432 933 : CPLFree(gpabyBuffer);
433 933 : gpabyBuffer = nullptr;
434 933 : gnBufferLimit = 0;
435 933 : gnBufferAlloc = 0;
436 933 : gnBufferLen = 0;
437 933 : gnRealPos = 0;
438 933 : gosStdinFilename.clear();
439 1866 : }
440 :
441 : /************************************************************************/
442 : /* GetBufferLimit() */
443 : /************************************************************************/
444 :
445 22 : static size_t GetBufferLimit(const char *pszBufferLimit)
446 : {
447 : uint64_t nVal =
448 22 : 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 22 : constexpr size_t MAX_BUFFER_LIMIT = std::numeric_limits<size_t>::max() - 1;
454 22 : if (strstr(pszBufferLimit, "MB") != nullptr)
455 : {
456 1 : constexpr size_t ONE_MB = 1024 * 1024;
457 1 : if (nVal > MAX_BUFFER_LIMIT / ONE_MB)
458 : {
459 0 : nVal = MAX_BUFFER_LIMIT;
460 : }
461 : else
462 : {
463 1 : nVal *= ONE_MB;
464 : }
465 : }
466 21 : else if (strstr(pszBufferLimit, "GB") != nullptr)
467 : {
468 1 : constexpr size_t ONE_GB = 1024 * 1024 * 1024;
469 1 : if (nVal > MAX_BUFFER_LIMIT / ONE_GB)
470 : {
471 0 : nVal = MAX_BUFFER_LIMIT;
472 : }
473 : else
474 : {
475 1 : nVal *= ONE_GB;
476 : }
477 : }
478 22 : if (nVal > MAX_BUFFER_LIMIT)
479 : {
480 5 : nVal = MAX_BUFFER_LIMIT;
481 : }
482 22 : return static_cast<size_t>(nVal);
483 : }
484 :
485 : /************************************************************************/
486 : /* ParseFilename() */
487 : /************************************************************************/
488 :
489 137 : static bool ParseFilename(const char *pszFilename)
490 : {
491 137 : if (!(EQUAL(pszFilename, "/vsistdin/") ||
492 131 : ((STARTS_WITH(pszFilename, "/vsistdin/?") ||
493 131 : STARTS_WITH(pszFilename, "/vsistdin?")) &&
494 8 : strchr(pszFilename, '.') == nullptr)))
495 : {
496 123 : return false;
497 : }
498 :
499 14 : if (!CPLTestBool(CPLGetConfigOption("CPL_ALLOW_VSISTDIN", "YES")))
500 : {
501 0 : CPLError(CE_Failure, CPLE_NotSupported,
502 : "/vsistdin/ disabled. Set CPL_ALLOW_VSISTDIN to YES to "
503 : "enable it");
504 0 : return false;
505 : }
506 :
507 : const char *pszBufferLimit =
508 14 : CPLGetConfigOption("CPL_VSISTDIN_BUFFER_LIMIT", "1048576");
509 14 : size_t nBufferLimit = GetBufferLimit(pszBufferLimit);
510 :
511 14 : pszFilename += strlen("/vsistdin/");
512 14 : if (*pszFilename == '?')
513 0 : pszFilename++;
514 14 : char **papszTokens = CSLTokenizeString2(pszFilename, "&", 0);
515 22 : for (int i = 0; papszTokens[i] != nullptr; i++)
516 : {
517 : char *pszUnescaped =
518 8 : CPLUnescapeString(papszTokens[i], nullptr, CPLES_URL);
519 8 : CPLFree(papszTokens[i]);
520 8 : papszTokens[i] = pszUnescaped;
521 : }
522 :
523 22 : for (int i = 0; papszTokens[i]; i++)
524 : {
525 8 : char *pszKey = nullptr;
526 8 : const char *pszValue = CPLParseNameValue(papszTokens[i], &pszKey);
527 8 : if (pszKey && pszValue)
528 : {
529 8 : if (EQUAL(pszKey, "buffer_limit"))
530 : {
531 8 : nBufferLimit = GetBufferLimit(pszValue);
532 : }
533 : else
534 : {
535 0 : CPLError(CE_Warning, CPLE_NotSupported,
536 : "Unsupported option: %s", pszKey);
537 : }
538 : }
539 8 : CPLFree(pszKey);
540 : }
541 :
542 14 : CSLDestroy(papszTokens);
543 :
544 : // For testing purposes
545 : const char *pszStdinFilename =
546 14 : CPLGetConfigOption("CPL_VSISTDIN_FILE", "stdin");
547 14 : if (EQUAL(pszStdinFilename, "stdin"))
548 : {
549 4 : if (!gosStdinFilename.empty())
550 : {
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 : }
560 : }
561 : else
562 : {
563 10 : bool bReset = false;
564 10 : if (gosStdinFilename != pszStdinFilename)
565 : {
566 8 : if (gStdinFile != stdin)
567 0 : fclose(gStdinFile);
568 8 : gStdinFile = fopen(pszStdinFilename, "rb");
569 8 : if (gStdinFile == nullptr)
570 : {
571 0 : gStdinFile = stdin;
572 0 : return false;
573 : }
574 8 : gosStdinFilename = pszStdinFilename;
575 8 : bReset = true;
576 : }
577 : else
578 : {
579 2 : bReset = CPLTestBool(
580 : CPLGetConfigOption("CPL_VSISTDIN_RESET_POSITION", "NO"));
581 : }
582 10 : if (bReset)
583 : {
584 10 : gnBufferLimit = 0;
585 10 : gnBufferLen = 0;
586 10 : gnRealPos = 0;
587 10 : gbHasSoughtToEnd = false;
588 10 : gnFileSize = 0;
589 : }
590 : }
591 :
592 14 : gnBufferLimit = std::max(gnBufferLimit, nBufferLimit);
593 :
594 14 : return true;
595 : }
596 :
597 : /************************************************************************/
598 : /* Open() */
599 : /************************************************************************/
600 :
601 : VSIVirtualHandle *
602 43 : VSIStdinFilesystemHandler::Open(const char *pszFilename, const char *pszAccess,
603 : bool /* bSetError */,
604 : CSLConstList /* papszOptions */)
605 :
606 : {
607 43 : if (!ParseFilename(pszFilename))
608 : {
609 31 : return nullptr;
610 : }
611 :
612 12 : if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
613 : {
614 1 : CPLError(CE_Failure, CPLE_NotSupported,
615 : "Write or update mode not supported on /vsistdin");
616 1 : return nullptr;
617 : }
618 :
619 11 : return new VSIStdinHandle();
620 : }
621 :
622 : /************************************************************************/
623 : /* Stat() */
624 : /************************************************************************/
625 :
626 94 : int VSIStdinFilesystemHandler::Stat(const char *pszFilename,
627 : VSIStatBufL *pStatBuf, int nFlags)
628 :
629 : {
630 94 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
631 :
632 94 : if (!ParseFilename(pszFilename))
633 : {
634 92 : return -1;
635 : }
636 :
637 2 : if (nFlags & VSI_STAT_SIZE_FLAG)
638 : {
639 2 : if (gbHasSoughtToEnd)
640 0 : pStatBuf->st_size = gnFileSize;
641 : else
642 : {
643 2 : auto handle = Open(pszFilename, "rb", false, nullptr);
644 2 : if (handle == nullptr)
645 0 : return -1;
646 2 : handle->Seek(0, SEEK_END);
647 2 : pStatBuf->st_size = handle->Tell();
648 2 : delete handle;
649 : }
650 : }
651 :
652 2 : pStatBuf->st_mode = S_IFREG;
653 2 : return 0;
654 : }
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 1304 : void VSIInstallStdinHandler()
683 :
684 : {
685 1304 : auto poHandler = new VSIStdinFilesystemHandler;
686 1304 : VSIFileManager::InstallHandler("/vsistdin/", poHandler);
687 1304 : VSIFileManager::InstallHandler("/vsistdin?", poHandler);
688 1304 : }
|