/src/gdal/port/cpl_vsil_subfile.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: VSI Virtual File System |
4 | | * Purpose: Implementation of subfile virtual IO functions. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com> |
9 | | * Copyright (c) 2009-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 | | |
17 | | #include <cerrno> |
18 | | #include <cstddef> |
19 | | #include <cstring> |
20 | | #include <limits> |
21 | | |
22 | | #include "cpl_conv.h" |
23 | | #include "cpl_multiproc.h" |
24 | | #include "cpl_string.h" |
25 | | #include "cpl_vsi_virtual.h" |
26 | | |
27 | | /************************************************************************/ |
28 | | /* ==================================================================== */ |
29 | | /* VSISubFileHandle */ |
30 | | /* ==================================================================== */ |
31 | | /************************************************************************/ |
32 | | |
33 | | class VSISubFileHandle final : public VSIVirtualHandle |
34 | | { |
35 | | CPL_DISALLOW_COPY_ASSIGN(VSISubFileHandle) |
36 | | |
37 | | public: |
38 | | VSILFILE *fp = nullptr; |
39 | | vsi_l_offset nSubregionOffset = 0; |
40 | | vsi_l_offset nSubregionSize = 0; |
41 | | bool bAtEOF = false; |
42 | | bool bError = false; |
43 | | |
44 | 619 | VSISubFileHandle() = default; |
45 | | ~VSISubFileHandle() override; |
46 | | |
47 | | int Seek(vsi_l_offset nOffset, int nWhence) override; |
48 | | vsi_l_offset Tell() override; |
49 | | size_t Read(void *pBuffer, size_t nSize, size_t nMemb) override; |
50 | | size_t Write(const void *pBuffer, size_t nSize, size_t nMemb) override; |
51 | | void ClearErr() override; |
52 | | int Eof() override; |
53 | | int Error() override; |
54 | | int Close() override; |
55 | | }; |
56 | | |
57 | | /************************************************************************/ |
58 | | /* ==================================================================== */ |
59 | | /* VSISubFileFilesystemHandler */ |
60 | | /* ==================================================================== */ |
61 | | /************************************************************************/ |
62 | | |
63 | | class VSISubFileFilesystemHandler final : public VSIFilesystemHandler |
64 | | { |
65 | | CPL_DISALLOW_COPY_ASSIGN(VSISubFileFilesystemHandler) |
66 | | |
67 | | public: |
68 | 3 | VSISubFileFilesystemHandler() = default; |
69 | | ~VSISubFileFilesystemHandler() override = default; |
70 | | |
71 | | static int DecomposePath(const char *pszPath, CPLString &osFilename, |
72 | | vsi_l_offset &nSubFileOffset, |
73 | | vsi_l_offset &nSubFileSize); |
74 | | |
75 | | VSIVirtualHandleUniquePtr Open(const char *pszFilename, |
76 | | const char *pszAccess, bool bSetError, |
77 | | CSLConstList /* papszOptions */) override; |
78 | | int Stat(const char *pszFilename, VSIStatBufL *pStatBuf, |
79 | | int nFlags) override; |
80 | | int Unlink(const char *pszFilename) override; |
81 | | int Mkdir(const char *pszDirname, long nMode) override; |
82 | | int Rmdir(const char *pszDirname) override; |
83 | | char **ReadDirEx(const char *pszDirname, int nMaxFiles) override; |
84 | | }; |
85 | | |
86 | | /************************************************************************/ |
87 | | /* ==================================================================== */ |
88 | | /* VSISubFileHandle */ |
89 | | /* ==================================================================== */ |
90 | | /************************************************************************/ |
91 | | |
92 | | VSISubFileHandle::~VSISubFileHandle() |
93 | 619 | { |
94 | 619 | VSISubFileHandle::Close(); |
95 | 619 | } |
96 | | |
97 | | /************************************************************************/ |
98 | | /* Close() */ |
99 | | /************************************************************************/ |
100 | | |
101 | | int VSISubFileHandle::Close() |
102 | | |
103 | 1.04k | { |
104 | 1.04k | if (fp == nullptr) |
105 | 430 | return -1; |
106 | 619 | int nRet = VSIFCloseL(fp); |
107 | 619 | fp = nullptr; |
108 | | |
109 | 619 | return nRet; |
110 | 1.04k | } |
111 | | |
112 | | /************************************************************************/ |
113 | | /* Seek() */ |
114 | | /************************************************************************/ |
115 | | |
116 | | int VSISubFileHandle::Seek(vsi_l_offset nOffset, int nWhence) |
117 | | |
118 | 2.09k | { |
119 | 2.09k | bAtEOF = false; |
120 | | |
121 | 2.09k | if (nWhence == SEEK_SET) |
122 | 1.36k | { |
123 | 1.36k | if (nOffset > |
124 | 1.36k | std::numeric_limits<vsi_l_offset>::max() - nSubregionOffset) |
125 | 0 | return -1; |
126 | 1.36k | nOffset += nSubregionOffset; |
127 | 1.36k | } |
128 | 732 | else if (nWhence == SEEK_CUR) |
129 | 0 | { |
130 | | // handle normally. |
131 | 0 | } |
132 | 732 | else if (nWhence == SEEK_END) |
133 | 732 | { |
134 | 732 | if (nSubregionSize != 0) |
135 | 0 | { |
136 | 0 | nOffset = nSubregionOffset + nSubregionSize; |
137 | 0 | nWhence = SEEK_SET; |
138 | 0 | } |
139 | 732 | } |
140 | 0 | else |
141 | 0 | { |
142 | 0 | errno = EINVAL; |
143 | 0 | return -1; |
144 | 0 | } |
145 | | |
146 | 2.09k | return VSIFSeekL(fp, nOffset, nWhence); |
147 | 2.09k | } |
148 | | |
149 | | /************************************************************************/ |
150 | | /* Tell() */ |
151 | | /************************************************************************/ |
152 | | |
153 | | vsi_l_offset VSISubFileHandle::Tell() |
154 | | |
155 | 732 | { |
156 | 732 | vsi_l_offset nBasePos = VSIFTellL(fp); |
157 | 732 | if (nBasePos >= nSubregionOffset) |
158 | 732 | return nBasePos - nSubregionOffset; |
159 | 0 | return 0; |
160 | 732 | } |
161 | | |
162 | | /************************************************************************/ |
163 | | /* Read() */ |
164 | | /************************************************************************/ |
165 | | |
166 | | size_t VSISubFileHandle::Read(void *pBuffer, size_t nSize, size_t nCount) |
167 | | |
168 | 707 | { |
169 | 707 | size_t nRet = 0; |
170 | 707 | if (nSubregionSize == 0) |
171 | 707 | { |
172 | 707 | nRet = VSIFReadL(pBuffer, nSize, nCount, fp); |
173 | 707 | } |
174 | 0 | else |
175 | 0 | { |
176 | 0 | if (nSize == 0) |
177 | 0 | return 0; |
178 | | |
179 | 0 | const vsi_l_offset nCurOffset = VSIFTellL(fp); |
180 | 0 | if (nCurOffset >= nSubregionOffset + nSubregionSize) |
181 | 0 | { |
182 | 0 | bAtEOF = true; |
183 | 0 | return 0; |
184 | 0 | } |
185 | | |
186 | 0 | const size_t nByteToRead = nSize * nCount; |
187 | 0 | if (nCurOffset + nByteToRead > nSubregionOffset + nSubregionSize) |
188 | 0 | { |
189 | 0 | const int nRead = static_cast<int>( |
190 | 0 | VSIFReadL(pBuffer, 1, |
191 | 0 | static_cast<size_t>(nSubregionOffset + |
192 | 0 | nSubregionSize - nCurOffset), |
193 | 0 | fp)); |
194 | 0 | nRet = nRead / nSize; |
195 | 0 | } |
196 | 0 | else |
197 | 0 | { |
198 | 0 | nRet = VSIFReadL(pBuffer, nSize, nCount, fp); |
199 | 0 | } |
200 | 0 | } |
201 | | |
202 | 707 | if (nRet < nCount) |
203 | 707 | { |
204 | 707 | if (fp->Eof()) |
205 | 707 | bAtEOF = true; |
206 | 0 | else /* if (fp->Error()) */ |
207 | 0 | bError = true; |
208 | 707 | } |
209 | | |
210 | 707 | return nRet; |
211 | 707 | } |
212 | | |
213 | | /************************************************************************/ |
214 | | /* Write() */ |
215 | | /************************************************************************/ |
216 | | |
217 | | size_t VSISubFileHandle::Write(const void *pBuffer, size_t nSize, size_t nCount) |
218 | | |
219 | 0 | { |
220 | 0 | bAtEOF = false; |
221 | |
|
222 | 0 | if (nSubregionSize == 0) |
223 | 0 | return VSIFWriteL(pBuffer, nSize, nCount, fp); |
224 | | |
225 | 0 | if (nSize == 0) |
226 | 0 | return 0; |
227 | | |
228 | 0 | const vsi_l_offset nCurOffset = VSIFTellL(fp); |
229 | 0 | if (nCurOffset >= nSubregionOffset + nSubregionSize) |
230 | 0 | return 0; |
231 | | |
232 | 0 | const size_t nByteToWrite = nSize * nCount; |
233 | 0 | if (nCurOffset + nByteToWrite > nSubregionOffset + nSubregionSize) |
234 | 0 | { |
235 | 0 | const int nWritten = static_cast<int>(VSIFWriteL( |
236 | 0 | pBuffer, 1, |
237 | 0 | static_cast<size_t>(nSubregionOffset + nSubregionSize - nCurOffset), |
238 | 0 | fp)); |
239 | 0 | return nWritten / nSize; |
240 | 0 | } |
241 | | |
242 | 0 | return VSIFWriteL(pBuffer, nSize, nCount, fp); |
243 | 0 | } |
244 | | |
245 | | /************************************************************************/ |
246 | | /* ClearErr() */ |
247 | | /************************************************************************/ |
248 | | |
249 | | void VSISubFileHandle::ClearErr() |
250 | | |
251 | 0 | { |
252 | 0 | fp->ClearErr(); |
253 | 0 | bAtEOF = false; |
254 | 0 | bError = false; |
255 | 0 | } |
256 | | |
257 | | /************************************************************************/ |
258 | | /* Error() */ |
259 | | /************************************************************************/ |
260 | | |
261 | | int VSISubFileHandle::Error() |
262 | | |
263 | 0 | { |
264 | 0 | return bError; |
265 | 0 | } |
266 | | |
267 | | /************************************************************************/ |
268 | | /* Eof() */ |
269 | | /************************************************************************/ |
270 | | |
271 | | int VSISubFileHandle::Eof() |
272 | | |
273 | 532 | { |
274 | 532 | return bAtEOF; |
275 | 532 | } |
276 | | |
277 | | /************************************************************************/ |
278 | | /* ==================================================================== */ |
279 | | /* VSISubFileFilesystemHandler */ |
280 | | /* ==================================================================== */ |
281 | | /************************************************************************/ |
282 | | |
283 | | /************************************************************************/ |
284 | | /* DecomposePath() */ |
285 | | /* */ |
286 | | /* Parse a path like /vsisubfile/1000_2000,data/abc.tif into an */ |
287 | | /* offset (1000), a size (2000) and a path (data/abc.tif). */ |
288 | | /************************************************************************/ |
289 | | |
290 | | int VSISubFileFilesystemHandler::DecomposePath(const char *pszPath, |
291 | | CPLString &osFilename, |
292 | | vsi_l_offset &nSubFileOffset, |
293 | | vsi_l_offset &nSubFileSize) |
294 | | |
295 | 4.20k | { |
296 | 4.20k | if (!STARTS_WITH(pszPath, "/vsisubfile/")) |
297 | 0 | return FALSE; |
298 | | |
299 | 4.20k | osFilename = ""; |
300 | 4.20k | nSubFileOffset = 0; |
301 | 4.20k | nSubFileSize = 0; |
302 | | |
303 | 4.20k | nSubFileOffset = |
304 | 4.20k | CPLScanUIntBig(pszPath + 12, static_cast<int>(strlen(pszPath + 12))); |
305 | 230k | for (int i = 12; pszPath[i] != '\0'; i++) |
306 | 229k | { |
307 | 229k | if (pszPath[i] == '_' && nSubFileSize == 0) |
308 | 5.48k | { |
309 | | // -1 is sometimes passed to mean that we don't know the file size |
310 | | // for example when creating a JPEG2000 datastream in a NITF file |
311 | | // Transform it into 0 for correct behavior of Read(), Write() and |
312 | | // Eof(). |
313 | 5.48k | if (pszPath[i + 1] == '-') |
314 | 334 | nSubFileSize = 0; |
315 | 5.15k | else |
316 | 5.15k | nSubFileSize = CPLScanUIntBig( |
317 | 5.15k | pszPath + i + 1, static_cast<int>(strlen(pszPath + i + 1))); |
318 | 5.48k | } |
319 | 224k | else if (pszPath[i] == ',') |
320 | 3.64k | { |
321 | 3.64k | osFilename = pszPath + i + 1; |
322 | 3.64k | return TRUE; |
323 | 3.64k | } |
324 | 220k | else if (pszPath[i] == '/') |
325 | 173 | { |
326 | | // Missing comma! |
327 | 173 | return FALSE; |
328 | 173 | } |
329 | 229k | } |
330 | | |
331 | 394 | return FALSE; |
332 | 4.20k | } |
333 | | |
334 | | /************************************************************************/ |
335 | | /* Open() */ |
336 | | /************************************************************************/ |
337 | | |
338 | | VSIVirtualHandleUniquePtr |
339 | | VSISubFileFilesystemHandler::Open(const char *pszFilename, |
340 | | const char *pszAccess, bool /* bSetError */, |
341 | | CSLConstList /* papszOptions */) |
342 | | |
343 | 1.58k | { |
344 | 1.58k | if (!STARTS_WITH_CI(pszFilename, "/vsisubfile/")) |
345 | 1 | return nullptr; |
346 | | |
347 | 1.58k | CPLString osSubFilePath; |
348 | 1.58k | vsi_l_offset nOff = 0; |
349 | 1.58k | vsi_l_offset nSize = 0; |
350 | | |
351 | 1.58k | if (!DecomposePath(pszFilename, osSubFilePath, nOff, nSize)) |
352 | 311 | { |
353 | 311 | errno = ENOENT; |
354 | 311 | return nullptr; |
355 | 311 | } |
356 | 1.27k | if (nOff > std::numeric_limits<vsi_l_offset>::max() - nSize) |
357 | 1 | { |
358 | 1 | return nullptr; |
359 | 1 | } |
360 | | |
361 | | /* -------------------------------------------------------------------- */ |
362 | | /* We can't open the containing file with "w" access, so if */ |
363 | | /* that is requested use "r+" instead to update in place. */ |
364 | | /* -------------------------------------------------------------------- */ |
365 | 1.27k | if (pszAccess[0] == 'w') |
366 | 0 | pszAccess = "r+"; |
367 | | |
368 | | /* -------------------------------------------------------------------- */ |
369 | | /* Open the underlying file. */ |
370 | | /* -------------------------------------------------------------------- */ |
371 | 1.27k | auto fp = VSIFilesystemHandler::OpenStatic(osSubFilePath, pszAccess); |
372 | | |
373 | 1.27k | if (fp == nullptr) |
374 | 655 | return nullptr; |
375 | | |
376 | | /* -------------------------------------------------------------------- */ |
377 | | /* Setup the file handle on this file. */ |
378 | | /* -------------------------------------------------------------------- */ |
379 | 619 | auto poHandle = std::make_unique<VSISubFileHandle>(); |
380 | | |
381 | 619 | poHandle->fp = fp.release(); |
382 | 619 | poHandle->nSubregionOffset = nOff; |
383 | 619 | poHandle->nSubregionSize = nSize; |
384 | | |
385 | | // In read-only mode validate (offset, size) against underlying file size |
386 | 619 | if (strchr(pszAccess, 'r') != nullptr && strchr(pszAccess, '+') == nullptr) |
387 | 619 | { |
388 | 619 | if (VSIFSeekL(poHandle->fp, 0, SEEK_END) != 0) |
389 | 100 | { |
390 | 100 | return nullptr; |
391 | 100 | } |
392 | 519 | vsi_l_offset nFpSize = VSIFTellL(poHandle->fp); |
393 | | // For a directory, the size will be max(vsi_l_offset) / 2 |
394 | 519 | if (nFpSize == ~(static_cast<vsi_l_offset>(0)) / 2 || nOff > nFpSize) |
395 | 89 | { |
396 | 89 | return nullptr; |
397 | 89 | } |
398 | 430 | if (nOff + nSize > nFpSize) |
399 | 140 | { |
400 | 140 | nSize = nFpSize - nOff; |
401 | 140 | poHandle->nSubregionSize = nSize; |
402 | 140 | } |
403 | 430 | } |
404 | | |
405 | 430 | if (VSIFSeekL(poHandle->fp, nOff, SEEK_SET) != 0) |
406 | 0 | { |
407 | 0 | poHandle.reset(); |
408 | 0 | } |
409 | | |
410 | 430 | return VSIVirtualHandleUniquePtr(poHandle.release()); |
411 | 619 | } |
412 | | |
413 | | /************************************************************************/ |
414 | | /* Stat() */ |
415 | | /************************************************************************/ |
416 | | |
417 | | int VSISubFileFilesystemHandler::Stat(const char *pszFilename, |
418 | | VSIStatBufL *psStatBuf, int nFlags) |
419 | | |
420 | 2.63k | { |
421 | 2.63k | if (!STARTS_WITH_CI(pszFilename, "/vsisubfile/")) |
422 | 12 | return -1; |
423 | | |
424 | 2.62k | CPLString osSubFilePath; |
425 | 2.62k | vsi_l_offset nOff = 0; |
426 | 2.62k | vsi_l_offset nSize = 0; |
427 | | |
428 | 2.62k | memset(psStatBuf, 0, sizeof(VSIStatBufL)); |
429 | | |
430 | 2.62k | if (!DecomposePath(pszFilename, osSubFilePath, nOff, nSize)) |
431 | 256 | { |
432 | 256 | errno = ENOENT; |
433 | 256 | return -1; |
434 | 256 | } |
435 | | |
436 | 2.36k | const int nResult = VSIStatExL(osSubFilePath, psStatBuf, nFlags); |
437 | | |
438 | 2.36k | if (nResult == 0) |
439 | 702 | { |
440 | 702 | if (nSize != 0) |
441 | 149 | psStatBuf->st_size = nSize; |
442 | 553 | else if (static_cast<vsi_l_offset>(psStatBuf->st_size) >= nOff) |
443 | 446 | psStatBuf->st_size -= nOff; |
444 | 107 | else |
445 | 107 | psStatBuf->st_size = 0; |
446 | 702 | } |
447 | | |
448 | 2.36k | return nResult; |
449 | 2.62k | } |
450 | | |
451 | | /************************************************************************/ |
452 | | /* Unlink() */ |
453 | | /************************************************************************/ |
454 | | |
455 | | int VSISubFileFilesystemHandler::Unlink(const char * /* pszFilename */) |
456 | 0 | { |
457 | 0 | errno = EACCES; |
458 | 0 | return -1; |
459 | 0 | } |
460 | | |
461 | | /************************************************************************/ |
462 | | /* Mkdir() */ |
463 | | /************************************************************************/ |
464 | | |
465 | | int VSISubFileFilesystemHandler::Mkdir(const char * /* pszPathname */, |
466 | | long /* nMode */) |
467 | 0 | { |
468 | 0 | errno = EACCES; |
469 | 0 | return -1; |
470 | 0 | } |
471 | | |
472 | | /************************************************************************/ |
473 | | /* Rmdir() */ |
474 | | /************************************************************************/ |
475 | | |
476 | | int VSISubFileFilesystemHandler::Rmdir(const char * /* pszPathname */) |
477 | | |
478 | 0 | { |
479 | 0 | errno = EACCES; |
480 | 0 | return -1; |
481 | 0 | } |
482 | | |
483 | | /************************************************************************/ |
484 | | /* ReadDirEx() */ |
485 | | /************************************************************************/ |
486 | | |
487 | | char **VSISubFileFilesystemHandler::ReadDirEx(const char * /* pszPath */, |
488 | | int /* nMaxFiles */) |
489 | 0 | { |
490 | 0 | errno = EACCES; |
491 | 0 | return nullptr; |
492 | 0 | } |
493 | | |
494 | | /************************************************************************/ |
495 | | /* VSIInstallSubFileFilesystemHandler() */ |
496 | | /************************************************************************/ |
497 | | |
498 | | /*! |
499 | | \brief Install /vsisubfile/ virtual file handler. |
500 | | |
501 | | \verbatim embed:rst |
502 | | See :ref:`/vsisubfile/ documentation <vsisubfile>` |
503 | | \endverbatim |
504 | | */ |
505 | | |
506 | | void VSIInstallSubFileHandler() |
507 | 3 | { |
508 | 3 | VSIFileManager::InstallHandler("/vsisubfile/", |
509 | 3 | new VSISubFileFilesystemHandler); |
510 | 3 | } |