/src/gdal/port/cpl_vsi_mem.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: VSI Virtual File System |
4 | | * Purpose: Implementation of Memory Buffer virtual IO functions. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com> |
9 | | * Copyright (c) 2007-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.h" |
16 | | #include "cpl_vsi_virtual.h" |
17 | | |
18 | | #include <cerrno> |
19 | | #include <cstddef> |
20 | | #include <cstdlib> |
21 | | #include <cstring> |
22 | | #include <ctime> |
23 | | #if HAVE_FCNTL_H |
24 | | #include <fcntl.h> |
25 | | #endif |
26 | | #if HAVE_SYS_STAT_H |
27 | | #include <sys/stat.h> |
28 | | #endif |
29 | | |
30 | | #include <algorithm> |
31 | | #include <atomic> |
32 | | #include <map> |
33 | | #include <string> |
34 | | #include <utility> |
35 | | #include <memory> |
36 | | #include <set> |
37 | | |
38 | | #include <mutex> |
39 | | // c++17 or VS2017 |
40 | | #if defined(HAVE_SHARED_MUTEX) || _MSC_VER >= 1910 |
41 | | #include <shared_mutex> |
42 | | #define CPL_SHARED_MUTEX_TYPE std::shared_mutex |
43 | 677k | #define CPL_SHARED_LOCK std::shared_lock<std::shared_mutex> |
44 | 0 | #define CPL_EXCLUSIVE_LOCK std::unique_lock<std::shared_mutex> |
45 | | #else |
46 | | // Poor-man implementation of std::shared_mutex with an exclusive mutex |
47 | | #define CPL_SHARED_MUTEX_TYPE std::mutex |
48 | | #define CPL_SHARED_LOCK std::lock_guard<std::mutex> |
49 | | #define CPL_EXCLUSIVE_LOCK std::lock_guard<std::mutex> |
50 | | #endif |
51 | | |
52 | | #include "cpl_atomic_ops.h" |
53 | | #include "cpl_conv.h" |
54 | | #include "cpl_error.h" |
55 | | #include "cpl_multiproc.h" |
56 | | #include "cpl_string.h" |
57 | | |
58 | | //! @cond Doxygen_Suppress |
59 | | |
60 | | // szHIDDEN_DIRNAME is for files created by VSIMemGenerateHiddenFilename(pszFilename). |
61 | | // Such files are of the form "/vsimem/.#!HIDDEN!#./{counter}/{pszFilename}" |
62 | | // |
63 | | // The high-level design constraint is that "/vsimem/.#!HIDDEN!#." acts as a |
64 | | // "side" hierarchy, but still under the "/vsimem/" namespace, so that code |
65 | | // having special processing of filenames starting with /vsimem/ can still work. |
66 | | // The structure of the returned filename is also such that those files form |
67 | | // independent hierarchies, i.e. the tree generated by a |
68 | | // VSIMemGenerateHiddenFilename() is "invisible" from the one returned by |
69 | | // another call to it. |
70 | | // |
71 | | // As a consequence: |
72 | | // - we don't want ".#!HIDDEN!#." to be listed in VSIReadDir("/vsimem/") |
73 | | // - we don't want content under ""/vsimem/.#!HIDDEN!#" to be deleted by |
74 | | // VSIRmdirRecursive("/vsimem/") |
75 | | // - we don't want the creation of a file (or directory) called |
76 | | // "/vsimem/.#!HIDDEN!#./{counter}/{pszFilename}" |
77 | | // to cause the implicit creation of "/vsimem/.#!HIDDEN!#./{counter}" and |
78 | | // "/vsimem/.#!HIDDEN!#". This is done so that users don't have to care about |
79 | | // cleaning such implicit directories that are upper in the hierarchy w.r.t. |
80 | | // to what we return to them. |
81 | | // - But we want the creation of file or directory |
82 | | // "/vsimem/.#!HIDDEN!#./{counter}/{pszFilename}/something_added_by_user" |
83 | | // to cause "/vsimem/.#!HIDDEN!#./{counter}/{pszFilename}" to be implicitly |
84 | | // created as a directory, so they can list it, or recursively delete it. |
85 | | // - we want VSIReadDirRecursive("/vsimem/.#!HIDDEN!#.") to list everything |
86 | | // under it (for debugging purposes) |
87 | | // - we want VSIRmdirRecursive("/vsimem/.#!HIDDEN!#.") to remove everything |
88 | | // under it (for debugging purposes) |
89 | | // |
90 | | |
91 | | constexpr const char *szHIDDEN_DIRNAME = "/vsimem/.#!HIDDEN!#."; |
92 | | |
93 | | /* |
94 | | ** Notes on Multithreading: |
95 | | ** |
96 | | ** VSIMemFilesystemHandler: This class maintains a mutex to protect |
97 | | ** access and update of the oFileList array which has all the "files" in |
98 | | ** the memory filesystem area. It is expected that multiple threads would |
99 | | ** want to create and read different files at the same time and so might |
100 | | ** collide access oFileList without the mutex. |
101 | | ** |
102 | | ** VSIMemFile: A mutex protects accesses to the file |
103 | | ** |
104 | | ** VSIMemHandle: This is essentially a "current location" representing |
105 | | ** on accessor to a file, and is inherently intended only to be used in |
106 | | ** a single thread. |
107 | | ** |
108 | | ** In General: |
109 | | ** |
110 | | ** Multiple threads accessing the memory filesystem are ok as long as |
111 | | ** a given VSIMemHandle (i.e. FILE * at app level) isn't used by multiple |
112 | | ** threads at once. |
113 | | */ |
114 | | |
115 | | /************************************************************************/ |
116 | | /* ==================================================================== */ |
117 | | /* VSIMemFile */ |
118 | | /* ==================================================================== */ |
119 | | /************************************************************************/ |
120 | | |
121 | | class VSIMemFile |
122 | | { |
123 | | CPL_DISALLOW_COPY_ASSIGN(VSIMemFile) |
124 | | |
125 | | public: |
126 | | CPLString osFilename{}; |
127 | | |
128 | | bool bIsDirectory = false; |
129 | | |
130 | | bool bOwnData = true; |
131 | | GByte *pabyData = nullptr; |
132 | | vsi_l_offset nLength = 0; |
133 | | vsi_l_offset nAllocLength = 0; |
134 | | vsi_l_offset nMaxLength = GUINTBIG_MAX; |
135 | | |
136 | | time_t mTime = 0; |
137 | | CPL_SHARED_MUTEX_TYPE m_oMutex{}; |
138 | | |
139 | | VSIMemFile(); |
140 | | virtual ~VSIMemFile(); |
141 | | |
142 | | bool SetLength(vsi_l_offset nNewSize); |
143 | | }; |
144 | | |
145 | | /************************************************************************/ |
146 | | /* ==================================================================== */ |
147 | | /* VSIMemHandle */ |
148 | | /* ==================================================================== */ |
149 | | /************************************************************************/ |
150 | | |
151 | | class VSIMemHandle final : public VSIVirtualHandle |
152 | | { |
153 | | CPL_DISALLOW_COPY_ASSIGN(VSIMemHandle) |
154 | | |
155 | | public: |
156 | | std::shared_ptr<VSIMemFile> poFile = nullptr; |
157 | | vsi_l_offset m_nOffset = 0; |
158 | | bool m_bReadAllowed = false; |
159 | | bool bUpdate = false; |
160 | | bool bEOF = false; |
161 | | bool m_bError = false; |
162 | | |
163 | 5.68k | VSIMemHandle() = default; |
164 | | ~VSIMemHandle() override; |
165 | | |
166 | | int Seek(vsi_l_offset nOffset, int nWhence) override; |
167 | | vsi_l_offset Tell() override; |
168 | | size_t Read(void *pBuffer, size_t nSize, size_t nMemb) override; |
169 | | size_t Write(const void *pBuffer, size_t nSize, size_t nMemb) override; |
170 | | void ClearErr() override; |
171 | | int Error() override; |
172 | | int Eof() override; |
173 | | int Close() override; |
174 | | int Truncate(vsi_l_offset nNewSize) override; |
175 | | |
176 | | bool HasPRead() const override |
177 | 0 | { |
178 | 0 | return true; |
179 | 0 | } |
180 | | |
181 | | size_t PRead(void * /*pBuffer*/, size_t /* nSize */, |
182 | | vsi_l_offset /*nOffset*/) const override; |
183 | | }; |
184 | | |
185 | | /************************************************************************/ |
186 | | /* ==================================================================== */ |
187 | | /* VSIMemFilesystemHandler */ |
188 | | /* ==================================================================== */ |
189 | | /************************************************************************/ |
190 | | |
191 | | class VSIMemFilesystemHandler final : public VSIFilesystemHandler |
192 | | { |
193 | | const std::string m_osPrefix; |
194 | | CPL_DISALLOW_COPY_ASSIGN(VSIMemFilesystemHandler) |
195 | | |
196 | | public: |
197 | | std::map<std::string, std::shared_ptr<VSIMemFile>> oFileList{}; |
198 | | CPLMutex *hMutex = nullptr; |
199 | | |
200 | | explicit VSIMemFilesystemHandler(const char *pszPrefix) |
201 | 3 | : m_osPrefix(pszPrefix) |
202 | 3 | { |
203 | 3 | } |
204 | | |
205 | | ~VSIMemFilesystemHandler() override; |
206 | | |
207 | | // TODO(schwehr): Fix VSIFileFromMemBuffer so that using is not needed. |
208 | | using VSIFilesystemHandler::Open; |
209 | | |
210 | | VSIVirtualHandle *Open(const char *pszFilename, const char *pszAccess, |
211 | | bool bSetError, |
212 | | CSLConstList /* papszOptions */) override; |
213 | | int Stat(const char *pszFilename, VSIStatBufL *pStatBuf, |
214 | | int nFlags) override; |
215 | | int Unlink(const char *pszFilename) override; |
216 | | int Mkdir(const char *pszDirname, long nMode) override; |
217 | | int Rmdir(const char *pszDirname) override; |
218 | | int RmdirRecursive(const char *pszDirname) override; |
219 | | char **ReadDirEx(const char *pszDirname, int nMaxFiles) override; |
220 | | int Rename(const char *oldpath, const char *newpath, GDALProgressFunc, |
221 | | void *) override; |
222 | | GIntBig GetDiskFreeSpace(const char *pszDirname) override; |
223 | | |
224 | | static std::string NormalizePath(const std::string &in); |
225 | | |
226 | | int Unlink_unlocked(const char *pszFilename); |
227 | | |
228 | | VSIFilesystemHandler *Duplicate(const char *pszPrefix) override |
229 | 0 | { |
230 | 0 | return new VSIMemFilesystemHandler(pszPrefix); |
231 | 0 | } |
232 | | }; |
233 | | |
234 | | /************************************************************************/ |
235 | | /* ==================================================================== */ |
236 | | /* VSIMemFile */ |
237 | | /* ==================================================================== */ |
238 | | /************************************************************************/ |
239 | | |
240 | | /************************************************************************/ |
241 | | /* VSIMemFile() */ |
242 | | /************************************************************************/ |
243 | | |
244 | | VSIMemFile::VSIMemFile() |
245 | 5.68k | { |
246 | 5.68k | time(&mTime); |
247 | 5.68k | } |
248 | | |
249 | | /************************************************************************/ |
250 | | /* ~VSIMemFile() */ |
251 | | /************************************************************************/ |
252 | | |
253 | | VSIMemFile::~VSIMemFile() |
254 | 5.68k | { |
255 | 5.68k | if (bOwnData && pabyData) |
256 | 0 | CPLFree(pabyData); |
257 | 5.68k | } |
258 | | |
259 | | /************************************************************************/ |
260 | | /* SetLength() */ |
261 | | /************************************************************************/ |
262 | | |
263 | | // Must be called under exclusive lock |
264 | | bool VSIMemFile::SetLength(vsi_l_offset nNewLength) |
265 | | |
266 | 0 | { |
267 | 0 | if (nNewLength > nMaxLength) |
268 | 0 | { |
269 | 0 | CPLError(CE_Failure, CPLE_NotSupported, "Maximum file size reached!"); |
270 | 0 | return false; |
271 | 0 | } |
272 | | |
273 | | /* -------------------------------------------------------------------- */ |
274 | | /* Grow underlying array if needed. */ |
275 | | /* -------------------------------------------------------------------- */ |
276 | 0 | if (nNewLength > nAllocLength) |
277 | 0 | { |
278 | | // If we don't own the buffer, we cannot reallocate it because |
279 | | // the return address might be different from the one passed by |
280 | | // the caller. Hence, the caller would not be able to free |
281 | | // the buffer. |
282 | 0 | if (!bOwnData) |
283 | 0 | { |
284 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
285 | 0 | "Cannot extended in-memory file whose ownership was not " |
286 | 0 | "transferred"); |
287 | 0 | return false; |
288 | 0 | } |
289 | | |
290 | | // If the first allocation is 1 MB or above, just take that value |
291 | | // as the one to allocate |
292 | | // Otherwise slightly reserve more to avoid too frequent reallocations. |
293 | 0 | const vsi_l_offset nNewAlloc = |
294 | 0 | (nAllocLength == 0 && nNewLength >= 1024 * 1024) |
295 | 0 | ? nNewLength |
296 | 0 | : nNewLength + nNewLength / 10 + 5000; |
297 | 0 | GByte *pabyNewData = nullptr; |
298 | 0 | if (static_cast<vsi_l_offset>(static_cast<size_t>(nNewAlloc)) == |
299 | 0 | nNewAlloc) |
300 | 0 | { |
301 | 0 | pabyNewData = static_cast<GByte *>( |
302 | 0 | nAllocLength == 0 |
303 | 0 | ? VSICalloc(1, static_cast<size_t>(nNewAlloc)) |
304 | 0 | : VSIRealloc(pabyData, static_cast<size_t>(nNewAlloc))); |
305 | 0 | } |
306 | 0 | if (pabyNewData == nullptr) |
307 | 0 | { |
308 | 0 | CPLError(CE_Failure, CPLE_OutOfMemory, |
309 | 0 | "Cannot extend in-memory file to " CPL_FRMT_GUIB |
310 | 0 | " bytes due to out-of-memory situation", |
311 | 0 | nNewAlloc); |
312 | 0 | return false; |
313 | 0 | } |
314 | | |
315 | 0 | if (nAllocLength > 0) |
316 | 0 | { |
317 | | // Clear the new allocated part of the buffer (only needed if |
318 | | // there was already reserved memory, otherwise VSICalloc() has |
319 | | // zeroized it already) |
320 | 0 | memset(pabyNewData + nAllocLength, 0, |
321 | 0 | static_cast<size_t>(nNewAlloc - nAllocLength)); |
322 | 0 | } |
323 | |
|
324 | 0 | pabyData = pabyNewData; |
325 | 0 | nAllocLength = nNewAlloc; |
326 | 0 | } |
327 | 0 | else if (nNewLength < nLength) |
328 | 0 | { |
329 | 0 | memset(pabyData + nNewLength, 0, |
330 | 0 | static_cast<size_t>(nLength - nNewLength)); |
331 | 0 | } |
332 | | |
333 | 0 | nLength = nNewLength; |
334 | 0 | time(&mTime); |
335 | |
|
336 | 0 | return true; |
337 | 0 | } |
338 | | |
339 | | /************************************************************************/ |
340 | | /* ==================================================================== */ |
341 | | /* VSIMemHandle */ |
342 | | /* ==================================================================== */ |
343 | | /************************************************************************/ |
344 | | |
345 | | /************************************************************************/ |
346 | | /* ~VSIMemHandle() */ |
347 | | /************************************************************************/ |
348 | | |
349 | | VSIMemHandle::~VSIMemHandle() |
350 | 5.68k | { |
351 | 5.68k | VSIMemHandle::Close(); |
352 | 5.68k | } |
353 | | |
354 | | /************************************************************************/ |
355 | | /* Close() */ |
356 | | /************************************************************************/ |
357 | | |
358 | | int VSIMemHandle::Close() |
359 | | |
360 | 11.3k | { |
361 | 11.3k | if (poFile) |
362 | 5.68k | { |
363 | | #ifdef DEBUG_VERBOSE |
364 | | CPLDebug("VSIMEM", "Closing handle %p on %s: ref_count=%d (before)", |
365 | | this, poFile->osFilename.c_str(), |
366 | | static_cast<int>(poFile.use_count())); |
367 | | #endif |
368 | 5.68k | poFile = nullptr; |
369 | 5.68k | } |
370 | | |
371 | 11.3k | return 0; |
372 | 11.3k | } |
373 | | |
374 | | /************************************************************************/ |
375 | | /* Seek() */ |
376 | | /************************************************************************/ |
377 | | |
378 | | int VSIMemHandle::Seek(vsi_l_offset nOffset, int nWhence) |
379 | | |
380 | 336k | { |
381 | 336k | vsi_l_offset nLength; |
382 | 336k | { |
383 | 336k | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
384 | 336k | nLength = poFile->nLength; |
385 | 336k | } |
386 | | |
387 | 336k | if (nWhence == SEEK_CUR) |
388 | 0 | { |
389 | 0 | if (nOffset > INT_MAX) |
390 | 0 | { |
391 | | // printf("likely negative offset intended\n"); |
392 | 0 | } |
393 | 0 | m_nOffset += nOffset; |
394 | 0 | } |
395 | 336k | else if (nWhence == SEEK_SET) |
396 | 335k | { |
397 | 335k | m_nOffset = nOffset; |
398 | 335k | } |
399 | 966 | else if (nWhence == SEEK_END) |
400 | 966 | { |
401 | 966 | m_nOffset = nLength + nOffset; |
402 | 966 | } |
403 | 0 | else |
404 | 0 | { |
405 | 0 | errno = EINVAL; |
406 | 0 | return -1; |
407 | 0 | } |
408 | | |
409 | 336k | bEOF = false; |
410 | | |
411 | 336k | return 0; |
412 | 336k | } |
413 | | |
414 | | /************************************************************************/ |
415 | | /* Tell() */ |
416 | | /************************************************************************/ |
417 | | |
418 | | vsi_l_offset VSIMemHandle::Tell() |
419 | | |
420 | 966 | { |
421 | 966 | return m_nOffset; |
422 | 966 | } |
423 | | |
424 | | /************************************************************************/ |
425 | | /* Read() */ |
426 | | /************************************************************************/ |
427 | | |
428 | | size_t VSIMemHandle::Read(void *pBuffer, size_t nSize, size_t nCount) |
429 | | |
430 | 340k | { |
431 | 340k | const vsi_l_offset nOffset = m_nOffset; |
432 | | |
433 | 340k | size_t nBytesToRead = nSize * nCount; |
434 | 340k | if (nBytesToRead == 0) |
435 | 0 | return 0; |
436 | | |
437 | 340k | if (nCount > 0 && nBytesToRead / nCount != nSize) |
438 | 0 | { |
439 | 0 | bEOF = true; |
440 | 0 | return 0; |
441 | 0 | } |
442 | | |
443 | 340k | if (!m_bReadAllowed) |
444 | 0 | { |
445 | 0 | m_bError = true; |
446 | 0 | return 0; |
447 | 0 | } |
448 | | |
449 | 340k | bool bEOFTmp = bEOF; |
450 | | // Do not access/modify bEOF under the lock to avoid confusing Coverity |
451 | | // Scan since we access it in other methods outside of the lock. |
452 | 340k | const auto DoUnderLock = |
453 | 340k | [this, nOffset, pBuffer, nSize, &nBytesToRead, &nCount, &bEOFTmp] |
454 | 340k | { |
455 | 340k | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
456 | | |
457 | 340k | if (poFile->nLength <= nOffset || nBytesToRead + nOffset < nBytesToRead) |
458 | 454 | { |
459 | 454 | bEOFTmp = true; |
460 | 454 | return false; |
461 | 454 | } |
462 | 340k | if (nBytesToRead + nOffset > poFile->nLength) |
463 | 268 | { |
464 | 268 | nBytesToRead = static_cast<size_t>(poFile->nLength - nOffset); |
465 | 268 | nCount = nBytesToRead / nSize; |
466 | 268 | bEOFTmp = true; |
467 | 268 | } |
468 | | |
469 | 340k | if (nBytesToRead) |
470 | 340k | memcpy(pBuffer, poFile->pabyData + nOffset, |
471 | 340k | static_cast<size_t>(nBytesToRead)); |
472 | 340k | return true; |
473 | 340k | }; |
474 | | |
475 | 340k | bool bRet = DoUnderLock(); |
476 | 340k | bEOF = bEOFTmp; |
477 | 340k | if (!bRet) |
478 | 454 | return 0; |
479 | | |
480 | 340k | m_nOffset += nBytesToRead; |
481 | | |
482 | 340k | return nCount; |
483 | 340k | } |
484 | | |
485 | | /************************************************************************/ |
486 | | /* PRead() */ |
487 | | /************************************************************************/ |
488 | | |
489 | | size_t VSIMemHandle::PRead(void *pBuffer, size_t nSize, |
490 | | vsi_l_offset nOffset) const |
491 | 0 | { |
492 | 0 | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
493 | |
|
494 | 0 | if (nOffset < poFile->nLength) |
495 | 0 | { |
496 | 0 | const size_t nToCopy = static_cast<size_t>( |
497 | 0 | std::min(static_cast<vsi_l_offset>(poFile->nLength - nOffset), |
498 | 0 | static_cast<vsi_l_offset>(nSize))); |
499 | 0 | memcpy(pBuffer, poFile->pabyData + static_cast<size_t>(nOffset), |
500 | 0 | nToCopy); |
501 | 0 | return nToCopy; |
502 | 0 | } |
503 | 0 | return 0; |
504 | 0 | } |
505 | | |
506 | | /************************************************************************/ |
507 | | /* Write() */ |
508 | | /************************************************************************/ |
509 | | |
510 | | size_t VSIMemHandle::Write(const void *pBuffer, size_t nSize, size_t nCount) |
511 | | |
512 | 0 | { |
513 | 0 | const vsi_l_offset nOffset = m_nOffset; |
514 | |
|
515 | 0 | if (!bUpdate) |
516 | 0 | { |
517 | 0 | errno = EACCES; |
518 | 0 | return 0; |
519 | 0 | } |
520 | | |
521 | 0 | const size_t nBytesToWrite = nSize * nCount; |
522 | |
|
523 | 0 | { |
524 | 0 | CPL_EXCLUSIVE_LOCK oLock(poFile->m_oMutex); |
525 | |
|
526 | 0 | if (nCount > 0 && nBytesToWrite / nCount != nSize) |
527 | 0 | { |
528 | 0 | return 0; |
529 | 0 | } |
530 | 0 | if (nBytesToWrite + nOffset < nBytesToWrite) |
531 | 0 | { |
532 | 0 | return 0; |
533 | 0 | } |
534 | | |
535 | 0 | if (nBytesToWrite + nOffset > poFile->nLength) |
536 | 0 | { |
537 | 0 | if (!poFile->SetLength(nBytesToWrite + nOffset)) |
538 | 0 | return 0; |
539 | 0 | } |
540 | | |
541 | 0 | if (nBytesToWrite) |
542 | 0 | memcpy(poFile->pabyData + nOffset, pBuffer, nBytesToWrite); |
543 | |
|
544 | 0 | time(&poFile->mTime); |
545 | 0 | } |
546 | | |
547 | 0 | m_nOffset += nBytesToWrite; |
548 | |
|
549 | 0 | return nCount; |
550 | 0 | } |
551 | | |
552 | | /************************************************************************/ |
553 | | /* ClearErr() */ |
554 | | /************************************************************************/ |
555 | | |
556 | | void VSIMemHandle::ClearErr() |
557 | | |
558 | 0 | { |
559 | 0 | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
560 | 0 | bEOF = false; |
561 | 0 | m_bError = false; |
562 | 0 | } |
563 | | |
564 | | /************************************************************************/ |
565 | | /* Error() */ |
566 | | /************************************************************************/ |
567 | | |
568 | | int VSIMemHandle::Error() |
569 | | |
570 | 0 | { |
571 | 0 | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
572 | 0 | return m_bError ? TRUE : FALSE; |
573 | 0 | } |
574 | | |
575 | | /************************************************************************/ |
576 | | /* Eof() */ |
577 | | /************************************************************************/ |
578 | | |
579 | | int VSIMemHandle::Eof() |
580 | | |
581 | 0 | { |
582 | 0 | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
583 | 0 | return bEOF ? TRUE : FALSE; |
584 | 0 | } |
585 | | |
586 | | /************************************************************************/ |
587 | | /* Truncate() */ |
588 | | /************************************************************************/ |
589 | | |
590 | | int VSIMemHandle::Truncate(vsi_l_offset nNewSize) |
591 | 0 | { |
592 | 0 | if (!bUpdate) |
593 | 0 | { |
594 | 0 | errno = EACCES; |
595 | 0 | return -1; |
596 | 0 | } |
597 | | |
598 | 0 | CPL_EXCLUSIVE_LOCK oLock(poFile->m_oMutex); |
599 | 0 | if (poFile->SetLength(nNewSize)) |
600 | 0 | return 0; |
601 | | |
602 | 0 | return -1; |
603 | 0 | } |
604 | | |
605 | | /************************************************************************/ |
606 | | /* ==================================================================== */ |
607 | | /* VSIMemFilesystemHandler */ |
608 | | /* ==================================================================== */ |
609 | | /************************************************************************/ |
610 | | |
611 | | /************************************************************************/ |
612 | | /* ~VSIMemFilesystemHandler() */ |
613 | | /************************************************************************/ |
614 | | |
615 | | VSIMemFilesystemHandler::~VSIMemFilesystemHandler() |
616 | | |
617 | 0 | { |
618 | 0 | oFileList.clear(); |
619 | |
|
620 | 0 | if (hMutex != nullptr) |
621 | 0 | CPLDestroyMutex(hMutex); |
622 | 0 | hMutex = nullptr; |
623 | 0 | } |
624 | | |
625 | | /************************************************************************/ |
626 | | /* Open() */ |
627 | | /************************************************************************/ |
628 | | |
629 | | VSIVirtualHandle *VSIMemFilesystemHandler::Open(const char *pszFilename, |
630 | | const char *pszAccess, |
631 | | bool bSetError, |
632 | | CSLConstList /* papszOptions */) |
633 | | |
634 | 724 | { |
635 | 724 | CPLMutexHolder oHolder(&hMutex); |
636 | 724 | const std::string osFilename = NormalizePath(pszFilename); |
637 | 724 | if (osFilename.empty()) |
638 | 0 | return nullptr; |
639 | | |
640 | 724 | vsi_l_offset nMaxLength = GUINTBIG_MAX; |
641 | 724 | const size_t iPos = osFilename.find("||maxlength="); |
642 | 724 | if (iPos != std::string::npos) |
643 | 319 | { |
644 | 319 | nMaxLength = static_cast<vsi_l_offset>(CPLAtoGIntBig( |
645 | 319 | osFilename.substr(iPos + strlen("||maxlength=")).c_str())); |
646 | 319 | } |
647 | | |
648 | | /* -------------------------------------------------------------------- */ |
649 | | /* Get the filename we are opening, create if needed. */ |
650 | | /* -------------------------------------------------------------------- */ |
651 | 724 | std::shared_ptr<VSIMemFile> poFile = nullptr; |
652 | 724 | const auto oIter = oFileList.find(osFilename); |
653 | 724 | if (oIter != oFileList.end()) |
654 | 0 | { |
655 | 0 | poFile = oIter->second; |
656 | 0 | } |
657 | | |
658 | | // If no file and opening in read, error out. |
659 | 724 | if (strstr(pszAccess, "w") == nullptr && |
660 | 724 | strstr(pszAccess, "a") == nullptr && poFile == nullptr) |
661 | 724 | { |
662 | 724 | if (bSetError) |
663 | 0 | { |
664 | 0 | VSIError(VSIE_FileError, "No such file or directory"); |
665 | 0 | } |
666 | 724 | errno = ENOENT; |
667 | 724 | return nullptr; |
668 | 724 | } |
669 | | |
670 | | // Create. |
671 | 0 | if (poFile == nullptr) |
672 | 0 | { |
673 | 0 | const std::string osFileDir = CPLGetPathSafe(osFilename.c_str()); |
674 | 0 | if (VSIMkdirRecursive(osFileDir.c_str(), 0755) == -1) |
675 | 0 | { |
676 | 0 | if (bSetError) |
677 | 0 | { |
678 | 0 | VSIError(VSIE_FileError, |
679 | 0 | "Could not create directory %s for writing", |
680 | 0 | osFileDir.c_str()); |
681 | 0 | } |
682 | 0 | errno = ENOENT; |
683 | 0 | return nullptr; |
684 | 0 | } |
685 | | |
686 | 0 | poFile = std::make_shared<VSIMemFile>(); |
687 | 0 | poFile->osFilename = osFilename; |
688 | 0 | oFileList[poFile->osFilename] = poFile; |
689 | | #ifdef DEBUG_VERBOSE |
690 | | CPLDebug("VSIMEM", "Creating file %s: ref_count=%d", pszFilename, |
691 | | static_cast<int>(poFile.use_count())); |
692 | | #endif |
693 | 0 | poFile->nMaxLength = nMaxLength; |
694 | 0 | } |
695 | | // Overwrite |
696 | 0 | else if (strstr(pszAccess, "w")) |
697 | 0 | { |
698 | 0 | CPL_EXCLUSIVE_LOCK oLock(poFile->m_oMutex); |
699 | 0 | poFile->SetLength(0); |
700 | 0 | poFile->nMaxLength = nMaxLength; |
701 | 0 | } |
702 | | |
703 | 0 | if (poFile->bIsDirectory) |
704 | 0 | { |
705 | 0 | errno = EISDIR; |
706 | 0 | return nullptr; |
707 | 0 | } |
708 | | |
709 | | /* -------------------------------------------------------------------- */ |
710 | | /* Setup the file handle on this file. */ |
711 | | /* -------------------------------------------------------------------- */ |
712 | 0 | VSIMemHandle *poHandle = new VSIMemHandle; |
713 | |
|
714 | 0 | poHandle->poFile = poFile; |
715 | 0 | poHandle->m_nOffset = 0; |
716 | 0 | poHandle->bEOF = false; |
717 | 0 | poHandle->bUpdate = strchr(pszAccess, 'w') || strchr(pszAccess, '+') || |
718 | 0 | strchr(pszAccess, 'a'); |
719 | 0 | poHandle->m_bReadAllowed = strchr(pszAccess, 'r') || strchr(pszAccess, '+'); |
720 | |
|
721 | | #ifdef DEBUG_VERBOSE |
722 | | CPLDebug("VSIMEM", "Opening handle %p on %s: ref_count=%d", poHandle, |
723 | | pszFilename, static_cast<int>(poFile.use_count())); |
724 | | #endif |
725 | 0 | if (strchr(pszAccess, 'a')) |
726 | 0 | { |
727 | 0 | vsi_l_offset nOffset; |
728 | 0 | { |
729 | 0 | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
730 | 0 | nOffset = poFile->nLength; |
731 | 0 | } |
732 | 0 | poHandle->m_nOffset = nOffset; |
733 | 0 | } |
734 | |
|
735 | 0 | return poHandle; |
736 | 0 | } |
737 | | |
738 | | /************************************************************************/ |
739 | | /* Stat() */ |
740 | | /************************************************************************/ |
741 | | |
742 | | int VSIMemFilesystemHandler::Stat(const char *pszFilename, |
743 | | VSIStatBufL *pStatBuf, int /* nFlags */) |
744 | | |
745 | 6.34k | { |
746 | 6.34k | CPLMutexHolder oHolder(&hMutex); |
747 | | |
748 | 6.34k | const std::string osFilename = NormalizePath(pszFilename); |
749 | | |
750 | 6.34k | memset(pStatBuf, 0, sizeof(VSIStatBufL)); |
751 | | |
752 | 6.34k | if (osFilename == m_osPrefix || osFilename + '/' == m_osPrefix) |
753 | 5.80k | { |
754 | 5.80k | pStatBuf->st_size = 0; |
755 | 5.80k | pStatBuf->st_mode = S_IFDIR; |
756 | 5.80k | return 0; |
757 | 5.80k | } |
758 | | |
759 | 544 | auto oIter = oFileList.find(osFilename); |
760 | 544 | if (oIter == oFileList.end()) |
761 | 322 | { |
762 | 322 | errno = ENOENT; |
763 | 322 | return -1; |
764 | 322 | } |
765 | | |
766 | 222 | std::shared_ptr<VSIMemFile> poFile = oIter->second; |
767 | | |
768 | 222 | memset(pStatBuf, 0, sizeof(VSIStatBufL)); |
769 | | |
770 | 222 | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
771 | 222 | if (poFile->bIsDirectory) |
772 | 0 | { |
773 | 0 | pStatBuf->st_size = 0; |
774 | 0 | pStatBuf->st_mode = S_IFDIR; |
775 | 0 | } |
776 | 222 | else |
777 | 222 | { |
778 | 222 | pStatBuf->st_size = poFile->nLength; |
779 | 222 | pStatBuf->st_mode = S_IFREG; |
780 | 222 | if (const char *mtime = |
781 | 222 | CPLGetConfigOption("CPL_VSI_MEM_MTIME", nullptr)) |
782 | 0 | { |
783 | 0 | pStatBuf->st_mtime = |
784 | 0 | static_cast<time_t>(std::strtoll(mtime, nullptr, 10)); |
785 | 0 | } |
786 | 222 | else |
787 | 222 | { |
788 | 222 | pStatBuf->st_mtime = poFile->mTime; |
789 | 222 | } |
790 | 222 | } |
791 | | |
792 | 222 | return 0; |
793 | 544 | } |
794 | | |
795 | | /************************************************************************/ |
796 | | /* Unlink() */ |
797 | | /************************************************************************/ |
798 | | |
799 | | int VSIMemFilesystemHandler::Unlink(const char *pszFilename) |
800 | | |
801 | 5.68k | { |
802 | 5.68k | CPLMutexHolder oHolder(&hMutex); |
803 | 5.68k | return Unlink_unlocked(pszFilename); |
804 | 5.68k | } |
805 | | |
806 | | /************************************************************************/ |
807 | | /* Unlink_unlocked() */ |
808 | | /************************************************************************/ |
809 | | |
810 | | int VSIMemFilesystemHandler::Unlink_unlocked(const char *pszFilename) |
811 | | |
812 | 11.3k | { |
813 | 11.3k | const std::string osFilename = NormalizePath(pszFilename); |
814 | | |
815 | 11.3k | auto oIter = oFileList.find(osFilename); |
816 | 11.3k | if (oIter == oFileList.end()) |
817 | 5.68k | { |
818 | 5.68k | errno = ENOENT; |
819 | 5.68k | return -1; |
820 | 5.68k | } |
821 | | |
822 | | #ifdef DEBUG_VERBOSE |
823 | | std::shared_ptr<VSIMemFile> poFile = oIter->second; |
824 | | CPLDebug("VSIMEM", "Unlink %s: ref_count=%d (before)", pszFilename, |
825 | | static_cast<int>(poFile.use_count())); |
826 | | #endif |
827 | 5.68k | oFileList.erase(oIter); |
828 | | |
829 | 5.68k | return 0; |
830 | 11.3k | } |
831 | | |
832 | | /************************************************************************/ |
833 | | /* Mkdir() */ |
834 | | /************************************************************************/ |
835 | | |
836 | | int VSIMemFilesystemHandler::Mkdir(const char *pszPathname, long /* nMode */) |
837 | | |
838 | 0 | { |
839 | 0 | CPLMutexHolder oHolder(&hMutex); |
840 | |
|
841 | 0 | const std::string osPathname = NormalizePath(pszPathname); |
842 | 0 | if (STARTS_WITH(osPathname.c_str(), szHIDDEN_DIRNAME)) |
843 | 0 | { |
844 | 0 | if (osPathname.size() == strlen(szHIDDEN_DIRNAME)) |
845 | 0 | return 0; |
846 | | // "/vsimem/.#!HIDDEN!#./{unique_counter}" |
847 | 0 | else if (osPathname.find('/', strlen(szHIDDEN_DIRNAME) + 1) == |
848 | 0 | std::string::npos) |
849 | 0 | return 0; |
850 | | |
851 | | // If "/vsimem/.#!HIDDEN!#./{unique_counter}/user_directory", then |
852 | | // accept creating an explicit directory |
853 | 0 | } |
854 | | |
855 | 0 | if (oFileList.find(osPathname) != oFileList.end()) |
856 | 0 | { |
857 | 0 | errno = EEXIST; |
858 | 0 | return -1; |
859 | 0 | } |
860 | | |
861 | 0 | std::shared_ptr<VSIMemFile> poFile = std::make_shared<VSIMemFile>(); |
862 | 0 | poFile->osFilename = osPathname; |
863 | 0 | poFile->bIsDirectory = true; |
864 | 0 | oFileList[osPathname] = poFile; |
865 | | #ifdef DEBUG_VERBOSE |
866 | | CPLDebug("VSIMEM", "Mkdir on %s: ref_count=%d", pszPathname, |
867 | | static_cast<int>(poFile.use_count())); |
868 | | #endif |
869 | 0 | CPL_IGNORE_RET_VAL(poFile); |
870 | 0 | return 0; |
871 | 0 | } |
872 | | |
873 | | /************************************************************************/ |
874 | | /* Rmdir() */ |
875 | | /************************************************************************/ |
876 | | |
877 | | int VSIMemFilesystemHandler::Rmdir(const char *pszPathname) |
878 | | |
879 | 0 | { |
880 | 0 | return Unlink(pszPathname); |
881 | 0 | } |
882 | | |
883 | | /************************************************************************/ |
884 | | /* RmdirRecursive() */ |
885 | | /************************************************************************/ |
886 | | |
887 | | int VSIMemFilesystemHandler::RmdirRecursive(const char *pszDirname) |
888 | 0 | { |
889 | 0 | CPLMutexHolder oHolder(&hMutex); |
890 | |
|
891 | 0 | const CPLString osPath = NormalizePath(pszDirname); |
892 | 0 | const size_t nPathLen = osPath.size(); |
893 | 0 | int ret = 0; |
894 | 0 | if (osPath == "/vsimem") |
895 | 0 | { |
896 | | // Clean-up all files under pszDirname, except hidden directories |
897 | | // if called from "/vsimem" |
898 | 0 | for (auto iter = oFileList.begin(); iter != oFileList.end(); |
899 | 0 | /* no automatic increment */) |
900 | 0 | { |
901 | 0 | const char *pszFilePath = iter->second->osFilename.c_str(); |
902 | 0 | const size_t nFileLen = iter->second->osFilename.size(); |
903 | 0 | if (nFileLen > nPathLen && |
904 | 0 | memcmp(pszFilePath, osPath.c_str(), nPathLen) == 0 && |
905 | 0 | pszFilePath[nPathLen] == '/' && |
906 | 0 | !STARTS_WITH(pszFilePath, szHIDDEN_DIRNAME)) |
907 | 0 | { |
908 | 0 | iter = oFileList.erase(iter); |
909 | 0 | } |
910 | 0 | else |
911 | 0 | { |
912 | 0 | ++iter; |
913 | 0 | } |
914 | 0 | } |
915 | 0 | } |
916 | 0 | else |
917 | 0 | { |
918 | 0 | ret = -1; |
919 | 0 | for (auto iter = oFileList.begin(); iter != oFileList.end(); |
920 | 0 | /* no automatic increment */) |
921 | 0 | { |
922 | 0 | const char *pszFilePath = iter->second->osFilename.c_str(); |
923 | 0 | const size_t nFileLen = iter->second->osFilename.size(); |
924 | 0 | if (nFileLen >= nPathLen && |
925 | 0 | memcmp(pszFilePath, osPath.c_str(), nPathLen) == 0 && |
926 | 0 | (nFileLen == nPathLen || pszFilePath[nPathLen] == '/')) |
927 | 0 | { |
928 | | // If VSIRmdirRecursive() is used correctly, it should at |
929 | | // least delete the directory on which it has been called |
930 | 0 | ret = 0; |
931 | 0 | iter = oFileList.erase(iter); |
932 | 0 | } |
933 | 0 | else |
934 | 0 | { |
935 | 0 | ++iter; |
936 | 0 | } |
937 | 0 | } |
938 | | |
939 | | // Make sure that it always succeed on the root hidden directory |
940 | 0 | if (osPath == szHIDDEN_DIRNAME) |
941 | 0 | ret = 0; |
942 | 0 | } |
943 | 0 | return ret; |
944 | 0 | } |
945 | | |
946 | | /************************************************************************/ |
947 | | /* ReadDirEx() */ |
948 | | /************************************************************************/ |
949 | | |
950 | | char **VSIMemFilesystemHandler::ReadDirEx(const char *pszPath, int nMaxFiles) |
951 | | |
952 | 0 | { |
953 | 0 | CPLMutexHolder oHolder(&hMutex); |
954 | |
|
955 | 0 | const CPLString osPath = NormalizePath(pszPath); |
956 | |
|
957 | 0 | char **papszDir = nullptr; |
958 | 0 | const size_t nPathLen = osPath.size(); |
959 | | |
960 | | // In case of really big number of files in the directory, CSLAddString |
961 | | // can be slow (see #2158). We then directly build the list. |
962 | 0 | int nItems = 0; |
963 | 0 | int nAllocatedItems = 0; |
964 | |
|
965 | 0 | if (osPath == szHIDDEN_DIRNAME) |
966 | 0 | { |
967 | | // Special mode for hidden filenames. |
968 | | // "/vsimem/.#!HIDDEN!#./{counter}" subdirectories are not explicitly |
969 | | // created so they do not appear in oFileList, but their subcontent |
970 | | // (e.g "/vsimem/.#!HIDDEN!#./{counter}/foo") does |
971 | 0 | std::set<std::string> oSetSubDirs; |
972 | 0 | for (const auto &iter : oFileList) |
973 | 0 | { |
974 | 0 | const char *pszFilePath = iter.second->osFilename.c_str(); |
975 | 0 | if (iter.second->osFilename.size() > nPathLen && |
976 | 0 | memcmp(pszFilePath, osPath.c_str(), nPathLen) == 0) |
977 | 0 | { |
978 | 0 | char *pszItem = CPLStrdup(pszFilePath + nPathLen + 1); |
979 | 0 | char *pszSlash = strchr(pszItem, '/'); |
980 | 0 | if (pszSlash) |
981 | 0 | *pszSlash = 0; |
982 | 0 | if (cpl::contains(oSetSubDirs, pszItem)) |
983 | 0 | { |
984 | 0 | CPLFree(pszItem); |
985 | 0 | continue; |
986 | 0 | } |
987 | 0 | oSetSubDirs.insert(pszItem); |
988 | |
|
989 | 0 | if (nItems == 0) |
990 | 0 | { |
991 | 0 | papszDir = |
992 | 0 | static_cast<char **>(CPLCalloc(2, sizeof(char *))); |
993 | 0 | nAllocatedItems = 1; |
994 | 0 | } |
995 | 0 | else if (nItems >= nAllocatedItems) |
996 | 0 | { |
997 | 0 | nAllocatedItems = nAllocatedItems * 2; |
998 | 0 | papszDir = static_cast<char **>(CPLRealloc( |
999 | 0 | papszDir, (nAllocatedItems + 2) * sizeof(char *))); |
1000 | 0 | } |
1001 | |
|
1002 | 0 | papszDir[nItems] = pszItem; |
1003 | 0 | papszDir[nItems + 1] = nullptr; |
1004 | |
|
1005 | 0 | nItems++; |
1006 | 0 | if (nMaxFiles > 0 && nItems > nMaxFiles) |
1007 | 0 | break; |
1008 | 0 | } |
1009 | 0 | } |
1010 | 0 | } |
1011 | 0 | else |
1012 | 0 | { |
1013 | 0 | for (const auto &iter : oFileList) |
1014 | 0 | { |
1015 | 0 | const char *pszFilePath = iter.second->osFilename.c_str(); |
1016 | 0 | if (iter.second->osFilename.size() > nPathLen && |
1017 | 0 | memcmp(pszFilePath, osPath.c_str(), nPathLen) == 0 && |
1018 | 0 | pszFilePath[nPathLen] == '/' && |
1019 | 0 | strstr(pszFilePath + nPathLen + 1, "/") == nullptr) |
1020 | 0 | { |
1021 | 0 | if (nItems == 0) |
1022 | 0 | { |
1023 | 0 | papszDir = |
1024 | 0 | static_cast<char **>(CPLCalloc(2, sizeof(char *))); |
1025 | 0 | nAllocatedItems = 1; |
1026 | 0 | } |
1027 | 0 | else if (nItems >= nAllocatedItems) |
1028 | 0 | { |
1029 | 0 | nAllocatedItems = nAllocatedItems * 2; |
1030 | 0 | papszDir = static_cast<char **>(CPLRealloc( |
1031 | 0 | papszDir, (nAllocatedItems + 2) * sizeof(char *))); |
1032 | 0 | } |
1033 | |
|
1034 | 0 | papszDir[nItems] = CPLStrdup(pszFilePath + nPathLen + 1); |
1035 | 0 | papszDir[nItems + 1] = nullptr; |
1036 | |
|
1037 | 0 | nItems++; |
1038 | 0 | if (nMaxFiles > 0 && nItems > nMaxFiles) |
1039 | 0 | break; |
1040 | 0 | } |
1041 | 0 | } |
1042 | 0 | } |
1043 | |
|
1044 | 0 | return papszDir; |
1045 | 0 | } |
1046 | | |
1047 | | /************************************************************************/ |
1048 | | /* Rename() */ |
1049 | | /************************************************************************/ |
1050 | | |
1051 | | int VSIMemFilesystemHandler::Rename(const char *pszOldPath, |
1052 | | const char *pszNewPath, GDALProgressFunc, |
1053 | | void *) |
1054 | | |
1055 | 0 | { |
1056 | 0 | CPLMutexHolder oHolder(&hMutex); |
1057 | |
|
1058 | 0 | const std::string osOldPath = NormalizePath(pszOldPath); |
1059 | 0 | const std::string osNewPath = NormalizePath(pszNewPath); |
1060 | 0 | if (!STARTS_WITH(pszNewPath, m_osPrefix.c_str())) |
1061 | 0 | return -1; |
1062 | | |
1063 | 0 | if (osOldPath.compare(osNewPath) == 0) |
1064 | 0 | return 0; |
1065 | | |
1066 | 0 | if (oFileList.find(osOldPath) == oFileList.end()) |
1067 | 0 | { |
1068 | 0 | errno = ENOENT; |
1069 | 0 | return -1; |
1070 | 0 | } |
1071 | | |
1072 | 0 | std::map<std::string, std::shared_ptr<VSIMemFile>>::iterator it = |
1073 | 0 | oFileList.find(osOldPath); |
1074 | 0 | while (it != oFileList.end() && it->first.find(osOldPath) == 0) |
1075 | 0 | { |
1076 | 0 | const std::string osRemainder = it->first.substr(osOldPath.size()); |
1077 | 0 | if (osRemainder.empty() || osRemainder[0] == '/') |
1078 | 0 | { |
1079 | 0 | const std::string osNewFullPath = osNewPath + osRemainder; |
1080 | 0 | Unlink_unlocked(osNewFullPath.c_str()); |
1081 | 0 | oFileList[osNewFullPath] = it->second; |
1082 | 0 | it->second->osFilename = osNewFullPath; |
1083 | 0 | oFileList.erase(it++); |
1084 | 0 | } |
1085 | 0 | else |
1086 | 0 | { |
1087 | 0 | ++it; |
1088 | 0 | } |
1089 | 0 | } |
1090 | |
|
1091 | 0 | return 0; |
1092 | 0 | } |
1093 | | |
1094 | | /************************************************************************/ |
1095 | | /* NormalizePath() */ |
1096 | | /************************************************************************/ |
1097 | | |
1098 | | std::string VSIMemFilesystemHandler::NormalizePath(const std::string &in) |
1099 | 24.1k | { |
1100 | 24.1k | CPLString s(in); |
1101 | 24.1k | std::replace(s.begin(), s.end(), '\\', '/'); |
1102 | 24.1k | s.replaceAll("//", '/'); |
1103 | 24.1k | if (!s.empty() && s.back() == '/') |
1104 | 219 | s.pop_back(); |
1105 | | #if __GNUC__ >= 13 |
1106 | | // gcc 13 complains about below explicit std::move() |
1107 | | return s; |
1108 | | #else |
1109 | | // Android NDK (and probably other compilers) warn about |
1110 | | // "warning: local variable 's' will be copied despite being returned by name [-Wreturn-std-move]" |
1111 | | // if not specifying std::move() |
1112 | 24.1k | return std::move(s); |
1113 | 24.1k | #endif |
1114 | 24.1k | } |
1115 | | |
1116 | | /************************************************************************/ |
1117 | | /* GetDiskFreeSpace() */ |
1118 | | /************************************************************************/ |
1119 | | |
1120 | | GIntBig VSIMemFilesystemHandler::GetDiskFreeSpace(const char * /*pszDirname*/) |
1121 | 0 | { |
1122 | 0 | const GIntBig nRet = CPLGetUsablePhysicalRAM(); |
1123 | 0 | if (nRet <= 0) |
1124 | 0 | return -1; |
1125 | 0 | return nRet; |
1126 | 0 | } |
1127 | | |
1128 | | //! @endcond |
1129 | | |
1130 | | /************************************************************************/ |
1131 | | /* VSIInstallMemFileHandler() */ |
1132 | | /************************************************************************/ |
1133 | | |
1134 | | /** |
1135 | | * \brief Install "memory" file system handler. |
1136 | | * |
1137 | | * A special file handler is installed that allows block of memory to be |
1138 | | * treated as files. All portions of the file system underneath the base |
1139 | | * path "/vsimem/" will be handled by this driver. |
1140 | | * |
1141 | | * Normal VSI*L functions can be used freely to create and destroy memory |
1142 | | * arrays treating them as if they were real file system objects. Some |
1143 | | * additional methods exist to efficient create memory file system objects |
1144 | | * without duplicating original copies of the data or to "steal" the block |
1145 | | * of memory associated with a memory file. |
1146 | | * |
1147 | | * Directory related functions are supported. |
1148 | | * |
1149 | | * This code example demonstrates using GDAL to translate from one memory |
1150 | | * buffer to another. |
1151 | | * |
1152 | | * \code |
1153 | | * GByte *ConvertBufferFormat( GByte *pabyInData, vsi_l_offset nInDataLength, |
1154 | | * vsi_l_offset *pnOutDataLength ) |
1155 | | * { |
1156 | | * // create memory file system object from buffer. |
1157 | | * VSIFCloseL( VSIFileFromMemBuffer( "/vsimem/work.dat", pabyInData, |
1158 | | * nInDataLength, FALSE ) ); |
1159 | | * |
1160 | | * // Open memory buffer for read. |
1161 | | * GDALDatasetH hDS = GDALOpen( "/vsimem/work.dat", GA_ReadOnly ); |
1162 | | * |
1163 | | * // Get output format driver. |
1164 | | * GDALDriverH hDriver = GDALGetDriverByName( "GTiff" ); |
1165 | | * GDALDatasetH hOutDS; |
1166 | | * |
1167 | | * hOutDS = GDALCreateCopy( hDriver, "/vsimem/out.tif", hDS, TRUE, NULL, |
1168 | | * NULL, NULL ); |
1169 | | * |
1170 | | * // close source file, and "unlink" it. |
1171 | | * GDALClose( hDS ); |
1172 | | * VSIUnlink( "/vsimem/work.dat" ); |
1173 | | * |
1174 | | * // seize the buffer associated with the output file. |
1175 | | * |
1176 | | * return VSIGetMemFileBuffer( "/vsimem/out.tif", pnOutDataLength, TRUE ); |
1177 | | * } |
1178 | | * \endcode |
1179 | | */ |
1180 | | |
1181 | | void VSIInstallMemFileHandler() |
1182 | 3 | { |
1183 | 3 | VSIFileManager::InstallHandler("/vsimem/", |
1184 | 3 | new VSIMemFilesystemHandler("/vsimem/")); |
1185 | 3 | } |
1186 | | |
1187 | | /************************************************************************/ |
1188 | | /* VSIFileFromMemBuffer() */ |
1189 | | /************************************************************************/ |
1190 | | |
1191 | | /** |
1192 | | * \brief Create memory "file" from a buffer. |
1193 | | * |
1194 | | * A virtual memory file is created from the passed buffer with the indicated |
1195 | | * filename. Under normal conditions the filename would need to be absolute |
1196 | | * and within the /vsimem/ portion of the filesystem. |
1197 | | * Starting with GDAL 3.6, nullptr can also be passed as pszFilename to mean |
1198 | | * an anonymous file, that is destroyed when the handle is closed. |
1199 | | * |
1200 | | * If bTakeOwnership is TRUE, then the memory file system handler will take |
1201 | | * ownership of the buffer, freeing it when the file is deleted. Otherwise |
1202 | | * it remains the responsibility of the caller, but should not be freed as |
1203 | | * long as it might be accessed as a file. In no circumstances does this |
1204 | | * function take a copy of the pabyData contents. |
1205 | | * |
1206 | | * @param pszFilename the filename to be created, or nullptr |
1207 | | * @param pabyData the data buffer for the file. |
1208 | | * @param nDataLength the length of buffer in bytes. |
1209 | | * @param bTakeOwnership TRUE to transfer "ownership" of buffer or FALSE. |
1210 | | * |
1211 | | * @return open file handle on created file (see VSIFOpenL()). |
1212 | | */ |
1213 | | |
1214 | | VSILFILE *VSIFileFromMemBuffer(const char *pszFilename, GByte *pabyData, |
1215 | | vsi_l_offset nDataLength, int bTakeOwnership) |
1216 | | |
1217 | 5.68k | { |
1218 | 5.68k | if (VSIFileManager::GetHandler("") == |
1219 | 5.68k | VSIFileManager::GetHandler("/vsimem/")) |
1220 | 0 | VSIInstallMemFileHandler(); |
1221 | | |
1222 | 5.68k | VSIMemFilesystemHandler *poHandler = static_cast<VSIMemFilesystemHandler *>( |
1223 | 5.68k | VSIFileManager::GetHandler("/vsimem/")); |
1224 | | |
1225 | 5.68k | const CPLString osFilename = |
1226 | 5.68k | pszFilename ? VSIMemFilesystemHandler::NormalizePath(pszFilename) |
1227 | 5.68k | : std::string(); |
1228 | 5.68k | if (osFilename == "/vsimem/") |
1229 | 0 | { |
1230 | 0 | CPLDebug("VSIMEM", "VSIFileFromMemBuffer(): illegal filename: %s", |
1231 | 0 | pszFilename); |
1232 | 0 | return nullptr; |
1233 | 0 | } |
1234 | | |
1235 | | // Try to create the parent directory, if needed, before taking |
1236 | | // ownership of pabyData. |
1237 | 5.68k | if (!osFilename.empty()) |
1238 | 5.68k | { |
1239 | 5.68k | const std::string osFileDir = CPLGetPathSafe(osFilename.c_str()); |
1240 | 5.68k | if (VSIMkdirRecursive(osFileDir.c_str(), 0755) == -1) |
1241 | 0 | { |
1242 | 0 | VSIError(VSIE_FileError, |
1243 | 0 | "Could not create directory %s for writing", |
1244 | 0 | osFileDir.c_str()); |
1245 | 0 | errno = ENOENT; |
1246 | 0 | return nullptr; |
1247 | 0 | } |
1248 | 5.68k | } |
1249 | | |
1250 | 5.68k | std::shared_ptr<VSIMemFile> poFile = std::make_shared<VSIMemFile>(); |
1251 | | |
1252 | 5.68k | poFile->osFilename = osFilename; |
1253 | 5.68k | poFile->bOwnData = CPL_TO_BOOL(bTakeOwnership); |
1254 | 5.68k | poFile->pabyData = pabyData; |
1255 | 5.68k | poFile->nLength = nDataLength; |
1256 | 5.68k | poFile->nAllocLength = nDataLength; |
1257 | | |
1258 | 5.68k | if (!osFilename.empty()) |
1259 | 5.68k | { |
1260 | 5.68k | CPLMutexHolder oHolder(&poHandler->hMutex); |
1261 | 5.68k | poHandler->Unlink_unlocked(osFilename); |
1262 | 5.68k | poHandler->oFileList[poFile->osFilename] = poFile; |
1263 | | #ifdef DEBUG_VERBOSE |
1264 | | CPLDebug("VSIMEM", "VSIFileFromMemBuffer() %s: ref_count=%d (after)", |
1265 | | poFile->osFilename.c_str(), |
1266 | | static_cast<int>(poFile.use_count())); |
1267 | | #endif |
1268 | 5.68k | } |
1269 | | |
1270 | | /* -------------------------------------------------------------------- */ |
1271 | | /* Setup the file handle on this file. */ |
1272 | | /* -------------------------------------------------------------------- */ |
1273 | 5.68k | VSIMemHandle *poHandle = new VSIMemHandle; |
1274 | | |
1275 | 5.68k | poHandle->poFile = std::move(poFile); |
1276 | 5.68k | poHandle->bUpdate = true; |
1277 | 5.68k | poHandle->m_bReadAllowed = true; |
1278 | 5.68k | return poHandle; |
1279 | 5.68k | } |
1280 | | |
1281 | | /************************************************************************/ |
1282 | | /* VSIGetMemFileBuffer() */ |
1283 | | /************************************************************************/ |
1284 | | |
1285 | | /** |
1286 | | * \brief Fetch buffer underlying memory file. |
1287 | | * |
1288 | | * This function returns a pointer to the memory buffer underlying a |
1289 | | * virtual "in memory" file. If bUnlinkAndSeize is TRUE the filesystem |
1290 | | * object will be deleted, and ownership of the buffer will pass to the |
1291 | | * caller otherwise the underlying file will remain in existence. |
1292 | | * |
1293 | | * @param pszFilename the name of the file to grab the buffer of. |
1294 | | * @param pnDataLength (file) length returned in this variable. |
1295 | | * @param bUnlinkAndSeize TRUE to remove the file, or FALSE to leave unaltered. |
1296 | | * |
1297 | | * @return pointer to memory buffer or NULL on failure. |
1298 | | */ |
1299 | | |
1300 | | GByte *VSIGetMemFileBuffer(const char *pszFilename, vsi_l_offset *pnDataLength, |
1301 | | int bUnlinkAndSeize) |
1302 | | |
1303 | 0 | { |
1304 | 0 | VSIMemFilesystemHandler *poHandler = static_cast<VSIMemFilesystemHandler *>( |
1305 | 0 | VSIFileManager::GetHandler("/vsimem/")); |
1306 | |
|
1307 | 0 | if (pszFilename == nullptr) |
1308 | 0 | return nullptr; |
1309 | | |
1310 | 0 | const std::string osFilename = |
1311 | 0 | VSIMemFilesystemHandler::NormalizePath(pszFilename); |
1312 | |
|
1313 | 0 | CPLMutexHolder oHolder(&poHandler->hMutex); |
1314 | |
|
1315 | 0 | if (poHandler->oFileList.find(osFilename) == poHandler->oFileList.end()) |
1316 | 0 | return nullptr; |
1317 | | |
1318 | 0 | std::shared_ptr<VSIMemFile> poFile = poHandler->oFileList[osFilename]; |
1319 | 0 | GByte *pabyData = poFile->pabyData; |
1320 | 0 | if (pnDataLength != nullptr) |
1321 | 0 | *pnDataLength = poFile->nLength; |
1322 | |
|
1323 | 0 | if (bUnlinkAndSeize) |
1324 | 0 | { |
1325 | 0 | if (!poFile->bOwnData) |
1326 | 0 | CPLDebug("VSIMemFile", |
1327 | 0 | "File doesn't own data in VSIGetMemFileBuffer!"); |
1328 | 0 | else |
1329 | 0 | poFile->bOwnData = false; |
1330 | |
|
1331 | 0 | poHandler->oFileList.erase(poHandler->oFileList.find(osFilename)); |
1332 | | #ifdef DEBUG_VERBOSE |
1333 | | CPLDebug("VSIMEM", "VSIGetMemFileBuffer() %s: ref_count=%d (before)", |
1334 | | poFile->osFilename.c_str(), |
1335 | | static_cast<int>(poFile.use_count())); |
1336 | | #endif |
1337 | 0 | poFile->pabyData = nullptr; |
1338 | 0 | poFile->nLength = 0; |
1339 | 0 | poFile->nAllocLength = 0; |
1340 | 0 | } |
1341 | |
|
1342 | 0 | return pabyData; |
1343 | 0 | } |
1344 | | |
1345 | | /************************************************************************/ |
1346 | | /* VSIMemGenerateHiddenFilename() */ |
1347 | | /************************************************************************/ |
1348 | | |
1349 | | /** |
1350 | | * \brief Generates a unique filename that can be used with the /vsimem/ |
1351 | | * virtual file system. |
1352 | | * |
1353 | | * This function returns a (short-lived) string containing a unique filename, |
1354 | | * (using an atomic counter), designed for temporary files that must remain |
1355 | | * invisible for other users working at the "/vsimem/" top-level, i.e. |
1356 | | * such files are not returned by VSIReadDir("/vsimem/") or |
1357 | | * VSIReadDirRecursive("/vsimem/)". |
1358 | | * |
1359 | | * The function does not create the file per se. Such filename can be used to |
1360 | | * create a regular file with VSIFOpenL() or VSIFileFromMemBuffer(), or create |
1361 | | * a directory with VSIMkdir() |
1362 | | * |
1363 | | * Once created, deletion of those files using VSIUnlink(), VSIRmdirRecursive(), |
1364 | | * etc. is of the responsibility of the user. The user should not attempt to |
1365 | | * work with the "parent" directory returned by CPLGetPath() / CPLGetDirname() |
1366 | | * on the returned filename, and work only with files at the same level or |
1367 | | * in subdirectories of what is returned by this function. |
1368 | | * |
1369 | | * @param pszFilename the filename to be appended at the end of the returned |
1370 | | * filename. If not specified, defaults to "unnamed". |
1371 | | * |
1372 | | * @return pointer to a short-lived string (rotating buffer of strings in |
1373 | | * thread-local storage). It is recommended to use CPLStrdup() or std::string() |
1374 | | * immediately on it. |
1375 | | * |
1376 | | * @since GDAL 3.10 |
1377 | | */ |
1378 | | const char *VSIMemGenerateHiddenFilename(const char *pszFilename) |
1379 | 0 | { |
1380 | 0 | static std::atomic<uint32_t> nCounter{0}; |
1381 | 0 | return CPLSPrintf("%s/%u/%s", szHIDDEN_DIRNAME, ++nCounter, |
1382 | 0 | pszFilename ? pszFilename : "unnamed"); |
1383 | 0 | } |