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