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