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