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 619 : static void VSIStdinInit()
51 : {
52 619 : 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 619 : }
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 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 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 8280 : size_t VSIStdinHandle::ReadAndCache(void *pUserBuffer, size_t nToRead)
135 : {
136 8280 : CPLAssert(m_nCurOff == gnRealPos);
137 :
138 8280 : const size_t nRead = fread(pUserBuffer, 1, nToRead, gStdinFile);
139 :
140 8280 : if (gnRealPos < gnBufferLimit)
141 : {
142 6577 : bool bCopyInBuffer = true;
143 : const size_t nToCopy = static_cast<size_t>(
144 6577 : std::min(gnBufferLimit - gnRealPos, static_cast<uint64_t>(nRead)));
145 6577 : if (gnRealPos + nToCopy > gnBufferAlloc)
146 : {
147 53 : auto newAlloc = gnRealPos + nToCopy;
148 53 : if (newAlloc < gnBufferLimit - newAlloc / 3)
149 49 : newAlloc += newAlloc / 3;
150 : else
151 4 : newAlloc = gnBufferLimit;
152 53 : GByte *newBuffer = static_cast<GByte *>(VSI_REALLOC_VERBOSE(
153 : gpabyBuffer, static_cast<size_t>(newAlloc)));
154 53 : if (newBuffer == nullptr)
155 : {
156 0 : bCopyInBuffer = false;
157 : }
158 : else
159 : {
160 53 : gpabyBuffer = newBuffer;
161 53 : gnBufferAlloc = static_cast<size_t>(newAlloc);
162 : }
163 : }
164 6577 : if (bCopyInBuffer)
165 : {
166 6577 : memcpy(gpabyBuffer + static_cast<size_t>(gnRealPos), pUserBuffer,
167 : nToCopy);
168 6577 : gnBufferLen += nToCopy;
169 : }
170 : }
171 :
172 8280 : m_nCurOff += nRead;
173 8280 : gnRealPos = m_nCurOff;
174 :
175 8280 : 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 8280 : 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 601 : size_t VSIStdinHandle::Read(void *pBuffer, size_t nBytesToRead)
283 :
284 : {
285 601 : VSIStdinInit();
286 :
287 601 : if (nBytesToRead == 0)
288 0 : return 0;
289 :
290 601 : if (m_nCurOff < gnRealPos && gnRealPos >= gnBufferLimit &&
291 5 : m_nCurOff + nBytesToRead > gnBufferLimit)
292 : {
293 1 : CPLError(CE_Failure, CPLE_NotSupported,
294 : "Backward Seek() unsupported on /vsistdin beyond "
295 : "maximum buffer limit (" CPL_FRMT_GUIB " bytes).\n"
296 : "This limit can be extended by setting the "
297 : "CPL_VSISTDIN_BUFFER_LIMIT "
298 : "configuration option to a number of bytes, or by using the "
299 : "'/vsistdin?buffer_limit=number_of_bytes' filename.\n"
300 : "A limit of -1 means unlimited.",
301 : static_cast<GUIntBig>(gnBufferLimit));
302 1 : return 0;
303 : }
304 :
305 600 : if (m_nCurOff < gnBufferLen)
306 : {
307 11 : const size_t nAlreadyCached =
308 11 : static_cast<size_t>(gnBufferLen - m_nCurOff);
309 11 : if (nBytesToRead <= nAlreadyCached)
310 : {
311 7 : memcpy(pBuffer, gpabyBuffer + static_cast<size_t>(m_nCurOff),
312 : nBytesToRead);
313 7 : m_nCurOff += nBytesToRead;
314 7 : return nBytesToRead;
315 : }
316 :
317 4 : memcpy(pBuffer, gpabyBuffer + static_cast<size_t>(m_nCurOff),
318 : nAlreadyCached);
319 4 : m_nCurOff += nAlreadyCached;
320 :
321 : const size_t nRead =
322 4 : ReadAndCache(static_cast<GByte *>(pBuffer) + nAlreadyCached,
323 : nBytesToRead - nAlreadyCached);
324 4 : m_bEOF = gbHasSoughtToEnd;
325 4 : m_bError = gbHasErrored;
326 :
327 4 : return nRead + nAlreadyCached;
328 : }
329 :
330 589 : const size_t nRead = ReadAndCache(pBuffer, nBytesToRead);
331 589 : m_bEOF = gbHasSoughtToEnd;
332 589 : m_bError = gbHasErrored;
333 589 : return nRead;
334 : }
335 :
336 : /************************************************************************/
337 : /* Write() */
338 : /************************************************************************/
339 :
340 0 : size_t VSIStdinHandle::Write(const void * /* pBuffer */, size_t /* nBytes */)
341 : {
342 0 : CPLError(CE_Failure, CPLE_NotSupported, "Write() unsupported on /vsistdin");
343 0 : return 0;
344 : }
345 :
346 : /************************************************************************/
347 : /* ClearErr() */
348 : /************************************************************************/
349 :
350 0 : void VSIStdinHandle::ClearErr()
351 :
352 : {
353 0 : clearerr(gStdinFile);
354 0 : m_bEOF = false;
355 0 : m_bError = false;
356 0 : }
357 :
358 : /************************************************************************/
359 : /* Error() */
360 : /************************************************************************/
361 :
362 0 : int VSIStdinHandle::Error()
363 :
364 : {
365 0 : return m_bError;
366 : }
367 :
368 : /************************************************************************/
369 : /* Eof() */
370 : /************************************************************************/
371 :
372 2 : int VSIStdinHandle::Eof()
373 :
374 : {
375 2 : return m_bEOF;
376 : }
377 :
378 : /************************************************************************/
379 : /* Close() */
380 : /************************************************************************/
381 :
382 22 : int VSIStdinHandle::Close()
383 :
384 : {
385 31 : if (!gosStdinFilename.empty() &&
386 9 : CPLTestBool(CPLGetConfigOption("CPL_VSISTDIN_FILE_CLOSE", "NO")))
387 : {
388 7 : if (gStdinFile != stdin)
389 7 : fclose(gStdinFile);
390 7 : gStdinFile = stdin;
391 7 : gosStdinFilename.clear();
392 7 : gnRealPos = ftell(stdin);
393 7 : gnBufferLen = 0;
394 7 : gbHasSoughtToEnd = false;
395 7 : gbHasErrored = false;
396 7 : gnFileSize = 0;
397 : }
398 22 : return 0;
399 : }
400 :
401 : /************************************************************************/
402 : /* ==================================================================== */
403 : /* VSIStdinFilesystemHandler */
404 : /* ==================================================================== */
405 : /************************************************************************/
406 :
407 : /************************************************************************/
408 : /* VSIStdinFilesystemHandler() */
409 : /************************************************************************/
410 :
411 1789 : VSIStdinFilesystemHandler::VSIStdinFilesystemHandler()
412 : {
413 1789 : }
414 :
415 : /************************************************************************/
416 : /* ~VSIStdinFilesystemHandler() */
417 : /************************************************************************/
418 :
419 1126 : VSIStdinFilesystemHandler::~VSIStdinFilesystemHandler()
420 : {
421 1126 : if (gStdinFile != stdin)
422 0 : fclose(gStdinFile);
423 1126 : gStdinFile = stdin;
424 1126 : CPLFree(gpabyBuffer);
425 1126 : gpabyBuffer = nullptr;
426 1126 : gnBufferLimit = 0;
427 1126 : gnBufferAlloc = 0;
428 1126 : gnBufferLen = 0;
429 1126 : gnRealPos = 0;
430 1126 : gosStdinFilename.clear();
431 1126 : }
432 :
433 : /************************************************************************/
434 : /* GetBufferLimit() */
435 : /************************************************************************/
436 :
437 22 : static size_t GetBufferLimit(const char *pszBufferLimit)
438 : {
439 : uint64_t nVal =
440 22 : 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 22 : constexpr size_t MAX_BUFFER_LIMIT = std::numeric_limits<size_t>::max() - 1;
446 22 : if (strstr(pszBufferLimit, "MB") != nullptr)
447 : {
448 1 : constexpr size_t ONE_MB = 1024 * 1024;
449 1 : if (nVal > MAX_BUFFER_LIMIT / ONE_MB)
450 : {
451 0 : nVal = MAX_BUFFER_LIMIT;
452 : }
453 : else
454 : {
455 1 : nVal *= ONE_MB;
456 : }
457 : }
458 21 : else if (strstr(pszBufferLimit, "GB") != nullptr)
459 : {
460 1 : constexpr size_t ONE_GB = 1024 * 1024 * 1024;
461 1 : if (nVal > MAX_BUFFER_LIMIT / ONE_GB)
462 : {
463 0 : nVal = MAX_BUFFER_LIMIT;
464 : }
465 : else
466 : {
467 1 : nVal *= ONE_GB;
468 : }
469 : }
470 22 : if (nVal > MAX_BUFFER_LIMIT)
471 : {
472 5 : nVal = MAX_BUFFER_LIMIT;
473 : }
474 22 : return static_cast<size_t>(nVal);
475 : }
476 :
477 : /************************************************************************/
478 : /* ParseFilename() */
479 : /************************************************************************/
480 :
481 143 : static bool ParseFilename(const char *pszFilename)
482 : {
483 143 : if (!(EQUAL(pszFilename, "/vsistdin/") ||
484 137 : ((STARTS_WITH(pszFilename, "/vsistdin/?") ||
485 137 : STARTS_WITH(pszFilename, "/vsistdin?")) &&
486 8 : strchr(pszFilename, '.') == nullptr)))
487 : {
488 129 : return false;
489 : }
490 :
491 14 : if (!CPLTestBool(CPLGetConfigOption("CPL_ALLOW_VSISTDIN", "YES")))
492 : {
493 0 : CPLError(CE_Failure, CPLE_NotSupported,
494 : "/vsistdin/ disabled. Set CPL_ALLOW_VSISTDIN to YES to "
495 : "enable it");
496 0 : return false;
497 : }
498 :
499 : const char *pszBufferLimit =
500 14 : CPLGetConfigOption("CPL_VSISTDIN_BUFFER_LIMIT", "1048576");
501 14 : size_t nBufferLimit = GetBufferLimit(pszBufferLimit);
502 :
503 14 : pszFilename += strlen("/vsistdin/");
504 14 : if (*pszFilename == '?')
505 0 : pszFilename++;
506 14 : char **papszTokens = CSLTokenizeString2(pszFilename, "&", 0);
507 22 : for (int i = 0; papszTokens[i] != nullptr; i++)
508 : {
509 : char *pszUnescaped =
510 8 : CPLUnescapeString(papszTokens[i], nullptr, CPLES_URL);
511 8 : CPLFree(papszTokens[i]);
512 8 : papszTokens[i] = pszUnescaped;
513 : }
514 :
515 22 : for (int i = 0; papszTokens[i]; i++)
516 : {
517 8 : char *pszKey = nullptr;
518 8 : const char *pszValue = CPLParseNameValue(papszTokens[i], &pszKey);
519 8 : if (pszKey && pszValue)
520 : {
521 8 : if (EQUAL(pszKey, "buffer_limit"))
522 : {
523 8 : nBufferLimit = GetBufferLimit(pszValue);
524 : }
525 : else
526 : {
527 0 : CPLError(CE_Warning, CPLE_NotSupported,
528 : "Unsupported option: %s", pszKey);
529 : }
530 : }
531 8 : CPLFree(pszKey);
532 : }
533 :
534 14 : CSLDestroy(papszTokens);
535 :
536 : // For testing purposes
537 : const char *pszStdinFilename =
538 14 : CPLGetConfigOption("CPL_VSISTDIN_FILE", "stdin");
539 14 : if (EQUAL(pszStdinFilename, "stdin"))
540 : {
541 4 : if (!gosStdinFilename.empty())
542 : {
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 : }
552 : }
553 : else
554 : {
555 10 : bool bReset = false;
556 10 : if (gosStdinFilename != pszStdinFilename)
557 : {
558 8 : if (gStdinFile != stdin)
559 0 : fclose(gStdinFile);
560 8 : gStdinFile = fopen(pszStdinFilename, "rb");
561 8 : if (gStdinFile == nullptr)
562 : {
563 0 : gStdinFile = stdin;
564 0 : return false;
565 : }
566 8 : gosStdinFilename = pszStdinFilename;
567 8 : bReset = true;
568 : }
569 : else
570 : {
571 2 : bReset = CPLTestBool(
572 : CPLGetConfigOption("CPL_VSISTDIN_RESET_POSITION", "NO"));
573 : }
574 10 : if (bReset)
575 : {
576 10 : gnBufferLimit = 0;
577 10 : gnBufferLen = 0;
578 10 : gnRealPos = 0;
579 10 : gbHasSoughtToEnd = false;
580 10 : gnFileSize = 0;
581 : }
582 : }
583 :
584 14 : gnBufferLimit = std::max(gnBufferLimit, nBufferLimit);
585 :
586 14 : return true;
587 : }
588 :
589 : /************************************************************************/
590 : /* Open() */
591 : /************************************************************************/
592 :
593 : VSIVirtualHandleUniquePtr
594 43 : VSIStdinFilesystemHandler::Open(const char *pszFilename, const char *pszAccess,
595 : bool /* bSetError */,
596 : CSLConstList /* papszOptions */)
597 :
598 : {
599 43 : if (!ParseFilename(pszFilename))
600 : {
601 31 : return nullptr;
602 : }
603 :
604 12 : if (strchr(pszAccess, 'w') != nullptr || strchr(pszAccess, '+') != nullptr)
605 : {
606 1 : CPLError(CE_Failure, CPLE_NotSupported,
607 : "Write or update mode not supported on /vsistdin");
608 1 : return nullptr;
609 : }
610 :
611 : return VSIVirtualHandleUniquePtr(
612 11 : std::make_unique<VSIStdinHandle>().release());
613 : }
614 :
615 : /************************************************************************/
616 : /* Stat() */
617 : /************************************************************************/
618 :
619 100 : int VSIStdinFilesystemHandler::Stat(const char *pszFilename,
620 : VSIStatBufL *pStatBuf, int nFlags)
621 :
622 : {
623 100 : memset(pStatBuf, 0, sizeof(VSIStatBufL));
624 :
625 100 : if (!ParseFilename(pszFilename))
626 : {
627 98 : return -1;
628 : }
629 :
630 2 : if (nFlags & VSI_STAT_SIZE_FLAG)
631 : {
632 2 : if (gbHasSoughtToEnd)
633 0 : pStatBuf->st_size = gnFileSize;
634 : else
635 : {
636 2 : auto handle = Open(pszFilename, "rb", false, nullptr);
637 2 : if (handle == nullptr)
638 0 : return -1;
639 2 : handle->Seek(0, SEEK_END);
640 2 : pStatBuf->st_size = handle->Tell();
641 : }
642 : }
643 :
644 2 : pStatBuf->st_mode = S_IFREG;
645 2 : return 0;
646 : }
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 1789 : void VSIInstallStdinHandler()
674 :
675 : {
676 1789 : auto poHandler = std::make_shared<VSIStdinFilesystemHandler>();
677 1789 : VSIFileManager::InstallHandler("/vsistdin/", poHandler);
678 1789 : VSIFileManager::InstallHandler("/vsistdin?", poHandler);
679 1789 : }
|