Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: VSI Virtual File System
4 : * Purpose: Implementation of caching IO layer.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2011, Frank Warmerdam <warmerdam@pobox.com>
9 : * Copyright (c) 2011-2014, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "cpl_port.h"
15 : #include "cpl_vsi_virtual.h"
16 :
17 : #include <cstddef>
18 : #include <cstring>
19 : #if HAVE_FCNTL_H
20 : #include <fcntl.h>
21 : #endif
22 :
23 : #include <algorithm>
24 : #include <limits>
25 : #include <map>
26 : #include <memory>
27 : #include <utility>
28 :
29 : #include "cpl_conv.h"
30 : #include "cpl_error.h"
31 : #include "cpl_vsi.h"
32 : #include "cpl_vsi_virtual.h"
33 : #include "cpl_mem_cache.h"
34 : #include "cpl_noncopyablevector.h"
35 :
36 : //! @cond Doxygen_Suppress
37 :
38 : /************************************************************************/
39 : /* ==================================================================== */
40 : /* VSICachedFile */
41 : /* ==================================================================== */
42 : /************************************************************************/
43 :
44 : class VSICachedFile final : public VSIVirtualHandle
45 : {
46 : CPL_DISALLOW_COPY_ASSIGN(VSICachedFile)
47 :
48 : public:
49 : VSICachedFile(VSIVirtualHandle *poBaseHandle, size_t nChunkSize,
50 : size_t nCacheSize);
51 :
52 56 : ~VSICachedFile() override
53 28 : {
54 28 : VSICachedFile::Close();
55 56 : }
56 :
57 : bool LoadBlocks(vsi_l_offset nStartBlock, size_t nBlockCount, void *pBuffer,
58 : size_t nBufferSize);
59 :
60 : VSIVirtualHandleUniquePtr m_poBase{};
61 :
62 : vsi_l_offset m_nOffset = 0;
63 : vsi_l_offset m_nFileSize = 0;
64 :
65 : size_t m_nChunkSize = 0;
66 : lru11::Cache<vsi_l_offset, cpl::NonCopyableVector<GByte>>
67 : m_oCache; // can only been initialized in constructor
68 :
69 : bool m_bEOF = false;
70 : bool m_bError = false;
71 :
72 : int Seek(vsi_l_offset nOffset, int nWhence) override;
73 : vsi_l_offset Tell() override;
74 : size_t Read(void *pBuffer, size_t nSize, size_t nMemb) override;
75 : int ReadMultiRange(int nRanges, void **ppData,
76 : const vsi_l_offset *panOffsets,
77 : const size_t *panSizes) override;
78 :
79 0 : void AdviseRead(int nRanges, const vsi_l_offset *panOffsets,
80 : const size_t *panSizes) override
81 : {
82 0 : m_poBase->AdviseRead(nRanges, panOffsets, panSizes);
83 0 : }
84 :
85 : size_t Write(const void *pBuffer, size_t nSize, size_t nMemb) override;
86 : void ClearErr() override;
87 : int Eof() override;
88 : int Error() override;
89 : int Flush() override;
90 : int Close() override;
91 :
92 0 : void *GetNativeFileDescriptor() override
93 : {
94 0 : return m_poBase->GetNativeFileDescriptor();
95 : }
96 :
97 0 : bool HasPRead() const override
98 : {
99 0 : return m_poBase->HasPRead();
100 : }
101 :
102 0 : size_t PRead(void *pBuffer, size_t nSize,
103 : vsi_l_offset nOffset) const override
104 : {
105 0 : return m_poBase->PRead(pBuffer, nSize, nOffset);
106 : }
107 : };
108 :
109 : /************************************************************************/
110 : /* GetCacheMax() */
111 : /************************************************************************/
112 :
113 28 : static size_t GetCacheMax(size_t nCacheSize)
114 : {
115 28 : if (nCacheSize)
116 : {
117 0 : return nCacheSize;
118 : }
119 :
120 28 : const char *pszCacheSize = CPLGetConfigOption("VSI_CACHE_SIZE", "25000000");
121 : GIntBig nMemorySize;
122 : bool bUnitSpecified;
123 28 : if (CPLParseMemorySize(pszCacheSize, &nMemorySize, &bUnitSpecified) !=
124 : CE_None)
125 : {
126 0 : CPLError(
127 : CE_Failure, CPLE_IllegalArg,
128 : "Failed to parse value of VSI_CACHE_SIZE. Using default of 25MB");
129 0 : nMemorySize = 25000000;
130 : }
131 28 : else if (static_cast<size_t>(nMemorySize) >
132 28 : std::numeric_limits<size_t>::max() / 2)
133 : {
134 0 : nMemorySize =
135 0 : static_cast<GIntBig>(std::numeric_limits<size_t>::max() / 2);
136 : }
137 :
138 28 : return static_cast<size_t>(nMemorySize);
139 : }
140 :
141 : /************************************************************************/
142 : /* DIV_ROUND_UP() */
143 : /************************************************************************/
144 :
145 28 : template <class T> inline T DIV_ROUND_UP(T a, T b)
146 : {
147 28 : return a / b + (((a % b) == 0) ? 0 : 1);
148 : }
149 :
150 : /************************************************************************/
151 : /* VSICachedFile() */
152 : /************************************************************************/
153 :
154 28 : VSICachedFile::VSICachedFile(VSIVirtualHandle *poBaseHandle, size_t nChunkSize,
155 28 : size_t nCacheSize)
156 : : m_poBase(poBaseHandle),
157 28 : m_nChunkSize(nChunkSize ? nChunkSize : VSI_CACHED_DEFAULT_CHUNK_SIZE),
158 56 : m_oCache{DIV_ROUND_UP(GetCacheMax(nCacheSize), m_nChunkSize), 0}
159 : {
160 28 : m_poBase->Seek(0, SEEK_END);
161 28 : m_nFileSize = m_poBase->Tell();
162 28 : }
163 :
164 : /************************************************************************/
165 : /* Close() */
166 : /************************************************************************/
167 :
168 55 : int VSICachedFile::Close()
169 :
170 : {
171 55 : m_oCache.clear();
172 55 : m_poBase.reset();
173 :
174 55 : return 0;
175 : }
176 :
177 : /************************************************************************/
178 : /* Seek() */
179 : /************************************************************************/
180 :
181 10689 : int VSICachedFile::Seek(vsi_l_offset nReqOffset, int nWhence)
182 :
183 : {
184 10689 : m_bEOF = false;
185 :
186 10689 : if (nWhence == SEEK_SET)
187 : {
188 : // Use offset directly.
189 : }
190 7970 : else if (nWhence == SEEK_CUR)
191 : {
192 7937 : nReqOffset += m_nOffset;
193 : }
194 33 : else if (nWhence == SEEK_END)
195 : {
196 33 : nReqOffset += m_nFileSize;
197 : }
198 :
199 10689 : m_nOffset = nReqOffset;
200 :
201 10689 : return 0;
202 : }
203 :
204 : /************************************************************************/
205 : /* Tell() */
206 : /************************************************************************/
207 :
208 8001 : vsi_l_offset VSICachedFile::Tell()
209 :
210 : {
211 8001 : return m_nOffset;
212 : }
213 :
214 : /************************************************************************/
215 : /* LoadBlocks() */
216 : /* */
217 : /* Load the desired set of blocks. Use pBuffer as a temporary */
218 : /* buffer if it would be helpful. */
219 : /************************************************************************/
220 :
221 438 : bool VSICachedFile::LoadBlocks(vsi_l_offset nStartBlock, size_t nBlockCount,
222 : void *pBuffer, size_t nBufferSize)
223 :
224 : {
225 438 : if (nBlockCount == 0)
226 0 : return true;
227 :
228 : /* -------------------------------------------------------------------- */
229 : /* When we want to load only one block, we can directly load it */
230 : /* into the target buffer with no concern about intermediaries. */
231 : /* -------------------------------------------------------------------- */
232 438 : if (nBlockCount == 1)
233 : {
234 412 : if (m_poBase->Seek(static_cast<vsi_l_offset>(nStartBlock) *
235 412 : m_nChunkSize,
236 412 : SEEK_SET) != 0)
237 : {
238 0 : return false;
239 : }
240 :
241 : try
242 : {
243 412 : cpl::NonCopyableVector<GByte> oData(m_nChunkSize);
244 : const auto nDataRead =
245 412 : m_poBase->Read(oData.data(), 1, m_nChunkSize);
246 412 : if (nDataRead == 0)
247 6 : return false;
248 406 : if (nDataRead < m_nChunkSize && m_poBase->Error())
249 0 : m_bError = true;
250 406 : oData.resize(nDataRead);
251 :
252 406 : m_oCache.insert(nStartBlock, std::move(oData));
253 : }
254 0 : catch (const std::exception &)
255 : {
256 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
257 : "Out of memory situation in VSICachedFile::LoadBlocks()");
258 0 : return false;
259 : }
260 :
261 406 : return true;
262 : }
263 :
264 : /* -------------------------------------------------------------------- */
265 : /* If the buffer is quite large but not quite large enough to */
266 : /* hold all the blocks we will take the pain of splitting the */
267 : /* io request in two in order to avoid allocating a large */
268 : /* temporary buffer. */
269 : /* -------------------------------------------------------------------- */
270 26 : if (nBufferSize > m_nChunkSize * 20 &&
271 14 : nBufferSize < nBlockCount * m_nChunkSize)
272 : {
273 0 : if (!LoadBlocks(nStartBlock, 2, pBuffer, nBufferSize))
274 0 : return false;
275 :
276 0 : return LoadBlocks(nStartBlock + 2, nBlockCount - 2, pBuffer,
277 0 : nBufferSize);
278 : }
279 :
280 26 : if (m_poBase->Seek(static_cast<vsi_l_offset>(nStartBlock) * m_nChunkSize,
281 26 : SEEK_SET) != 0)
282 0 : return false;
283 :
284 : /* -------------------------------------------------------------------- */
285 : /* Do we need to allocate our own buffer? */
286 : /* -------------------------------------------------------------------- */
287 26 : GByte *pabyWorkBuffer = static_cast<GByte *>(pBuffer);
288 :
289 26 : if (nBufferSize < m_nChunkSize * nBlockCount)
290 : {
291 : pabyWorkBuffer = static_cast<GByte *>(
292 4 : VSI_MALLOC_VERBOSE(m_nChunkSize * nBlockCount));
293 4 : if (pabyWorkBuffer == nullptr)
294 0 : return false;
295 : }
296 :
297 : /* -------------------------------------------------------------------- */
298 : /* Read the whole request into the working buffer. */
299 : /* -------------------------------------------------------------------- */
300 :
301 26 : const size_t nToRead = nBlockCount * m_nChunkSize;
302 26 : const size_t nDataRead = m_poBase->Read(pabyWorkBuffer, 1, nToRead);
303 26 : if (nDataRead < nToRead && m_poBase->Error())
304 0 : m_bError = true;
305 :
306 26 : bool ret = true;
307 26 : if (nToRead > nDataRead + m_nChunkSize - 1)
308 : {
309 5 : size_t nNewBlockCount = (nDataRead + m_nChunkSize - 1) / m_nChunkSize;
310 5 : if (nNewBlockCount < nBlockCount)
311 : {
312 5 : nBlockCount = nNewBlockCount;
313 5 : ret = false;
314 : }
315 : }
316 :
317 519 : for (size_t i = 0; i < nBlockCount; i++)
318 : {
319 493 : const vsi_l_offset iBlock = nStartBlock + i;
320 :
321 986 : const auto nDataFilled = (nDataRead >= (i + 1) * m_nChunkSize)
322 493 : ? m_nChunkSize
323 4 : : nDataRead - i * m_nChunkSize;
324 : try
325 : {
326 986 : cpl::NonCopyableVector<GByte> oData(nDataFilled);
327 :
328 493 : memcpy(oData.data(), pabyWorkBuffer + i * m_nChunkSize,
329 : nDataFilled);
330 :
331 493 : m_oCache.insert(iBlock, std::move(oData));
332 : }
333 0 : catch (const std::exception &)
334 : {
335 0 : CPLError(CE_Failure, CPLE_OutOfMemory,
336 : "Out of memory situation in VSICachedFile::LoadBlocks()");
337 0 : ret = false;
338 0 : break;
339 : }
340 : }
341 :
342 26 : if (pabyWorkBuffer != pBuffer)
343 4 : CPLFree(pabyWorkBuffer);
344 :
345 26 : return ret;
346 : }
347 :
348 : /************************************************************************/
349 : /* Read() */
350 : /************************************************************************/
351 :
352 11523 : size_t VSICachedFile::Read(void *pBuffer, size_t nSize, size_t nCount)
353 :
354 : {
355 11523 : if (nSize == 0 || nCount == 0)
356 0 : return 0;
357 11523 : const size_t nRequestedBytes = nSize * nCount;
358 :
359 : // nFileSize might be set wrongly to 0 by underlying layers, such as
360 : // /vsicurl_streaming/https://query.data.world/s/jgsghstpphjhicstradhy5kpjwrnfy
361 11523 : if (m_nFileSize > 0 && m_nOffset >= m_nFileSize)
362 : {
363 7 : m_bEOF = true;
364 7 : return 0;
365 : }
366 :
367 : /* ==================================================================== */
368 : /* Make sure the cache is loaded for the whole request region. */
369 : /* ==================================================================== */
370 11516 : const vsi_l_offset nStartBlock = m_nOffset / m_nChunkSize;
371 : // Calculate last block
372 11516 : const vsi_l_offset nLastBlock = m_nFileSize / m_nChunkSize;
373 11516 : vsi_l_offset nEndBlock = (m_nOffset + nRequestedBytes - 1) / m_nChunkSize;
374 :
375 : // if nLastBlock is not 0 consider the min value to avoid out-of-range reads
376 11516 : if (nLastBlock != 0 && nEndBlock > nLastBlock)
377 : {
378 6 : nEndBlock = nLastBlock;
379 : }
380 :
381 23574 : for (vsi_l_offset iBlock = nStartBlock; iBlock <= nEndBlock; iBlock++)
382 : {
383 12064 : if (!m_oCache.contains(iBlock))
384 : {
385 389 : size_t nBlocksToLoad = 1;
386 1371 : while (iBlock + nBlocksToLoad <= nEndBlock &&
387 1371 : !m_oCache.contains(iBlock + nBlocksToLoad))
388 : {
389 487 : nBlocksToLoad++;
390 : }
391 :
392 389 : if (!LoadBlocks(iBlock, nBlocksToLoad, pBuffer, nRequestedBytes))
393 6 : break;
394 : }
395 : }
396 :
397 : /* ==================================================================== */
398 : /* Copy data into the target buffer to the extent possible. */
399 : /* ==================================================================== */
400 11516 : size_t nAmountCopied = 0;
401 :
402 23676 : while (nAmountCopied < nRequestedBytes)
403 : {
404 12172 : const vsi_l_offset iBlock = (m_nOffset + nAmountCopied) / m_nChunkSize;
405 12172 : const cpl::NonCopyableVector<GByte> *poData = m_oCache.getPtr(iBlock);
406 12172 : if (poData == nullptr)
407 : {
408 : // We can reach that point when the amount to read exceeds
409 : // the cache size.
410 49 : LoadBlocks(iBlock, 1, static_cast<GByte *>(pBuffer) + nAmountCopied,
411 49 : std::min(nRequestedBytes - nAmountCopied, m_nChunkSize));
412 49 : poData = m_oCache.getPtr(iBlock);
413 49 : if (poData == nullptr)
414 : {
415 12 : break;
416 : }
417 : }
418 :
419 12167 : const vsi_l_offset nStartOffset =
420 12167 : static_cast<vsi_l_offset>(iBlock) * m_nChunkSize;
421 12167 : if (nStartOffset + poData->size() < nAmountCopied + m_nOffset)
422 1 : break;
423 : const size_t nThisCopy =
424 24332 : std::min(nRequestedBytes - nAmountCopied,
425 12166 : static_cast<size_t>(((nStartOffset + poData->size()) -
426 12166 : nAmountCopied - m_nOffset)));
427 12166 : if (nThisCopy == 0)
428 6 : break;
429 :
430 12160 : memcpy(static_cast<GByte *>(pBuffer) + nAmountCopied,
431 12160 : poData->data() + (m_nOffset + nAmountCopied) - nStartOffset,
432 : nThisCopy);
433 :
434 12160 : nAmountCopied += nThisCopy;
435 : }
436 :
437 11516 : m_nOffset += nAmountCopied;
438 :
439 11516 : const size_t nRet = nAmountCopied / nSize;
440 11516 : if (nRet != nCount && !m_bError)
441 12 : m_bEOF = true;
442 11516 : return nRet;
443 : }
444 :
445 : /************************************************************************/
446 : /* ReadMultiRange() */
447 : /************************************************************************/
448 :
449 0 : int VSICachedFile::ReadMultiRange(int const nRanges, void **const ppData,
450 : const vsi_l_offset *const panOffsets,
451 : const size_t *const panSizes)
452 : {
453 : // If the base is /vsicurl/
454 0 : return m_poBase->ReadMultiRange(nRanges, ppData, panOffsets, panSizes);
455 : }
456 :
457 : /************************************************************************/
458 : /* Write() */
459 : /************************************************************************/
460 :
461 0 : size_t VSICachedFile::Write(const void * /* pBuffer */, size_t /*nSize */,
462 : size_t /* nCount */)
463 : {
464 0 : return 0;
465 : }
466 :
467 : /************************************************************************/
468 : /* ClearErr() */
469 : /************************************************************************/
470 :
471 1 : void VSICachedFile::ClearErr()
472 :
473 : {
474 1 : m_poBase->ClearErr();
475 1 : m_bEOF = false;
476 1 : m_bError = false;
477 1 : }
478 :
479 : /************************************************************************/
480 : /* Error() */
481 : /************************************************************************/
482 :
483 2 : int VSICachedFile::Error()
484 :
485 : {
486 2 : return m_bError;
487 : }
488 :
489 : /************************************************************************/
490 : /* Eof() */
491 : /************************************************************************/
492 :
493 2 : int VSICachedFile::Eof()
494 :
495 : {
496 2 : return m_bEOF;
497 : }
498 :
499 : /************************************************************************/
500 : /* Flush() */
501 : /************************************************************************/
502 :
503 0 : int VSICachedFile::Flush()
504 :
505 : {
506 0 : return 0;
507 : }
508 :
509 : /************************************************************************/
510 : /* VSICachedFilesystemHandler */
511 : /************************************************************************/
512 :
513 : class VSICachedFilesystemHandler final : public VSIFilesystemHandler
514 : {
515 : static bool AnalyzeFilename(const char *pszFilename,
516 : std::string &osUnderlyingFilename,
517 : size_t &nChunkSize, size_t &nCacheSize);
518 :
519 : public:
520 : VSIVirtualHandle *Open(const char *pszFilename, const char *pszAccess,
521 : bool bSetError, CSLConstList papszOptions) override;
522 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
523 : int nFlags) override;
524 : char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
525 : };
526 :
527 : /************************************************************************/
528 : /* ParseSize() */
529 : /************************************************************************/
530 :
531 17 : static bool ParseSize(const char *pszKey, const char *pszValue, size_t nMaxVal,
532 : size_t &nOutVal)
533 : {
534 17 : char *end = nullptr;
535 17 : auto nVal = std::strtoull(pszValue, &end, 10);
536 17 : if (!end || end == pszValue || nVal >= nMaxVal)
537 : {
538 6 : CPLError(
539 : CE_Failure, CPLE_IllegalArg,
540 : "Invalid value for %s: %s. Max supported value = " CPL_FRMT_GUIB,
541 : pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
542 6 : return false;
543 : }
544 11 : if (*end != '\0')
545 : {
546 10 : if (strcmp(end, "KB") == 0)
547 : {
548 4 : if (nVal > nMaxVal / 1024)
549 : {
550 2 : CPLError(CE_Failure, CPLE_IllegalArg,
551 : "Invalid value for %s: %s. Max supported value "
552 : "= " CPL_FRMT_GUIB,
553 : pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
554 2 : return false;
555 : }
556 2 : nVal *= 1024;
557 : }
558 6 : else if (strcmp(end, "MB") == 0)
559 : {
560 4 : if (nVal > nMaxVal / (1024 * 1024))
561 : {
562 2 : CPLError(CE_Failure, CPLE_IllegalArg,
563 : "Invalid value for %s: %s. Max supported value "
564 : "= " CPL_FRMT_GUIB,
565 : pszKey, pszValue, static_cast<GUIntBig>(nMaxVal));
566 2 : return false;
567 : }
568 2 : nVal *= (1024 * 1024);
569 : }
570 : else
571 : {
572 2 : CPLError(CE_Failure, CPLE_IllegalArg, "Invalid value for %s: %s",
573 : pszKey, pszValue);
574 2 : return false;
575 : }
576 : }
577 5 : nOutVal = static_cast<size_t>(nVal);
578 5 : return true;
579 : }
580 :
581 : /************************************************************************/
582 : /* AnalyzeFilename() */
583 : /************************************************************************/
584 :
585 26 : bool VSICachedFilesystemHandler::AnalyzeFilename(
586 : const char *pszFilename, std::string &osUnderlyingFilename,
587 : size_t &nChunkSize, size_t &nCacheSize)
588 : {
589 :
590 26 : if (!STARTS_WITH(pszFilename, "/vsicached?"))
591 0 : return false;
592 :
593 : const CPLStringList aosTokens(
594 52 : CSLTokenizeString2(pszFilename + strlen("/vsicached?"), "&", 0));
595 :
596 26 : osUnderlyingFilename.clear();
597 26 : nChunkSize = 0;
598 26 : nCacheSize = 0;
599 :
600 42 : for (int i = 0; i < aosTokens.size(); ++i)
601 : {
602 : char *pszUnescaped =
603 28 : CPLUnescapeString(aosTokens[i], nullptr, CPLES_URL);
604 28 : std::string osUnescaped(pszUnescaped);
605 28 : CPLFree(pszUnescaped);
606 28 : char *pszKey = nullptr;
607 28 : const char *pszValue = CPLParseNameValue(osUnescaped.c_str(), &pszKey);
608 28 : if (pszKey && pszValue)
609 : {
610 28 : if (strcmp(pszKey, "file") == 0)
611 : {
612 10 : osUnderlyingFilename = pszValue;
613 : }
614 18 : else if (strcmp(pszKey, "chunk_size") == 0)
615 : {
616 11 : if (!ParseSize(pszKey, pszValue, 1024 * 1024 * 1024,
617 : nChunkSize))
618 : {
619 6 : CPLFree(pszKey);
620 6 : return false;
621 : }
622 : }
623 7 : else if (strcmp(pszKey, "cache_size") == 0)
624 : {
625 6 : if (!ParseSize(pszKey, pszValue,
626 : std::numeric_limits<size_t>::max(), nCacheSize))
627 : {
628 6 : CPLFree(pszKey);
629 6 : return false;
630 : }
631 : }
632 : else
633 : {
634 1 : CPLError(CE_Warning, CPLE_NotSupported,
635 : "Unsupported option: %s", pszKey);
636 : }
637 : }
638 16 : CPLFree(pszKey);
639 : }
640 :
641 14 : if (osUnderlyingFilename.empty())
642 : {
643 4 : CPLError(CE_Warning, CPLE_NotSupported, "Missing 'file' option");
644 : }
645 :
646 14 : return !osUnderlyingFilename.empty();
647 : }
648 :
649 : /************************************************************************/
650 : /* Open() */
651 : /************************************************************************/
652 :
653 22 : VSIVirtualHandle *VSICachedFilesystemHandler::Open(const char *pszFilename,
654 : const char *pszAccess,
655 : bool bSetError,
656 : CSLConstList papszOptions)
657 : {
658 44 : std::string osUnderlyingFilename;
659 22 : size_t nChunkSize = 0;
660 22 : size_t nCacheSize = 0;
661 22 : if (!AnalyzeFilename(pszFilename, osUnderlyingFilename, nChunkSize,
662 : nCacheSize))
663 14 : return nullptr;
664 8 : if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "rb") != 0)
665 : {
666 1 : if (bSetError)
667 : {
668 0 : VSIError(VSIE_FileError,
669 : "/vsicached? supports only 'r' and 'rb' access modes");
670 : }
671 1 : return nullptr;
672 : }
673 :
674 7 : auto fp = VSIFOpenEx2L(osUnderlyingFilename.c_str(), pszAccess, bSetError,
675 : papszOptions);
676 7 : if (!fp)
677 1 : return nullptr;
678 6 : return VSICreateCachedFile(fp, nChunkSize, nCacheSize);
679 : }
680 :
681 : /************************************************************************/
682 : /* Stat() */
683 : /************************************************************************/
684 :
685 2 : int VSICachedFilesystemHandler::Stat(const char *pszFilename,
686 : VSIStatBufL *pStatBuf, int nFlags)
687 : {
688 4 : std::string osUnderlyingFilename;
689 2 : size_t nChunkSize = 0;
690 2 : size_t nCacheSize = 0;
691 2 : if (!AnalyzeFilename(pszFilename, osUnderlyingFilename, nChunkSize,
692 : nCacheSize))
693 1 : return -1;
694 1 : return VSIStatExL(osUnderlyingFilename.c_str(), pStatBuf, nFlags);
695 : }
696 :
697 : /************************************************************************/
698 : /* ReadDirEx() */
699 : /************************************************************************/
700 :
701 2 : char **VSICachedFilesystemHandler::ReadDirEx(const char *pszDirname,
702 : int nMaxFiles)
703 : {
704 4 : std::string osUnderlyingFilename;
705 2 : size_t nChunkSize = 0;
706 2 : size_t nCacheSize = 0;
707 2 : if (!AnalyzeFilename(pszDirname, osUnderlyingFilename, nChunkSize,
708 : nCacheSize))
709 1 : return nullptr;
710 1 : return VSIReadDirEx(osUnderlyingFilename.c_str(), nMaxFiles);
711 : }
712 :
713 : //! @endcond
714 :
715 : /************************************************************************/
716 : /* VSICreateCachedFile() */
717 : /************************************************************************/
718 :
719 : /** Wraps a file handle in another one, which has caching for read-operations.
720 : *
721 : * This takes a virtual file handle and returns a new handle that caches
722 : * read-operations on the input file handle. The cache is RAM based and
723 : * the content of the cache is discarded when the file handle is closed.
724 : * The cache is a least-recently used lists of blocks of 32KB each.
725 : *
726 : * @param poBaseHandle base handle
727 : * @param nChunkSize chunk size, in bytes. If 0, defaults to 32 KB
728 : * @param nCacheSize total size of the cache for the file, in bytes.
729 : * If 0, defaults to the value of the VSI_CACHE_SIZE
730 : * configuration option, which defaults to 25 MB.
731 : * @return a new handle
732 : */
733 28 : VSIVirtualHandle *VSICreateCachedFile(VSIVirtualHandle *poBaseHandle,
734 : size_t nChunkSize, size_t nCacheSize)
735 :
736 : {
737 28 : return new VSICachedFile(poBaseHandle, nChunkSize, nCacheSize);
738 : }
739 :
740 : /************************************************************************/
741 : /* VSIInstallCachedFileHandler() */
742 : /************************************************************************/
743 :
744 : /*!
745 : \brief Install /vsicached? file system handler
746 :
747 : \verbatim embed:rst
748 : See :ref:`/vsicached? documentation <vsicached>`
749 : \endverbatim
750 :
751 : @since GDAL 3.8.0
752 : */
753 1304 : void VSIInstallCachedFileHandler(void)
754 : {
755 1304 : VSIFilesystemHandler *poHandler = new VSICachedFilesystemHandler;
756 1304 : VSIFileManager::InstallHandler("/vsicached?", poHandler);
757 1304 : }
|