/src/gdal/port/cpl_vsi_mem.cpp
Line | Count | Source |
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 | 1.58G | #define CPL_SHARED_LOCK std::shared_lock<std::shared_mutex> |
40 | 50.6M | #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 final |
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 | | ~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 | 10.2M | 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 | 13.5k | { |
174 | 13.5k | return true; |
175 | 13.5k | } |
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 | 86 | : m_osPrefix(pszPrefix) |
198 | 86 | { |
199 | 86 | } |
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 CPLString 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 | 1.54M | { |
239 | 1.54M | time(&mTime); |
240 | 1.54M | } |
241 | | |
242 | | /************************************************************************/ |
243 | | /* ~VSIMemFile() */ |
244 | | /************************************************************************/ |
245 | | |
246 | | VSIMemFile::~VSIMemFile() |
247 | 1.54M | { |
248 | 1.54M | if (bOwnData && pabyData) |
249 | 831k | CPLFree(pabyData); |
250 | 1.54M | } |
251 | | |
252 | | /************************************************************************/ |
253 | | /* SetLength() */ |
254 | | /************************************************************************/ |
255 | | |
256 | | // Must be called under exclusive lock |
257 | | bool VSIMemFile::SetLength(vsi_l_offset nNewLength) |
258 | | |
259 | 40.1M | { |
260 | 40.1M | if (nNewLength > nMaxLength) |
261 | 2.29k | { |
262 | 2.29k | CPLError(CE_Failure, CPLE_NotSupported, "Maximum file size reached!"); |
263 | 2.29k | return false; |
264 | 2.29k | } |
265 | | |
266 | | /* -------------------------------------------------------------------- */ |
267 | | /* Grow underlying array if needed. */ |
268 | | /* -------------------------------------------------------------------- */ |
269 | 40.1M | if (nNewLength > nAllocLength) |
270 | 947k | { |
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 | 947k | 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 | 947k | const vsi_l_offset nNewAlloc = |
287 | 947k | (nAllocLength == 0 && nNewLength >= 1024 * 1024) |
288 | 947k | ? nNewLength |
289 | 947k | : nNewLength + nNewLength / 10 + 5000; |
290 | 947k | GByte *pabyNewData = nullptr; |
291 | 947k | if (static_cast<vsi_l_offset>(static_cast<size_t>(nNewAlloc)) == |
292 | 947k | nNewAlloc) |
293 | 947k | { |
294 | 947k | pabyNewData = static_cast<GByte *>( |
295 | 947k | nAllocLength == 0 |
296 | 947k | ? VSICalloc(1, static_cast<size_t>(nNewAlloc)) |
297 | 947k | : VSIRealloc(pabyData, static_cast<size_t>(nNewAlloc))); |
298 | 947k | } |
299 | 947k | 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 | 947k | if (nAllocLength > 0) |
309 | 283k | { |
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 | 283k | memset(pabyNewData + nAllocLength, 0, |
314 | 283k | static_cast<size_t>(nNewAlloc - nAllocLength)); |
315 | 283k | } |
316 | | |
317 | 947k | pabyData = pabyNewData; |
318 | 947k | nAllocLength = nNewAlloc; |
319 | 947k | } |
320 | 39.2M | else if (nNewLength < nLength) |
321 | 539k | { |
322 | 539k | memset(pabyData + nNewLength, 0, |
323 | 539k | static_cast<size_t>(nLength - nNewLength)); |
324 | 539k | } |
325 | | |
326 | 40.1M | nLength = nNewLength; |
327 | 40.1M | time(&mTime); |
328 | | |
329 | 40.1M | return true; |
330 | 40.1M | } |
331 | | |
332 | | /************************************************************************/ |
333 | | /* ==================================================================== */ |
334 | | /* VSIMemHandle */ |
335 | | /* ==================================================================== */ |
336 | | /************************************************************************/ |
337 | | |
338 | | /************************************************************************/ |
339 | | /* ~VSIMemHandle() */ |
340 | | /************************************************************************/ |
341 | | |
342 | | VSIMemHandle::~VSIMemHandle() |
343 | 10.2M | { |
344 | 10.2M | VSIMemHandle::Close(); |
345 | 10.2M | } |
346 | | |
347 | | /************************************************************************/ |
348 | | /* Close() */ |
349 | | /************************************************************************/ |
350 | | |
351 | | int VSIMemHandle::Close() |
352 | | |
353 | 20.5M | { |
354 | 20.5M | if (poFile) |
355 | 10.2M | { |
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 | 10.2M | poFile = nullptr; |
362 | 10.2M | } |
363 | | |
364 | 20.5M | return 0; |
365 | 20.5M | } |
366 | | |
367 | | /************************************************************************/ |
368 | | /* Seek() */ |
369 | | /************************************************************************/ |
370 | | |
371 | | int VSIMemHandle::Seek(vsi_l_offset nOffset, int nWhence) |
372 | | |
373 | 468M | { |
374 | 468M | vsi_l_offset nLength; |
375 | 468M | { |
376 | 468M | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
377 | 468M | nLength = poFile->nLength; |
378 | 468M | } |
379 | | |
380 | 468M | if (nWhence == SEEK_CUR) |
381 | 6.45M | { |
382 | 6.45M | if (nOffset > INT_MAX) |
383 | 1.63k | { |
384 | | // printf("likely negative offset intended\n"); |
385 | 1.63k | } |
386 | 6.45M | m_nOffset += nOffset; |
387 | 6.45M | } |
388 | 461M | else if (nWhence == SEEK_SET) |
389 | 455M | { |
390 | 455M | m_nOffset = nOffset; |
391 | 455M | } |
392 | 5.77M | else if (nWhence == SEEK_END) |
393 | 5.77M | { |
394 | 5.77M | m_nOffset = nLength + nOffset; |
395 | 5.77M | } |
396 | 0 | else |
397 | 0 | { |
398 | 0 | errno = EINVAL; |
399 | 0 | return -1; |
400 | 0 | } |
401 | | |
402 | 468M | bEOF = false; |
403 | | |
404 | 468M | return 0; |
405 | 468M | } |
406 | | |
407 | | /************************************************************************/ |
408 | | /* Tell() */ |
409 | | /************************************************************************/ |
410 | | |
411 | | vsi_l_offset VSIMemHandle::Tell() |
412 | | |
413 | 906M | { |
414 | 906M | return m_nOffset; |
415 | 906M | } |
416 | | |
417 | | /************************************************************************/ |
418 | | /* Read() */ |
419 | | /************************************************************************/ |
420 | | |
421 | | size_t VSIMemHandle::Read(void *pBuffer, size_t nSize, size_t nCount) |
422 | | |
423 | 1.06G | { |
424 | 1.06G | const vsi_l_offset nOffset = m_nOffset; |
425 | | |
426 | 1.06G | size_t nBytesToRead = nSize * nCount; |
427 | 1.06G | if (nBytesToRead == 0) |
428 | 2.36M | return 0; |
429 | | |
430 | 1.05G | if (nCount > 0 && nBytesToRead / nCount != nSize) |
431 | 0 | { |
432 | 0 | bEOF = true; |
433 | 0 | return 0; |
434 | 0 | } |
435 | | |
436 | 1.05G | if (!m_bReadAllowed) |
437 | 0 | { |
438 | 0 | m_bError = true; |
439 | 0 | return 0; |
440 | 0 | } |
441 | | |
442 | 1.05G | 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 | 1.05G | const auto DoUnderLock = |
446 | 1.05G | [this, nOffset, pBuffer, nSize, &nBytesToRead, &nCount, &bEOFTmp] |
447 | 1.05G | { |
448 | 1.05G | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
449 | | |
450 | 1.05G | if (poFile->nLength <= nOffset || nBytesToRead + nOffset < nBytesToRead) |
451 | 71.1M | { |
452 | 71.1M | bEOFTmp = true; |
453 | 71.1M | return false; |
454 | 71.1M | } |
455 | 986M | if (nBytesToRead + nOffset > poFile->nLength) |
456 | 2.90M | { |
457 | 2.90M | nBytesToRead = static_cast<size_t>(poFile->nLength - nOffset); |
458 | 2.90M | nCount = nBytesToRead / nSize; |
459 | 2.90M | bEOFTmp = true; |
460 | 2.90M | } |
461 | | |
462 | 986M | if (nBytesToRead) |
463 | 986M | memcpy(pBuffer, poFile->pabyData + nOffset, |
464 | 986M | static_cast<size_t>(nBytesToRead)); |
465 | 986M | return true; |
466 | 1.05G | }; |
467 | | |
468 | 1.05G | bool bRet = DoUnderLock(); |
469 | 1.05G | bEOF = bEOFTmp; |
470 | 1.05G | if (!bRet) |
471 | 71.1M | return 0; |
472 | | |
473 | 986M | m_nOffset += nBytesToRead; |
474 | | |
475 | 986M | return nCount; |
476 | 1.05G | } |
477 | | |
478 | | /************************************************************************/ |
479 | | /* PRead() */ |
480 | | /************************************************************************/ |
481 | | |
482 | | size_t VSIMemHandle::PRead(void *pBuffer, size_t nSize, |
483 | | vsi_l_offset nOffset) const |
484 | 262k | { |
485 | 262k | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
486 | | |
487 | 262k | if (nOffset < poFile->nLength) |
488 | 252k | { |
489 | 252k | const size_t nToCopy = static_cast<size_t>( |
490 | 252k | std::min(static_cast<vsi_l_offset>(poFile->nLength - nOffset), |
491 | 252k | static_cast<vsi_l_offset>(nSize))); |
492 | 252k | memcpy(pBuffer, poFile->pabyData + static_cast<size_t>(nOffset), |
493 | 252k | nToCopy); |
494 | 252k | return nToCopy; |
495 | 252k | } |
496 | 9.40k | return 0; |
497 | 262k | } |
498 | | |
499 | | /************************************************************************/ |
500 | | /* Write() */ |
501 | | /************************************************************************/ |
502 | | |
503 | | size_t VSIMemHandle::Write(const void *pBuffer, size_t nSize, size_t nCount) |
504 | | |
505 | 50.1M | { |
506 | 50.1M | const vsi_l_offset nOffset = m_nOffset; |
507 | | |
508 | 50.1M | if (!bUpdate) |
509 | 0 | { |
510 | 0 | errno = EACCES; |
511 | 0 | return 0; |
512 | 0 | } |
513 | | |
514 | 50.1M | const size_t nBytesToWrite = nSize * nCount; |
515 | | |
516 | 50.1M | { |
517 | 50.1M | CPL_EXCLUSIVE_LOCK oLock(poFile->m_oMutex); |
518 | | |
519 | 50.1M | if (nCount > 0 && nBytesToWrite / nCount != nSize) |
520 | 0 | { |
521 | 0 | return 0; |
522 | 0 | } |
523 | 50.1M | if (nBytesToWrite + nOffset < nBytesToWrite) |
524 | 0 | { |
525 | 0 | return 0; |
526 | 0 | } |
527 | | |
528 | 50.1M | if (nBytesToWrite + nOffset > poFile->nLength) |
529 | 39.6M | { |
530 | 39.6M | if (!poFile->SetLength(nBytesToWrite + nOffset)) |
531 | 2.29k | return 0; |
532 | 39.6M | } |
533 | | |
534 | 50.0M | if (nBytesToWrite) |
535 | 49.9M | memcpy(poFile->pabyData + nOffset, pBuffer, nBytesToWrite); |
536 | | |
537 | 50.0M | time(&poFile->mTime); |
538 | 50.0M | } |
539 | | |
540 | 0 | m_nOffset += nBytesToWrite; |
541 | | |
542 | 50.0M | return nCount; |
543 | 50.1M | } |
544 | | |
545 | | /************************************************************************/ |
546 | | /* ClearErr() */ |
547 | | /************************************************************************/ |
548 | | |
549 | | void VSIMemHandle::ClearErr() |
550 | | |
551 | 499 | { |
552 | 499 | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
553 | 499 | bEOF = false; |
554 | 499 | m_bError = false; |
555 | 499 | } |
556 | | |
557 | | /************************************************************************/ |
558 | | /* Error() */ |
559 | | /************************************************************************/ |
560 | | |
561 | | int VSIMemHandle::Error() |
562 | | |
563 | 3.68k | { |
564 | 3.68k | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
565 | 3.68k | return m_bError ? TRUE : FALSE; |
566 | 3.68k | } |
567 | | |
568 | | /************************************************************************/ |
569 | | /* Eof() */ |
570 | | /************************************************************************/ |
571 | | |
572 | | int VSIMemHandle::Eof() |
573 | | |
574 | 51.7M | { |
575 | 51.7M | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
576 | 51.7M | return bEOF ? TRUE : FALSE; |
577 | 51.7M | } |
578 | | |
579 | | /************************************************************************/ |
580 | | /* Truncate() */ |
581 | | /************************************************************************/ |
582 | | |
583 | | int VSIMemHandle::Truncate(vsi_l_offset nNewSize) |
584 | 8.03k | { |
585 | 8.03k | if (!bUpdate) |
586 | 0 | { |
587 | 0 | errno = EACCES; |
588 | 0 | return -1; |
589 | 0 | } |
590 | | |
591 | 8.03k | CPL_EXCLUSIVE_LOCK oLock(poFile->m_oMutex); |
592 | 8.03k | if (poFile->SetLength(nNewSize)) |
593 | 8.03k | return 0; |
594 | | |
595 | 0 | return -1; |
596 | 8.03k | } |
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 | 11.3M | { |
627 | 11.3M | CPLMutexHolder oHolder(&hMutex); |
628 | 11.3M | const CPLString osFilename = NormalizePath(pszFilename); |
629 | 11.3M | if (osFilename.empty()) |
630 | 0 | return nullptr; |
631 | | |
632 | 11.3M | vsi_l_offset nMaxLength = GUINTBIG_MAX; |
633 | 11.3M | const size_t iPos = osFilename.find("||maxlength="); |
634 | 11.3M | if (iPos != std::string::npos) |
635 | 2.55k | { |
636 | 2.55k | nMaxLength = static_cast<vsi_l_offset>(CPLAtoGIntBig( |
637 | 2.55k | osFilename.substr(iPos + strlen("||maxlength=")).c_str())); |
638 | 2.55k | } |
639 | | |
640 | | /* -------------------------------------------------------------------- */ |
641 | | /* Get the filename we are opening, create if needed. */ |
642 | | /* -------------------------------------------------------------------- */ |
643 | 11.3M | std::shared_ptr<VSIMemFile> poFile = nullptr; |
644 | 11.3M | const auto oIter = oFileList.find(osFilename); |
645 | 11.3M | if (oIter != oFileList.end()) |
646 | 8.78M | { |
647 | 8.78M | poFile = oIter->second; |
648 | 8.78M | } |
649 | | |
650 | | // If no file and opening in read, error out. |
651 | 11.3M | if (strstr(pszAccess, "w") == nullptr && |
652 | 10.1M | strstr(pszAccess, "a") == nullptr && poFile == nullptr) |
653 | 1.86M | { |
654 | 1.86M | if (bSetError) |
655 | 27.2k | { |
656 | 27.2k | VSIError(VSIE_FileError, "No such file or directory"); |
657 | 27.2k | } |
658 | 1.86M | errno = ENOENT; |
659 | 1.86M | return nullptr; |
660 | 1.86M | } |
661 | | |
662 | | // Create. |
663 | 9.45M | if (poFile == nullptr) |
664 | 666k | { |
665 | 666k | const std::string osFileDir = CPLGetPathSafe(osFilename.c_str()); |
666 | 666k | if (VSIMkdirRecursive(osFileDir.c_str(), 0755) == -1) |
667 | 139 | { |
668 | 139 | if (bSetError) |
669 | 38 | { |
670 | 38 | VSIError(VSIE_FileError, |
671 | 38 | "Could not create directory %s for writing", |
672 | 38 | osFileDir.c_str()); |
673 | 38 | } |
674 | 139 | errno = ENOENT; |
675 | 139 | return nullptr; |
676 | 139 | } |
677 | | |
678 | 666k | poFile = std::make_shared<VSIMemFile>(); |
679 | 666k | poFile->osFilename = osFilename; |
680 | 666k | 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 | 666k | poFile->nMaxLength = nMaxLength; |
686 | 666k | } |
687 | | // Overwrite |
688 | 8.78M | else if (strstr(pszAccess, "w")) |
689 | 538k | { |
690 | 538k | CPL_EXCLUSIVE_LOCK oLock(poFile->m_oMutex); |
691 | 538k | poFile->SetLength(0); |
692 | 538k | poFile->nMaxLength = nMaxLength; |
693 | 538k | } |
694 | | |
695 | 9.45M | if (poFile->bIsDirectory) |
696 | 3.15k | { |
697 | 3.15k | errno = EISDIR; |
698 | 3.15k | return nullptr; |
699 | 3.15k | } |
700 | | |
701 | | /* -------------------------------------------------------------------- */ |
702 | | /* Setup the file handle on this file. */ |
703 | | /* -------------------------------------------------------------------- */ |
704 | 9.45M | auto poHandle = std::make_unique<VSIMemHandle>(); |
705 | | |
706 | 9.45M | poHandle->poFile = poFile; |
707 | 9.45M | poHandle->m_nOffset = 0; |
708 | 9.45M | poHandle->bEOF = false; |
709 | 9.45M | poHandle->bUpdate = strchr(pszAccess, 'w') || strchr(pszAccess, '+') || |
710 | 8.20M | strchr(pszAccess, 'a'); |
711 | 9.45M | 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.get(), |
715 | | pszFilename, static_cast<int>(poFile.use_count())); |
716 | | #endif |
717 | 9.45M | 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 | 9.45M | return VSIVirtualHandleUniquePtr(poHandle.release()); |
728 | 9.45M | } |
729 | | |
730 | | /************************************************************************/ |
731 | | /* Stat() */ |
732 | | /************************************************************************/ |
733 | | |
734 | | int VSIMemFilesystemHandler::Stat(const char *pszFilename, |
735 | | VSIStatBufL *pStatBuf, int /* nFlags */) |
736 | | |
737 | 14.1M | { |
738 | 14.1M | CPLMutexHolder oHolder(&hMutex); |
739 | | |
740 | 14.1M | const CPLString osFilename = NormalizePath(pszFilename); |
741 | | |
742 | 14.1M | memset(pStatBuf, 0, sizeof(VSIStatBufL)); |
743 | | |
744 | 14.1M | if (osFilename == m_osPrefix || osFilename + '/' == m_osPrefix) |
745 | 1.34M | { |
746 | 1.34M | pStatBuf->st_size = 0; |
747 | 1.34M | pStatBuf->st_mode = S_IFDIR; |
748 | 1.34M | return 0; |
749 | 1.34M | } |
750 | | |
751 | 12.7M | auto oIter = oFileList.find(osFilename); |
752 | 12.7M | if (oIter == oFileList.end()) |
753 | 3.77M | { |
754 | 3.77M | errno = ENOENT; |
755 | 3.77M | return -1; |
756 | 3.77M | } |
757 | | |
758 | 9.01M | std::shared_ptr<VSIMemFile> poFile = oIter->second; |
759 | | |
760 | 9.01M | memset(pStatBuf, 0, sizeof(VSIStatBufL)); |
761 | | |
762 | 9.01M | CPL_SHARED_LOCK oLock(poFile->m_oMutex); |
763 | 9.01M | if (poFile->bIsDirectory) |
764 | 77.5k | { |
765 | 77.5k | pStatBuf->st_size = 0; |
766 | 77.5k | pStatBuf->st_mode = S_IFDIR; |
767 | 77.5k | } |
768 | 8.93M | else |
769 | 8.93M | { |
770 | 8.93M | pStatBuf->st_size = poFile->nLength; |
771 | 8.93M | pStatBuf->st_mode = S_IFREG; |
772 | 8.93M | if (const char *mtime = |
773 | 8.93M | 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 | 8.93M | else |
779 | 8.93M | { |
780 | 8.93M | pStatBuf->st_mtime = poFile->mTime; |
781 | 8.93M | } |
782 | 8.93M | } |
783 | | |
784 | 9.01M | return 0; |
785 | 12.7M | } |
786 | | |
787 | | /************************************************************************/ |
788 | | /* Unlink() */ |
789 | | /************************************************************************/ |
790 | | |
791 | | int VSIMemFilesystemHandler::Unlink(const char *pszFilename) |
792 | | |
793 | 1.35M | { |
794 | 1.35M | CPLMutexHolder oHolder(&hMutex); |
795 | 1.35M | return Unlink_unlocked(pszFilename); |
796 | 1.35M | } |
797 | | |
798 | | /************************************************************************/ |
799 | | /* Unlink_unlocked() */ |
800 | | /************************************************************************/ |
801 | | |
802 | | int VSIMemFilesystemHandler::Unlink_unlocked(const char *pszFilename) |
803 | | |
804 | 2.10M | { |
805 | 2.10M | const CPLString osFilename = NormalizePath(pszFilename); |
806 | | |
807 | 2.10M | auto oIter = oFileList.find(osFilename); |
808 | 2.10M | if (oIter == oFileList.end()) |
809 | 761k | { |
810 | 761k | errno = ENOENT; |
811 | 761k | return -1; |
812 | 761k | } |
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 | 1.34M | oFileList.erase(oIter); |
820 | | |
821 | 1.34M | return 0; |
822 | 2.10M | } |
823 | | |
824 | | /************************************************************************/ |
825 | | /* Mkdir() */ |
826 | | /************************************************************************/ |
827 | | |
828 | | int VSIMemFilesystemHandler::Mkdir(const char *pszPathname, long /* nMode */) |
829 | | |
830 | 1.25M | { |
831 | 1.25M | CPLMutexHolder oHolder(&hMutex); |
832 | | |
833 | 1.25M | const CPLString osPathname = NormalizePath(pszPathname); |
834 | 1.25M | if (STARTS_WITH(osPathname.c_str(), szHIDDEN_DIRNAME)) |
835 | 1.14M | { |
836 | 1.14M | if (osPathname.size() == strlen(szHIDDEN_DIRNAME)) |
837 | 572k | return 0; |
838 | | // "/vsimem/.#!HIDDEN!#./{unique_counter}" |
839 | 574k | else if (osPathname.find('/', strlen(szHIDDEN_DIRNAME) + 1) == |
840 | 574k | std::string::npos) |
841 | 573k | return 0; |
842 | | |
843 | | // If "/vsimem/.#!HIDDEN!#./{unique_counter}/user_directory", then |
844 | | // accept creating an explicit directory |
845 | 1.14M | } |
846 | | |
847 | 108k | if (oFileList.find(osPathname) != oFileList.end()) |
848 | 284 | { |
849 | 284 | errno = EEXIST; |
850 | 284 | return -1; |
851 | 284 | } |
852 | | |
853 | 107k | std::shared_ptr<VSIMemFile> poFile = std::make_shared<VSIMemFile>(); |
854 | 107k | poFile->osFilename = osPathname; |
855 | 107k | poFile->bIsDirectory = true; |
856 | 107k | 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 | 107k | CPL_IGNORE_RET_VAL(poFile); |
862 | 107k | return 0; |
863 | 108k | } |
864 | | |
865 | | /************************************************************************/ |
866 | | /* Rmdir() */ |
867 | | /************************************************************************/ |
868 | | |
869 | | int VSIMemFilesystemHandler::Rmdir(const char *pszPathname) |
870 | | |
871 | 1.61k | { |
872 | 1.61k | return Unlink(pszPathname); |
873 | 1.61k | } |
874 | | |
875 | | /************************************************************************/ |
876 | | /* RmdirRecursive() */ |
877 | | /************************************************************************/ |
878 | | |
879 | | int VSIMemFilesystemHandler::RmdirRecursive(const char *pszDirname) |
880 | 31.2k | { |
881 | 31.2k | CPLMutexHolder oHolder(&hMutex); |
882 | | |
883 | 31.2k | const CPLString osPath = NormalizePath(pszDirname); |
884 | 31.2k | const size_t nPathLen = osPath.size(); |
885 | 31.2k | int ret = 0; |
886 | 31.2k | if (osPath == "/vsimem") |
887 | 26.8k | { |
888 | | // Clean-up all files under pszDirname, except hidden directories |
889 | | // if called from "/vsimem" |
890 | 5.16M | for (auto iter = oFileList.begin(); iter != oFileList.end(); |
891 | 26.8k | /* no automatic increment */) |
892 | 5.14M | { |
893 | 5.14M | const char *pszFilePath = iter->second->osFilename.c_str(); |
894 | 5.14M | const size_t nFileLen = iter->second->osFilename.size(); |
895 | 5.14M | if (nFileLen > nPathLen && |
896 | 5.14M | memcmp(pszFilePath, osPath.c_str(), nPathLen) == 0 && |
897 | 5.14M | pszFilePath[nPathLen] == '/' && |
898 | 5.14M | !STARTS_WITH(pszFilePath, szHIDDEN_DIRNAME)) |
899 | 178k | { |
900 | 178k | iter = oFileList.erase(iter); |
901 | 178k | } |
902 | 4.96M | else |
903 | 4.96M | { |
904 | 4.96M | ++iter; |
905 | 4.96M | } |
906 | 5.14M | } |
907 | 26.8k | } |
908 | 4.39k | else |
909 | 4.39k | { |
910 | 4.39k | ret = -1; |
911 | 13.1k | for (auto iter = oFileList.begin(); iter != oFileList.end(); |
912 | 4.39k | /* no automatic increment */) |
913 | 8.71k | { |
914 | 8.71k | const char *pszFilePath = iter->second->osFilename.c_str(); |
915 | 8.71k | const size_t nFileLen = iter->second->osFilename.size(); |
916 | 8.71k | if (nFileLen >= nPathLen && |
917 | 5.39k | memcmp(pszFilePath, osPath.c_str(), nPathLen) == 0 && |
918 | 841 | (nFileLen == nPathLen || pszFilePath[nPathLen] == '/')) |
919 | 841 | { |
920 | | // If VSIRmdirRecursive() is used correctly, it should at |
921 | | // least delete the directory on which it has been called |
922 | 841 | ret = 0; |
923 | 841 | iter = oFileList.erase(iter); |
924 | 841 | } |
925 | 7.87k | else |
926 | 7.87k | { |
927 | 7.87k | ++iter; |
928 | 7.87k | } |
929 | 8.71k | } |
930 | | |
931 | | // Make sure that it always succeed on the root hidden directory |
932 | 4.39k | if (osPath == szHIDDEN_DIRNAME) |
933 | 0 | ret = 0; |
934 | 4.39k | } |
935 | 31.2k | return ret; |
936 | 31.2k | } |
937 | | |
938 | | /************************************************************************/ |
939 | | /* ReadDirEx() */ |
940 | | /************************************************************************/ |
941 | | |
942 | | char **VSIMemFilesystemHandler::ReadDirEx(const char *pszPath, int nMaxFiles) |
943 | | |
944 | 129k | { |
945 | 129k | CPLMutexHolder oHolder(&hMutex); |
946 | | |
947 | 129k | const CPLString osPath = NormalizePath(pszPath); |
948 | | |
949 | 129k | char **papszDir = nullptr; |
950 | 129k | 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 | 129k | int nItems = 0; |
955 | 129k | int nAllocatedItems = 0; |
956 | | |
957 | 129k | if (osPath == szHIDDEN_DIRNAME) |
958 | 6 | { |
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 | 6 | std::set<std::string> oSetSubDirs; |
964 | 6 | for (const auto &iter : oFileList) |
965 | 2.93k | { |
966 | 2.93k | const char *pszFilePath = iter.second->osFilename.c_str(); |
967 | 2.93k | if (iter.second->osFilename.size() > nPathLen && |
968 | 2.93k | memcmp(pszFilePath, osPath.c_str(), nPathLen) == 0) |
969 | 2.93k | { |
970 | 2.93k | char *pszItem = CPLStrdup(pszFilePath + nPathLen + 1); |
971 | 2.93k | char *pszSlash = strchr(pszItem, '/'); |
972 | 2.93k | if (pszSlash) |
973 | 2.91k | *pszSlash = 0; |
974 | 2.93k | if (cpl::contains(oSetSubDirs, pszItem)) |
975 | 2.88k | { |
976 | 2.88k | CPLFree(pszItem); |
977 | 2.88k | continue; |
978 | 2.88k | } |
979 | 42 | oSetSubDirs.insert(pszItem); |
980 | | |
981 | 42 | if (nItems == 0) |
982 | 6 | { |
983 | 6 | papszDir = |
984 | 6 | static_cast<char **>(CPLCalloc(2, sizeof(char *))); |
985 | 6 | nAllocatedItems = 1; |
986 | 6 | } |
987 | 36 | else if (nItems >= nAllocatedItems) |
988 | 17 | { |
989 | 17 | nAllocatedItems = nAllocatedItems * 2; |
990 | 17 | papszDir = static_cast<char **>(CPLRealloc( |
991 | 17 | papszDir, (nAllocatedItems + 2) * sizeof(char *))); |
992 | 17 | } |
993 | | |
994 | 42 | papszDir[nItems] = pszItem; |
995 | 42 | papszDir[nItems + 1] = nullptr; |
996 | | |
997 | 42 | nItems++; |
998 | 42 | if (nMaxFiles > 0 && nItems > nMaxFiles) |
999 | 0 | break; |
1000 | 42 | } |
1001 | 2.93k | } |
1002 | 6 | } |
1003 | 129k | else |
1004 | 129k | { |
1005 | 129k | for (const auto &iter : oFileList) |
1006 | 4.06M | { |
1007 | 4.06M | const char *pszFilePath = iter.second->osFilename.c_str(); |
1008 | 4.06M | if (iter.second->osFilename.size() > nPathLen && |
1009 | 3.73M | memcmp(pszFilePath, osPath.c_str(), nPathLen) == 0 && |
1010 | 3.02M | pszFilePath[nPathLen] == '/' && |
1011 | 2.96M | strstr(pszFilePath + nPathLen + 1, "/") == nullptr) |
1012 | 311k | { |
1013 | 311k | if (nItems == 0) |
1014 | 127k | { |
1015 | 127k | papszDir = |
1016 | 127k | static_cast<char **>(CPLCalloc(2, sizeof(char *))); |
1017 | 127k | nAllocatedItems = 1; |
1018 | 127k | } |
1019 | 184k | else if (nItems >= nAllocatedItems) |
1020 | 50.5k | { |
1021 | 50.5k | nAllocatedItems = nAllocatedItems * 2; |
1022 | 50.5k | papszDir = static_cast<char **>(CPLRealloc( |
1023 | 50.5k | papszDir, (nAllocatedItems + 2) * sizeof(char *))); |
1024 | 50.5k | } |
1025 | | |
1026 | 311k | papszDir[nItems] = CPLStrdup(pszFilePath + nPathLen + 1); |
1027 | 311k | papszDir[nItems + 1] = nullptr; |
1028 | | |
1029 | 311k | nItems++; |
1030 | 311k | if (nMaxFiles > 0 && nItems > nMaxFiles) |
1031 | 0 | break; |
1032 | 311k | } |
1033 | 4.06M | } |
1034 | 129k | } |
1035 | | |
1036 | 129k | return papszDir; |
1037 | 129k | } |
1038 | | |
1039 | | /************************************************************************/ |
1040 | | /* Rename() */ |
1041 | | /************************************************************************/ |
1042 | | |
1043 | | int VSIMemFilesystemHandler::Rename(const char *pszOldPath, |
1044 | | const char *pszNewPath, GDALProgressFunc, |
1045 | | void *) |
1046 | | |
1047 | 2.06k | { |
1048 | 2.06k | CPLMutexHolder oHolder(&hMutex); |
1049 | | |
1050 | 2.06k | const std::string osOldPath = NormalizePath(pszOldPath); |
1051 | 2.06k | const std::string osNewPath = NormalizePath(pszNewPath); |
1052 | 2.06k | if (!STARTS_WITH(pszNewPath, m_osPrefix.c_str())) |
1053 | 0 | return -1; |
1054 | | |
1055 | 2.06k | if (osOldPath.compare(osNewPath) == 0) |
1056 | 0 | return 0; |
1057 | | |
1058 | 2.06k | if (oFileList.find(osOldPath) == oFileList.end()) |
1059 | 0 | { |
1060 | 0 | errno = ENOENT; |
1061 | 0 | return -1; |
1062 | 0 | } |
1063 | | |
1064 | 2.06k | std::map<std::string, std::shared_ptr<VSIMemFile>>::iterator it = |
1065 | 2.06k | oFileList.find(osOldPath); |
1066 | 5.00k | while (it != oFileList.end() && it->first.find(osOldPath) == 0) |
1067 | 2.94k | { |
1068 | 2.94k | const std::string osRemainder = it->first.substr(osOldPath.size()); |
1069 | 2.94k | if (osRemainder.empty() || osRemainder[0] == '/') |
1070 | 2.06k | { |
1071 | 2.06k | const std::string osNewFullPath = osNewPath + osRemainder; |
1072 | 2.06k | Unlink_unlocked(osNewFullPath.c_str()); |
1073 | 2.06k | oFileList[osNewFullPath] = it->second; |
1074 | 2.06k | it->second->osFilename = osNewFullPath; |
1075 | 2.06k | oFileList.erase(it++); |
1076 | 2.06k | } |
1077 | 879 | else |
1078 | 879 | { |
1079 | 879 | ++it; |
1080 | 879 | } |
1081 | 2.94k | } |
1082 | | |
1083 | 2.06k | return 0; |
1084 | 2.06k | } |
1085 | | |
1086 | | /************************************************************************/ |
1087 | | /* NormalizePath() */ |
1088 | | /************************************************************************/ |
1089 | | |
1090 | | CPLString VSIMemFilesystemHandler::NormalizePath(const std::string &in) |
1091 | 29.8M | { |
1092 | 29.8M | CPLString s(in); |
1093 | 29.8M | std::replace(s.begin(), s.end(), '\\', '/'); |
1094 | 29.8M | s.replaceAll("//", '/'); |
1095 | 29.8M | if (!s.empty() && s.back() == '/') |
1096 | 278k | s.pop_back(); |
1097 | 29.8M | return s; |
1098 | 29.8M | } |
1099 | | |
1100 | | /************************************************************************/ |
1101 | | /* GetDiskFreeSpace() */ |
1102 | | /************************************************************************/ |
1103 | | |
1104 | | GIntBig VSIMemFilesystemHandler::GetDiskFreeSpace(const char * /*pszDirname*/) |
1105 | 0 | { |
1106 | 0 | const GIntBig nRet = CPLGetUsablePhysicalRAM(); |
1107 | 0 | if (nRet <= 0) |
1108 | 0 | return -1; |
1109 | 0 | return nRet; |
1110 | 0 | } |
1111 | | |
1112 | | //! @endcond |
1113 | | |
1114 | | /************************************************************************/ |
1115 | | /* VSIInstallMemFileHandler() */ |
1116 | | /************************************************************************/ |
1117 | | |
1118 | | /** |
1119 | | * \brief Install "memory" file system handler. |
1120 | | * |
1121 | | * A special file handler is installed that allows block of memory to be |
1122 | | * treated as files. All portions of the file system underneath the base |
1123 | | * path "/vsimem/" will be handled by this driver. |
1124 | | * |
1125 | | * Normal VSI*L functions can be used freely to create and destroy memory |
1126 | | * arrays treating them as if they were real file system objects. Some |
1127 | | * additional methods exist to efficient create memory file system objects |
1128 | | * without duplicating original copies of the data or to "steal" the block |
1129 | | * of memory associated with a memory file. |
1130 | | * |
1131 | | * Directory related functions are supported. |
1132 | | * |
1133 | | * This code example demonstrates using GDAL to translate from one memory |
1134 | | * buffer to another. |
1135 | | * |
1136 | | * \code |
1137 | | * GByte *ConvertBufferFormat( GByte *pabyInData, vsi_l_offset nInDataLength, |
1138 | | * vsi_l_offset *pnOutDataLength ) |
1139 | | * { |
1140 | | * // create memory file system object from buffer. |
1141 | | * VSIFCloseL( VSIFileFromMemBuffer( "/vsimem/work.dat", pabyInData, |
1142 | | * nInDataLength, FALSE ) ); |
1143 | | * |
1144 | | * // Open memory buffer for read. |
1145 | | * GDALDatasetH hDS = GDALOpen( "/vsimem/work.dat", GA_ReadOnly ); |
1146 | | * |
1147 | | * // Get output format driver. |
1148 | | * GDALDriverH hDriver = GDALGetDriverByName( "GTiff" ); |
1149 | | * GDALDatasetH hOutDS; |
1150 | | * |
1151 | | * hOutDS = GDALCreateCopy( hDriver, "/vsimem/out.tif", hDS, TRUE, NULL, |
1152 | | * NULL, NULL ); |
1153 | | * |
1154 | | * // close source file, and "unlink" it. |
1155 | | * GDALClose( hDS ); |
1156 | | * VSIUnlink( "/vsimem/work.dat" ); |
1157 | | * |
1158 | | * // seize the buffer associated with the output file. |
1159 | | * |
1160 | | * return VSIGetMemFileBuffer( "/vsimem/out.tif", pnOutDataLength, TRUE ); |
1161 | | * } |
1162 | | * \endcode |
1163 | | */ |
1164 | | |
1165 | | void VSIInstallMemFileHandler() |
1166 | 86 | { |
1167 | 86 | VSIFileManager::InstallHandler("/vsimem/", |
1168 | 86 | new VSIMemFilesystemHandler("/vsimem/")); |
1169 | 86 | } |
1170 | | |
1171 | | /************************************************************************/ |
1172 | | /* VSIFileFromMemBuffer() */ |
1173 | | /************************************************************************/ |
1174 | | |
1175 | | /** |
1176 | | * \brief Create memory "file" from a buffer. |
1177 | | * |
1178 | | * A virtual memory file is created from the passed buffer with the indicated |
1179 | | * filename. Under normal conditions the filename would need to be absolute |
1180 | | * and within the /vsimem/ portion of the filesystem. |
1181 | | * Starting with GDAL 3.6, nullptr can also be passed as pszFilename to mean |
1182 | | * an anonymous file, that is destroyed when the handle is closed. |
1183 | | * |
1184 | | * If bTakeOwnership is TRUE, then the memory file system handler will take |
1185 | | * ownership of the buffer, freeing it when the file is deleted. Otherwise |
1186 | | * it remains the responsibility of the caller, but should not be freed as |
1187 | | * long as it might be accessed as a file. In no circumstances does this |
1188 | | * function take a copy of the pabyData contents. |
1189 | | * |
1190 | | * @param pszFilename the filename to be created, or nullptr |
1191 | | * @param pabyData the data buffer for the file. |
1192 | | * @param nDataLength the length of buffer in bytes. |
1193 | | * @param bTakeOwnership TRUE to transfer "ownership" of buffer or FALSE. |
1194 | | * |
1195 | | * @return open file handle on created file (see VSIFOpenL()). |
1196 | | */ |
1197 | | |
1198 | | VSILFILE *VSIFileFromMemBuffer(const char *pszFilename, GByte *pabyData, |
1199 | | vsi_l_offset nDataLength, int bTakeOwnership) |
1200 | | |
1201 | 773k | { |
1202 | 773k | if (VSIFileManager::GetHandler("") == |
1203 | 773k | VSIFileManager::GetHandler("/vsimem/")) |
1204 | 0 | VSIInstallMemFileHandler(); |
1205 | | |
1206 | 773k | VSIMemFilesystemHandler *poHandler = static_cast<VSIMemFilesystemHandler *>( |
1207 | 773k | VSIFileManager::GetHandler("/vsimem/")); |
1208 | | |
1209 | 773k | const CPLString osFilename = |
1210 | 773k | pszFilename ? VSIMemFilesystemHandler::NormalizePath(pszFilename) |
1211 | 773k | : std::string(); |
1212 | 773k | if (osFilename == "/vsimem/") |
1213 | 0 | { |
1214 | 0 | CPLDebug("VSIMEM", "VSIFileFromMemBuffer(): illegal filename: %s", |
1215 | 0 | pszFilename); |
1216 | 0 | return nullptr; |
1217 | 0 | } |
1218 | | |
1219 | | // Try to create the parent directory, if needed, before taking |
1220 | | // ownership of pabyData. |
1221 | 773k | if (!osFilename.empty()) |
1222 | 749k | { |
1223 | 749k | const std::string osFileDir = CPLGetPathSafe(osFilename.c_str()); |
1224 | 749k | if (VSIMkdirRecursive(osFileDir.c_str(), 0755) == -1) |
1225 | 60 | { |
1226 | 60 | VSIError(VSIE_FileError, |
1227 | 60 | "Could not create directory %s for writing", |
1228 | 60 | osFileDir.c_str()); |
1229 | 60 | errno = ENOENT; |
1230 | 60 | return nullptr; |
1231 | 60 | } |
1232 | 749k | } |
1233 | | |
1234 | 773k | std::shared_ptr<VSIMemFile> poFile = std::make_shared<VSIMemFile>(); |
1235 | | |
1236 | 773k | poFile->osFilename = osFilename; |
1237 | 773k | poFile->bOwnData = CPL_TO_BOOL(bTakeOwnership); |
1238 | 773k | poFile->pabyData = pabyData; |
1239 | 773k | poFile->nLength = nDataLength; |
1240 | 773k | poFile->nAllocLength = nDataLength; |
1241 | | |
1242 | 773k | if (!osFilename.empty()) |
1243 | 749k | { |
1244 | 749k | CPLMutexHolder oHolder(&poHandler->hMutex); |
1245 | 749k | poHandler->Unlink_unlocked(osFilename); |
1246 | 749k | poHandler->oFileList[poFile->osFilename] = poFile; |
1247 | | #ifdef DEBUG_VERBOSE |
1248 | | CPLDebug("VSIMEM", "VSIFileFromMemBuffer() %s: ref_count=%d (after)", |
1249 | | poFile->osFilename.c_str(), |
1250 | | static_cast<int>(poFile.use_count())); |
1251 | | #endif |
1252 | 749k | } |
1253 | | |
1254 | | /* -------------------------------------------------------------------- */ |
1255 | | /* Setup the file handle on this file. */ |
1256 | | /* -------------------------------------------------------------------- */ |
1257 | 773k | VSIMemHandle *poHandle = new VSIMemHandle; |
1258 | | |
1259 | 773k | poHandle->poFile = std::move(poFile); |
1260 | 773k | poHandle->bUpdate = true; |
1261 | 773k | poHandle->m_bReadAllowed = true; |
1262 | 773k | return poHandle; |
1263 | 773k | } |
1264 | | |
1265 | | /************************************************************************/ |
1266 | | /* VSIGetMemFileBuffer() */ |
1267 | | /************************************************************************/ |
1268 | | |
1269 | | /** |
1270 | | * \brief Fetch buffer underlying memory file. |
1271 | | * |
1272 | | * This function returns a pointer to the memory buffer underlying a |
1273 | | * virtual "in memory" file. If bUnlinkAndSeize is TRUE the filesystem |
1274 | | * object will be deleted, and ownership of the buffer will pass to the |
1275 | | * caller, otherwise the underlying file will remain in existence. |
1276 | | * bUnlinkAndSeize should only be used for files that own their data |
1277 | | * (see the bTakeOwnership parameter of VSIFileFromMemBuffer). |
1278 | | * |
1279 | | * @param pszFilename the name of the file to grab the buffer of. |
1280 | | * @param pnDataLength (file) length returned in this variable. |
1281 | | * @param bUnlinkAndSeize TRUE to remove the file, or FALSE to leave unaltered. |
1282 | | * |
1283 | | * @return pointer to memory buffer or NULL on failure. |
1284 | | */ |
1285 | | |
1286 | | GByte *VSIGetMemFileBuffer(const char *pszFilename, vsi_l_offset *pnDataLength, |
1287 | | int bUnlinkAndSeize) |
1288 | | |
1289 | 77.9k | { |
1290 | 77.9k | VSIMemFilesystemHandler *poHandler = static_cast<VSIMemFilesystemHandler *>( |
1291 | 77.9k | VSIFileManager::GetHandler("/vsimem/")); |
1292 | | |
1293 | 77.9k | if (pszFilename == nullptr) |
1294 | 0 | return nullptr; |
1295 | | |
1296 | 77.9k | const std::string osFilename = |
1297 | 77.9k | VSIMemFilesystemHandler::NormalizePath(pszFilename); |
1298 | | |
1299 | 77.9k | CPLMutexHolder oHolder(&poHandler->hMutex); |
1300 | | |
1301 | 77.9k | if (poHandler->oFileList.find(osFilename) == poHandler->oFileList.end()) |
1302 | 0 | return nullptr; |
1303 | | |
1304 | 77.9k | std::shared_ptr<VSIMemFile> poFile = poHandler->oFileList[osFilename]; |
1305 | 77.9k | GByte *pabyData = poFile->pabyData; |
1306 | 77.9k | if (pnDataLength != nullptr) |
1307 | 77.9k | *pnDataLength = poFile->nLength; |
1308 | | |
1309 | 77.9k | if (bUnlinkAndSeize) |
1310 | 0 | { |
1311 | 0 | if (!poFile->bOwnData) |
1312 | 0 | CPLDebug("VSIMemFile", |
1313 | 0 | "File doesn't own data in VSIGetMemFileBuffer!"); |
1314 | 0 | else |
1315 | 0 | poFile->bOwnData = false; |
1316 | |
|
1317 | 0 | poHandler->oFileList.erase(poHandler->oFileList.find(osFilename)); |
1318 | | #ifdef DEBUG_VERBOSE |
1319 | | CPLDebug("VSIMEM", "VSIGetMemFileBuffer() %s: ref_count=%d (before)", |
1320 | | poFile->osFilename.c_str(), |
1321 | | static_cast<int>(poFile.use_count())); |
1322 | | #endif |
1323 | 0 | poFile->pabyData = nullptr; |
1324 | 0 | poFile->nLength = 0; |
1325 | 0 | poFile->nAllocLength = 0; |
1326 | 0 | } |
1327 | | |
1328 | 77.9k | return pabyData; |
1329 | 77.9k | } |
1330 | | |
1331 | | /************************************************************************/ |
1332 | | /* VSIMemGenerateHiddenFilename() */ |
1333 | | /************************************************************************/ |
1334 | | |
1335 | | /** |
1336 | | * \brief Generates a unique filename that can be used with the /vsimem/ |
1337 | | * virtual file system. |
1338 | | * |
1339 | | * This function returns a (short-lived) string containing a unique filename, |
1340 | | * (using an atomic counter), designed for temporary files that must remain |
1341 | | * invisible for other users working at the "/vsimem/" top-level, i.e. |
1342 | | * such files are not returned by VSIReadDir("/vsimem/") or |
1343 | | * VSIReadDirRecursive("/vsimem/)". |
1344 | | * |
1345 | | * The function does not create the file per se. Such filename can be used to |
1346 | | * create a regular file with VSIFOpenL() or VSIFileFromMemBuffer(), or create |
1347 | | * a directory with VSIMkdir() |
1348 | | * |
1349 | | * Once created, deletion of those files using VSIUnlink(), VSIRmdirRecursive(), |
1350 | | * etc. is of the responsibility of the user. The user should not attempt to |
1351 | | * work with the "parent" directory returned by CPLGetPath() / CPLGetDirname() |
1352 | | * on the returned filename, and work only with files at the same level or |
1353 | | * in subdirectories of what is returned by this function. |
1354 | | * |
1355 | | * @param pszFilename the filename to be appended at the end of the returned |
1356 | | * filename. If not specified, defaults to "unnamed". |
1357 | | * |
1358 | | * @return pointer to a short-lived string (rotating buffer of strings in |
1359 | | * thread-local storage). It is recommended to use CPLStrdup() or std::string() |
1360 | | * immediately on it. |
1361 | | * |
1362 | | * @since GDAL 3.10 |
1363 | | */ |
1364 | | const char *VSIMemGenerateHiddenFilename(const char *pszFilename) |
1365 | 526k | { |
1366 | 526k | static std::atomic<uint32_t> nCounter{0}; |
1367 | 526k | return CPLSPrintf("%s/%u/%s", szHIDDEN_DIRNAME, ++nCounter, |
1368 | 526k | pszFilename ? pszFilename : "unnamed"); |
1369 | 526k | } |