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