Line data Source code
1 : /**********************************************************************
2 : *
3 : * Project: CPL - Common Portability Library
4 : * Purpose: Implement VSI large file api for Unix platforms with fseek64()
5 : * and ftell64() such as IRIX.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : **********************************************************************
9 : * Copyright (c) 2001, Frank Warmerdam
10 : * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
11 : *
12 : * Permission is hereby granted, free of charge, to any person obtaining a
13 : * copy of this software and associated documentation files (the "Software"),
14 : * to deal in the Software without restriction, including without limitation
15 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 : * and/or sell copies of the Software, and to permit persons to whom the
17 : * Software is furnished to do so, subject to the following conditions:
18 : *
19 : * The above copyright notice and this permission notice shall be included
20 : * in all copies or substantial portions of the Software.
21 : *
22 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 : * DEALINGS IN THE SOFTWARE.
29 : ****************************************************************************
30 : *
31 : * NB: Note that in wrappers we are always saving the error state (errno
32 : * variable) to avoid side effects during debug prints or other possible
33 : * standard function calls (error states will be overwritten after such
34 : * a call).
35 : *
36 : ****************************************************************************/
37 :
38 : //! @cond Doxygen_Suppress
39 :
40 : // #define VSI_COUNT_BYTES_READ
41 :
42 : // Some unusual filesystems do not work if _FORTIFY_SOURCE in GCC or
43 : // clang is used within this source file, especially if techniques
44 : // like those in vsipreload are used. Fortify source interacts poorly with
45 : // filesystems that use fread for forward seeks. This leads to SIGSEGV within
46 : // fread calls.
47 : //
48 : // See this for hardening background info: https://wiki.debian.org/Hardening
49 : #undef _FORTIFY_SOURCE
50 :
51 : // 64 bit IO is only available on 32-bit android since API 24 / Android 7.0
52 : // See
53 : // https://android.googlesource.com/platform/bionic/+/master/docs/32-bit-abi.md
54 : #if defined(__ANDROID_API__) && __ANDROID_API__ >= 24
55 : #define _FILE_OFFSET_BITS 64
56 : #endif
57 :
58 : #include "cpl_port.h"
59 :
60 : #if !defined(_WIN32)
61 :
62 : #include "cpl_vsi.h"
63 : #include "cpl_vsi_virtual.h"
64 :
65 : #include <cstddef>
66 : #include <cstdio>
67 : #include <cstring>
68 : #include <dirent.h>
69 : #include <errno.h>
70 : #if HAVE_FCNTL_H
71 : #include <fcntl.h>
72 : #endif
73 : #include <sys/stat.h>
74 : #ifdef HAVE_STATVFS
75 : #include <sys/statvfs.h>
76 : #endif
77 : #include <sys/types.h>
78 : #if HAVE_UNISTD_H
79 : #include <unistd.h>
80 : #endif
81 : #ifdef HAVE_PREAD_BSD
82 : #include <sys/uio.h>
83 : #endif
84 :
85 : #if defined(__MACH__) && defined(__APPLE__)
86 : #define HAS_CASE_INSENSITIVE_FILE_SYSTEM
87 : #include <stdio.h>
88 : #include <stdlib.h>
89 : #include <limits.h>
90 : #endif
91 :
92 : #include <limits>
93 : #include <new>
94 :
95 : #include "cpl_config.h"
96 : #include "cpl_conv.h"
97 : #include "cpl_error.h"
98 : #include "cpl_multiproc.h"
99 : #include "cpl_string.h"
100 : #include "cpl_vsi_error.h"
101 :
102 : #if defined(UNIX_STDIO_64)
103 :
104 : #ifndef VSI_FTELL64
105 : #define VSI_FTELL64 ftell64
106 : #endif
107 : #ifndef VSI_FSEEK64
108 : #define VSI_FSEEK64 fseek64
109 : #endif
110 : #ifndef VSI_FOPEN64
111 : #define VSI_FOPEN64 fopen64
112 : #endif
113 : #ifndef VSI_STAT64
114 : #define VSI_STAT64 stat64
115 : #endif
116 : #ifndef VSI_STAT64_T
117 : #define VSI_STAT64_T stat64
118 : #endif
119 : #ifndef VSI_FTRUNCATE64
120 : #define VSI_FTRUNCATE64 ftruncate64
121 : #endif
122 :
123 : #else /* not UNIX_STDIO_64 */
124 :
125 : #ifndef VSI_FTELL64
126 : #define VSI_FTELL64 ftell
127 : #endif
128 : #ifndef VSI_FSEEK64
129 : #define VSI_FSEEK64 fseek
130 : #endif
131 : #ifndef VSI_FOPEN64
132 : #define VSI_FOPEN64 fopen
133 : #endif
134 : #ifndef VSI_STAT64
135 : #define VSI_STAT64 stat
136 : #endif
137 : #ifndef VSI_STAT64_T
138 : #define VSI_STAT64_T stat
139 : #endif
140 : #ifndef VSI_FTRUNCATE64
141 : #define VSI_FTRUNCATE64 ftruncate
142 : #endif
143 :
144 : #endif /* ndef UNIX_STDIO_64 */
145 :
146 : #ifndef BUILD_WITHOUT_64BIT_OFFSET
147 : // Ensure we have working 64 bit API
148 : static_assert(sizeof(VSI_FTELL64(stdout)) == sizeof(vsi_l_offset),
149 : "File API does not seem to support 64-bit offset. "
150 : "If you still want to build GDAL without > 4GB file support, "
151 : "add the -DBUILD_WITHOUT_64BIT_OFFSET define");
152 : static_assert(sizeof(VSIStatBufL::st_size) == sizeof(vsi_l_offset),
153 : "File API does not seem to support 64-bit file size. "
154 : "If you still want to build GDAL without > 4GB file support, "
155 : "add the -DBUILD_WITHOUT_64BIT_OFFSET define");
156 : #endif
157 :
158 : /************************************************************************/
159 : /* ==================================================================== */
160 : /* VSIUnixStdioFilesystemHandler */
161 : /* ==================================================================== */
162 : /************************************************************************/
163 :
164 : class VSIUnixStdioFilesystemHandler final : public VSIFilesystemHandler
165 : {
166 : CPL_DISALLOW_COPY_ASSIGN(VSIUnixStdioFilesystemHandler)
167 :
168 : #ifdef VSI_COUNT_BYTES_READ
169 : vsi_l_offset nTotalBytesRead = 0;
170 : CPLMutex *hMutex = nullptr;
171 : #endif
172 :
173 : public:
174 1228 : VSIUnixStdioFilesystemHandler() = default;
175 : #ifdef VSI_COUNT_BYTES_READ
176 : ~VSIUnixStdioFilesystemHandler() override;
177 : #endif
178 :
179 : VSIVirtualHandle *Open(const char *pszFilename, const char *pszAccess,
180 : bool bSetError,
181 : CSLConstList /* papszOptions */) override;
182 : int Stat(const char *pszFilename, VSIStatBufL *pStatBuf,
183 : int nFlags) override;
184 : int Unlink(const char *pszFilename) override;
185 : int Rename(const char *oldpath, const char *newpath) override;
186 : int Mkdir(const char *pszDirname, long nMode) override;
187 : int Rmdir(const char *pszDirname) override;
188 : char **ReadDirEx(const char *pszDirname, int nMaxFiles) override;
189 : GIntBig GetDiskFreeSpace(const char *pszDirname) override;
190 : int SupportsSparseFiles(const char *pszPath) override;
191 :
192 : bool IsLocal(const char *pszPath) override;
193 : bool SupportsSequentialWrite(const char *pszPath,
194 : bool /* bAllowLocalTempFile */) override;
195 : bool SupportsRandomWrite(const char *pszPath,
196 : bool /* bAllowLocalTempFile */) override;
197 :
198 : VSIDIR *OpenDir(const char *pszPath, int nRecurseDepth,
199 : const char *const *papszOptions) override;
200 :
201 : #ifdef HAS_CASE_INSENSITIVE_FILE_SYSTEM
202 : std::string
203 : GetCanonicalFilename(const std::string &osFilename) const override;
204 : #endif
205 :
206 : #ifdef VSI_COUNT_BYTES_READ
207 : void AddToTotal(vsi_l_offset nBytes);
208 : #endif
209 : };
210 :
211 : /************************************************************************/
212 : /* ==================================================================== */
213 : /* VSIUnixStdioHandle */
214 : /* ==================================================================== */
215 : /************************************************************************/
216 :
217 : class VSIUnixStdioHandle final : public VSIVirtualHandle
218 : {
219 : CPL_DISALLOW_COPY_ASSIGN(VSIUnixStdioHandle)
220 :
221 : FILE *fp = nullptr;
222 : vsi_l_offset m_nOffset = 0;
223 : bool bReadOnly = true;
224 : bool bLastOpWrite = false;
225 : bool bLastOpRead = false;
226 : bool bAtEOF = false;
227 : // In a+ mode, disable any optimization since the behavior of the file
228 : // pointer on Mac and other BSD system is to have a seek() to the end of
229 : // file and thus a call to our Seek(0, SEEK_SET) before a read will be a
230 : // no-op.
231 : bool bModeAppendReadWrite = false;
232 : #ifdef VSI_COUNT_BYTES_READ
233 : vsi_l_offset nTotalBytesRead = 0;
234 : VSIUnixStdioFilesystemHandler *poFS = nullptr;
235 : #endif
236 : public:
237 : VSIUnixStdioHandle(VSIUnixStdioFilesystemHandler *poFSIn, FILE *fpIn,
238 : bool bReadOnlyIn, bool bModeAppendReadWriteIn);
239 :
240 : int Seek(vsi_l_offset nOffsetIn, int nWhence) override;
241 : vsi_l_offset Tell() override;
242 : size_t Read(void *pBuffer, size_t nSize, size_t nMemb) override;
243 : size_t Write(const void *pBuffer, size_t nSize, size_t nMemb) override;
244 : int Eof() override;
245 : int Flush() override;
246 : int Close() override;
247 : int Truncate(vsi_l_offset nNewSize) override;
248 :
249 55 : void *GetNativeFileDescriptor() override
250 : {
251 55 : return reinterpret_cast<void *>(static_cast<uintptr_t>(fileno(fp)));
252 : }
253 :
254 : VSIRangeStatus GetRangeStatus(vsi_l_offset nOffset,
255 : vsi_l_offset nLength) override;
256 : #if defined(HAVE_PREAD64) || (defined(HAVE_PREAD_BSD) && SIZEOF_OFF_T == 8)
257 : bool HasPRead() const override;
258 : size_t PRead(void * /*pBuffer*/, size_t /* nSize */,
259 : vsi_l_offset /*nOffset*/) const override;
260 : #endif
261 : };
262 :
263 : /************************************************************************/
264 : /* VSIUnixStdioHandle() */
265 : /************************************************************************/
266 :
267 111450 : VSIUnixStdioHandle::VSIUnixStdioHandle(
268 : #ifndef VSI_COUNT_BYTES_READ
269 : CPL_UNUSED
270 : #endif
271 : VSIUnixStdioFilesystemHandler *poFSIn,
272 111450 : FILE *fpIn, bool bReadOnlyIn, bool bModeAppendReadWriteIn)
273 : : fp(fpIn), bReadOnly(bReadOnlyIn),
274 111450 : bModeAppendReadWrite(bModeAppendReadWriteIn)
275 : #ifdef VSI_COUNT_BYTES_READ
276 : ,
277 : poFS(poFSIn)
278 : #endif
279 : {
280 111030 : }
281 :
282 : /************************************************************************/
283 : /* Close() */
284 : /************************************************************************/
285 :
286 110891 : int VSIUnixStdioHandle::Close()
287 :
288 : {
289 110891 : if (!fp)
290 12 : return 0;
291 :
292 : VSIDebug1("VSIUnixStdioHandle::Close(%p)", fp);
293 :
294 : #ifdef VSI_COUNT_BYTES_READ
295 : poFS->AddToTotal(nTotalBytesRead);
296 : #endif
297 :
298 110879 : int ret = fclose(fp);
299 110565 : fp = nullptr;
300 110565 : return ret;
301 : }
302 :
303 : /************************************************************************/
304 : /* Seek() */
305 : /************************************************************************/
306 :
307 2830780 : int VSIUnixStdioHandle::Seek(vsi_l_offset nOffsetIn, int nWhence)
308 : {
309 2830780 : bAtEOF = false;
310 :
311 : // Seeks that do nothing are still surprisingly expensive with MSVCRT.
312 : // try and short circuit if possible.
313 2830780 : if (!bModeAppendReadWrite && nWhence == SEEK_SET && nOffsetIn == m_nOffset)
314 389401 : return 0;
315 :
316 : // On a read-only file, we can avoid a lseek() system call to be issued
317 : // if the next position to seek to is within the buffered page.
318 2441370 : if (bReadOnly && nWhence == SEEK_SET)
319 : {
320 1655010 : const int l_PAGE_SIZE = 4096;
321 1655010 : if (nOffsetIn > m_nOffset && nOffsetIn < l_PAGE_SIZE + m_nOffset)
322 : {
323 52030 : const int nDiff = static_cast<int>(nOffsetIn - m_nOffset);
324 : // Do not zero-initialize the buffer. We don't read from it
325 : GByte abyTemp[l_PAGE_SIZE];
326 52030 : const int nRead = static_cast<int>(fread(abyTemp, 1, nDiff, fp));
327 52035 : if (nRead == nDiff)
328 : {
329 52019 : m_nOffset = nOffsetIn;
330 52019 : bLastOpWrite = false;
331 52019 : bLastOpRead = false;
332 52019 : return 0;
333 : }
334 : }
335 : }
336 :
337 : #if !defined(UNIX_STDIO_64) && SIZEOF_UNSIGNED_LONG == 4
338 : if (nOffsetIn > static_cast<vsi_l_offset>(std::numeric_limits<long>::max()))
339 : {
340 : CPLError(
341 : CE_Failure, CPLE_AppDefined,
342 : "Attempt at seeking beyond long extent. Lack of 64-bit file I/O");
343 : return -1;
344 : }
345 : #endif
346 :
347 2389360 : const int nResult = VSI_FSEEK64(fp, nOffsetIn, nWhence);
348 2389100 : const int nError = errno;
349 :
350 : #ifdef VSI_DEBUG
351 :
352 : if (nWhence == SEEK_SET)
353 : {
354 : VSIDebug3("VSIUnixStdioHandle::Seek(%p," CPL_FRMT_GUIB
355 : ",SEEK_SET) = %d",
356 : fp, nOffsetIn, nResult);
357 : }
358 : else if (nWhence == SEEK_END)
359 : {
360 : VSIDebug3("VSIUnixStdioHandle::Seek(%p," CPL_FRMT_GUIB
361 : ",SEEK_END) = %d",
362 : fp, nOffsetIn, nResult);
363 : }
364 : else if (nWhence == SEEK_CUR)
365 : {
366 : VSIDebug3("VSIUnixStdioHandle::Seek(%p," CPL_FRMT_GUIB
367 : ",SEEK_CUR) = %d",
368 : fp, nOffsetIn, nResult);
369 : }
370 : else
371 : {
372 : VSIDebug4("VSIUnixStdioHandle::Seek(%p," CPL_FRMT_GUIB
373 : ",%d-Unknown) = %d",
374 : fp, nOffsetIn, nWhence, nResult);
375 : }
376 :
377 : #endif
378 :
379 2389100 : if (nResult != -1)
380 : {
381 2388610 : if (nWhence == SEEK_SET)
382 : {
383 1798260 : m_nOffset = nOffsetIn;
384 : }
385 590353 : else if (nWhence == SEEK_END)
386 : {
387 515745 : m_nOffset = VSI_FTELL64(fp);
388 : }
389 74608 : else if (nWhence == SEEK_CUR)
390 : {
391 : if (nOffsetIn > INT_MAX)
392 : {
393 : // printf("likely negative offset intended\n");
394 : }
395 74614 : m_nOffset += nOffsetIn;
396 : }
397 : }
398 :
399 2389080 : bLastOpWrite = false;
400 2389080 : bLastOpRead = false;
401 :
402 2389080 : errno = nError;
403 2389080 : return nResult;
404 : }
405 :
406 : /************************************************************************/
407 : /* Tell() */
408 : /************************************************************************/
409 :
410 2084700 : vsi_l_offset VSIUnixStdioHandle::Tell()
411 :
412 : {
413 : #if 0
414 : const vsi_l_offset nOffset = VSI_FTELL64( fp );
415 : const int nError = errno;
416 :
417 : VSIDebug2( "VSIUnixStdioHandle::Tell(%p) = %ld",
418 : fp, static_cast<long>(nOffset) );
419 :
420 : errno = nError;
421 : #endif
422 :
423 2084700 : return m_nOffset;
424 : }
425 :
426 : /************************************************************************/
427 : /* Flush() */
428 : /************************************************************************/
429 :
430 35038 : int VSIUnixStdioHandle::Flush()
431 :
432 : {
433 : VSIDebug1("VSIUnixStdioHandle::Flush(%p)", fp);
434 :
435 35038 : return fflush(fp);
436 : }
437 :
438 : /************************************************************************/
439 : /* Read() */
440 : /************************************************************************/
441 :
442 7093980 : size_t VSIUnixStdioHandle::Read(void *pBuffer, size_t nSize, size_t nCount)
443 :
444 : {
445 : /* -------------------------------------------------------------------- */
446 : /* If a fwrite() is followed by an fread(), the POSIX rules are */
447 : /* that some of the write may still be buffered and lost. We */
448 : /* are required to do a seek between to force flushing. So we */
449 : /* keep careful track of what happened last to know if we */
450 : /* skipped a flushing seek that we may need to do now. */
451 : /* -------------------------------------------------------------------- */
452 7093980 : if (!bModeAppendReadWrite && bLastOpWrite)
453 : {
454 2845 : if (VSI_FSEEK64(fp, m_nOffset, SEEK_SET) != 0)
455 : {
456 : VSIDebug1("Write calling seek failed. %d", m_nOffset);
457 : }
458 : }
459 :
460 : /* -------------------------------------------------------------------- */
461 : /* Perform the read. */
462 : /* -------------------------------------------------------------------- */
463 7093980 : const size_t nResult = fread(pBuffer, nSize, nCount, fp);
464 :
465 : #ifdef VSI_DEBUG
466 : const int nError = errno;
467 : VSIDebug4("VSIUnixStdioHandle::Read(%p,%ld,%ld) = %ld", fp,
468 : static_cast<long>(nSize), static_cast<long>(nCount),
469 : static_cast<long>(nResult));
470 : errno = nError;
471 : #endif
472 :
473 : /* -------------------------------------------------------------------- */
474 : /* Update current offset. */
475 : /* -------------------------------------------------------------------- */
476 :
477 : #ifdef VSI_COUNT_BYTES_READ
478 : nTotalBytesRead += nSize * nResult;
479 : #endif
480 :
481 7093340 : m_nOffset += nSize * nResult;
482 7093340 : bLastOpWrite = false;
483 7093340 : bLastOpRead = true;
484 :
485 7093340 : if (nResult != nCount)
486 : {
487 75572 : errno = 0;
488 75572 : vsi_l_offset nNewOffset = VSI_FTELL64(fp);
489 75571 : if (errno == 0) // ftell() can fail if we are end of file with a pipe.
490 75573 : m_nOffset = nNewOffset;
491 : else
492 0 : CPLDebug("VSI", "%s", VSIStrerror(errno));
493 75573 : bAtEOF = CPL_TO_BOOL(feof(fp));
494 : }
495 :
496 7093000 : return nResult;
497 : }
498 :
499 : /************************************************************************/
500 : /* Write() */
501 : /************************************************************************/
502 :
503 2366260 : size_t VSIUnixStdioHandle::Write(const void *pBuffer, size_t nSize,
504 : size_t nCount)
505 :
506 : {
507 : /* -------------------------------------------------------------------- */
508 : /* If a fwrite() is followed by an fread(), the POSIX rules are */
509 : /* that some of the write may still be buffered and lost. We */
510 : /* are required to do a seek between to force flushing. So we */
511 : /* keep careful track of what happened last to know if we */
512 : /* skipped a flushing seek that we may need to do now. */
513 : /* -------------------------------------------------------------------- */
514 2366260 : if (!bModeAppendReadWrite && bLastOpRead)
515 : {
516 1848 : if (VSI_FSEEK64(fp, m_nOffset, SEEK_SET) != 0)
517 : {
518 : VSIDebug1("Write calling seek failed. %d", m_nOffset);
519 : }
520 : }
521 :
522 : /* -------------------------------------------------------------------- */
523 : /* Perform the write. */
524 : /* -------------------------------------------------------------------- */
525 2366260 : const size_t nResult = fwrite(pBuffer, nSize, nCount, fp);
526 :
527 : #if VSI_DEBUG
528 : const int nError = errno;
529 :
530 : VSIDebug4("VSIUnixStdioHandle::Write(%p,%ld,%ld) = %ld", fp,
531 : static_cast<long>(nSize), static_cast<long>(nCount),
532 : static_cast<long>(nResult));
533 :
534 : errno = nError;
535 : #endif
536 :
537 : /* -------------------------------------------------------------------- */
538 : /* Update current offset. */
539 : /* -------------------------------------------------------------------- */
540 2366260 : m_nOffset += nSize * nResult;
541 2366260 : bLastOpWrite = true;
542 2366260 : bLastOpRead = false;
543 :
544 2366260 : return nResult;
545 : }
546 :
547 : /************************************************************************/
548 : /* Eof() */
549 : /************************************************************************/
550 :
551 233543 : int VSIUnixStdioHandle::Eof()
552 :
553 : {
554 233543 : return bAtEOF ? TRUE : FALSE;
555 : }
556 :
557 : /************************************************************************/
558 : /* Truncate() */
559 : /************************************************************************/
560 :
561 500 : int VSIUnixStdioHandle::Truncate(vsi_l_offset nNewSize)
562 : {
563 500 : fflush(fp);
564 500 : return VSI_FTRUNCATE64(fileno(fp), nNewSize);
565 : }
566 :
567 : /************************************************************************/
568 : /* GetRangeStatus() */
569 : /************************************************************************/
570 :
571 : #ifdef __linux
572 : #if !defined(MISSING_LINUX_FS_H)
573 : #include <linux/fs.h> // FS_IOC_FIEMAP
574 : #endif
575 : #ifdef FS_IOC_FIEMAP
576 : #include <linux/types.h> // for types used in linux/fiemap.h
577 : #include <linux/fiemap.h> // struct fiemap
578 : #endif
579 : #include <sys/ioctl.h>
580 : #include <errno.h>
581 : #endif
582 :
583 274 : VSIRangeStatus VSIUnixStdioHandle::GetRangeStatus(vsi_l_offset
584 : #ifdef FS_IOC_FIEMAP
585 : nOffset
586 : #endif
587 : ,
588 : vsi_l_offset
589 : #ifdef FS_IOC_FIEMAP
590 : nLength
591 : #endif
592 : )
593 : {
594 : #ifdef FS_IOC_FIEMAP
595 : // fiemap IOCTL documented at
596 : // https://www.kernel.org/doc/Documentation/filesystems/fiemap.txt
597 :
598 : // The fiemap struct contains a "variable length" array at its end
599 : // As we are interested in only one extent, we allocate the base size of
600 : // fiemap + one fiemap_extent.
601 : GByte abyBuffer[sizeof(struct fiemap) + sizeof(struct fiemap_extent)];
602 274 : int fd = fileno(fp);
603 274 : struct fiemap *psExtentMap = reinterpret_cast<struct fiemap *>(&abyBuffer);
604 274 : memset(psExtentMap, 0,
605 : sizeof(struct fiemap) + sizeof(struct fiemap_extent));
606 274 : psExtentMap->fm_start = nOffset;
607 274 : psExtentMap->fm_length = nLength;
608 274 : psExtentMap->fm_extent_count = 1;
609 274 : int ret = ioctl(fd, FS_IOC_FIEMAP, psExtentMap);
610 274 : if (ret < 0)
611 0 : return VSI_RANGE_STATUS_UNKNOWN;
612 274 : if (psExtentMap->fm_mapped_extents == 0)
613 2 : return VSI_RANGE_STATUS_HOLE;
614 : // In case there is one extent with unknown status, retry after having
615 : // asked the kernel to sync the file.
616 272 : const fiemap_extent *pasExtent = &(psExtentMap->fm_extents[0]);
617 272 : if (psExtentMap->fm_mapped_extents == 1 &&
618 272 : (pasExtent[0].fe_flags & FIEMAP_EXTENT_UNKNOWN) != 0)
619 : {
620 14 : psExtentMap->fm_flags = FIEMAP_FLAG_SYNC;
621 14 : psExtentMap->fm_start = nOffset;
622 14 : psExtentMap->fm_length = nLength;
623 14 : psExtentMap->fm_extent_count = 1;
624 14 : ret = ioctl(fd, FS_IOC_FIEMAP, psExtentMap);
625 14 : if (ret < 0)
626 0 : return VSI_RANGE_STATUS_UNKNOWN;
627 14 : if (psExtentMap->fm_mapped_extents == 0)
628 0 : return VSI_RANGE_STATUS_HOLE;
629 : }
630 272 : return VSI_RANGE_STATUS_DATA;
631 : #else
632 : static bool bMessageEmitted = false;
633 : if (!bMessageEmitted)
634 : {
635 : CPLDebug("VSI", "Sorry: GetExtentStatus() not implemented for "
636 : "this operating system");
637 : bMessageEmitted = true;
638 : }
639 : return VSI_RANGE_STATUS_UNKNOWN;
640 : #endif
641 : }
642 :
643 : /************************************************************************/
644 : /* HasPRead() */
645 : /************************************************************************/
646 :
647 : #if defined(HAVE_PREAD64) || (defined(HAVE_PREAD_BSD) && SIZEOF_OFF_T == 8)
648 234 : bool VSIUnixStdioHandle::HasPRead() const
649 : {
650 234 : return true;
651 : }
652 :
653 : /************************************************************************/
654 : /* PRead() */
655 : /************************************************************************/
656 :
657 2340 : size_t VSIUnixStdioHandle::PRead(void *pBuffer, size_t nSize,
658 : vsi_l_offset nOffset) const
659 : {
660 : #ifdef HAVE_PREAD64
661 2340 : return pread64(fileno(fp), pBuffer, nSize, nOffset);
662 : #else
663 : return pread(fileno(fp), pBuffer, nSize, static_cast<off_t>(nOffset));
664 : #endif
665 : }
666 : #endif
667 :
668 : /************************************************************************/
669 : /* ==================================================================== */
670 : /* VSIUnixStdioFilesystemHandler */
671 : /* ==================================================================== */
672 : /************************************************************************/
673 :
674 : #ifdef VSI_COUNT_BYTES_READ
675 : /************************************************************************/
676 : /* ~VSIUnixStdioFilesystemHandler() */
677 : /************************************************************************/
678 :
679 : VSIUnixStdioFilesystemHandler::~VSIUnixStdioFilesystemHandler()
680 : {
681 : CPLDebug(
682 : "VSI",
683 : "~VSIUnixStdioFilesystemHandler() : nTotalBytesRead = " CPL_FRMT_GUIB,
684 : nTotalBytesRead);
685 :
686 : if (hMutex != nullptr)
687 : CPLDestroyMutex(hMutex);
688 : hMutex = nullptr;
689 : }
690 : #endif
691 :
692 : /************************************************************************/
693 : /* Open() */
694 : /************************************************************************/
695 :
696 : VSIVirtualHandle *
697 168091 : VSIUnixStdioFilesystemHandler::Open(const char *pszFilename,
698 : const char *pszAccess, bool bSetError,
699 : CSLConstList /* papszOptions */)
700 :
701 : {
702 168091 : FILE *fp = VSI_FOPEN64(pszFilename, pszAccess);
703 167500 : const int nError = errno;
704 :
705 : VSIDebug3("VSIUnixStdioFilesystemHandler::Open(\"%s\",\"%s\") = %p",
706 : pszFilename, pszAccess, fp);
707 :
708 167500 : if (fp == nullptr)
709 : {
710 56454 : if (bSetError)
711 : {
712 11732 : VSIError(VSIE_FileError, "%s: %s", pszFilename, strerror(nError));
713 : }
714 56461 : errno = nError;
715 56461 : return nullptr;
716 : }
717 :
718 111046 : const bool bReadOnly =
719 111046 : strcmp(pszAccess, "rb") == 0 || strcmp(pszAccess, "r") == 0;
720 111046 : const bool bModeAppendReadWrite =
721 111046 : strcmp(pszAccess, "a+b") == 0 || strcmp(pszAccess, "a+") == 0;
722 : VSIUnixStdioHandle *poHandle = new (std::nothrow)
723 111046 : VSIUnixStdioHandle(this, fp, bReadOnly, bModeAppendReadWrite);
724 111011 : if (poHandle == nullptr)
725 : {
726 0 : fclose(fp);
727 0 : return nullptr;
728 : }
729 :
730 111011 : errno = nError;
731 :
732 : /* -------------------------------------------------------------------- */
733 : /* If VSI_CACHE is set we want to use a cached reader instead */
734 : /* of more direct io on the underlying file. */
735 : /* -------------------------------------------------------------------- */
736 111011 : if (bReadOnly && CPLTestBool(CPLGetConfigOption("VSI_CACHE", "FALSE")))
737 : {
738 5 : return VSICreateCachedFile(poHandle);
739 : }
740 :
741 111623 : return poHandle;
742 : }
743 :
744 : /************************************************************************/
745 : /* Stat() */
746 : /************************************************************************/
747 :
748 204480 : int VSIUnixStdioFilesystemHandler::Stat(const char *pszFilename,
749 : VSIStatBufL *pStatBuf, int /* nFlags */)
750 : {
751 204480 : return (VSI_STAT64(pszFilename, pStatBuf));
752 : }
753 :
754 : /************************************************************************/
755 : /* Unlink() */
756 : /************************************************************************/
757 :
758 25815 : int VSIUnixStdioFilesystemHandler::Unlink(const char *pszFilename)
759 :
760 : {
761 25815 : return unlink(pszFilename);
762 : }
763 :
764 : /************************************************************************/
765 : /* Rename() */
766 : /************************************************************************/
767 :
768 454 : int VSIUnixStdioFilesystemHandler::Rename(const char *oldpath,
769 : const char *newpath)
770 :
771 : {
772 454 : return rename(oldpath, newpath);
773 : }
774 :
775 : /************************************************************************/
776 : /* Mkdir() */
777 : /************************************************************************/
778 :
779 539 : int VSIUnixStdioFilesystemHandler::Mkdir(const char *pszPathname, long nMode)
780 :
781 : {
782 539 : return mkdir(pszPathname, static_cast<int>(nMode));
783 : }
784 :
785 : /************************************************************************/
786 : /* Rmdir() */
787 : /************************************************************************/
788 :
789 189 : int VSIUnixStdioFilesystemHandler::Rmdir(const char *pszPathname)
790 :
791 : {
792 189 : return rmdir(pszPathname);
793 : }
794 :
795 : /************************************************************************/
796 : /* ReadDirEx() */
797 : /************************************************************************/
798 :
799 18561 : char **VSIUnixStdioFilesystemHandler::ReadDirEx(const char *pszPath,
800 : int nMaxFiles)
801 :
802 : {
803 18561 : if (strlen(pszPath) == 0)
804 17 : pszPath = ".";
805 :
806 37122 : CPLStringList oDir;
807 18561 : DIR *hDir = opendir(pszPath);
808 18561 : if (hDir != nullptr)
809 : {
810 : // We want to avoid returning NULL for an empty list.
811 16453 : oDir.Assign(static_cast<char **>(CPLCalloc(2, sizeof(char *))));
812 :
813 16453 : struct dirent *psDirEntry = nullptr;
814 1384410 : while ((psDirEntry = readdir(hDir)) != nullptr)
815 : {
816 1367960 : oDir.AddString(psDirEntry->d_name);
817 1367960 : if (nMaxFiles > 0 && oDir.Count() > nMaxFiles)
818 5 : break;
819 : }
820 :
821 16453 : closedir(hDir);
822 : }
823 : else
824 : {
825 : // Should we generate an error?
826 : // For now we'll just return NULL (at the end of the function).
827 : }
828 :
829 37122 : return oDir.StealList();
830 : }
831 :
832 : /************************************************************************/
833 : /* GetDiskFreeSpace() */
834 : /************************************************************************/
835 :
836 3 : GIntBig VSIUnixStdioFilesystemHandler::GetDiskFreeSpace(const char *
837 : #ifdef HAVE_STATVFS
838 : pszDirname
839 : #endif
840 : )
841 : {
842 3 : GIntBig nRet = -1;
843 : #ifdef HAVE_STATVFS
844 :
845 : #ifdef HAVE_STATVFS64
846 : struct statvfs64 buf;
847 3 : if (statvfs64(pszDirname, &buf) == 0)
848 : {
849 2 : nRet = static_cast<GIntBig>(buf.f_frsize *
850 2 : static_cast<GUIntBig>(buf.f_bavail));
851 : }
852 : #else
853 : struct statvfs buf;
854 : if (statvfs(pszDirname, &buf) == 0)
855 : {
856 : nRet = static_cast<GIntBig>(buf.f_frsize *
857 : static_cast<GUIntBig>(buf.f_bavail));
858 : }
859 : #endif
860 :
861 : #endif
862 3 : return nRet;
863 : }
864 :
865 : /************************************************************************/
866 : /* SupportsSparseFiles() */
867 : /************************************************************************/
868 :
869 : #ifdef __linux
870 : #include <sys/vfs.h>
871 : #endif
872 :
873 2 : int VSIUnixStdioFilesystemHandler::SupportsSparseFiles(const char *
874 : #ifdef __linux
875 : pszPath
876 : #endif
877 : )
878 : {
879 : #ifdef __linux
880 : struct statfs sStatFS;
881 2 : if (statfs(pszPath, &sStatFS) == 0)
882 : {
883 : // Add here any missing filesystem supporting sparse files.
884 : // See http://en.wikipedia.org/wiki/Comparison_of_file_systems
885 2 : switch (static_cast<unsigned>(sStatFS.f_type))
886 : {
887 : // Codes from http://man7.org/linux/man-pages/man2/statfs.2.html
888 2 : case 0xef53U: // ext2, 3, 4
889 : case 0x52654973U: // reiser
890 : case 0x58465342U: // xfs
891 : case 0x3153464aU: // jfs
892 : case 0x5346544eU: // ntfs
893 : case 0x9123683eU: // brfs
894 : // nfs: NFS < 4.2 supports creating sparse files (but reading them
895 : // not efficiently).
896 : case 0x6969U:
897 : case 0x01021994U: // tmpfs
898 2 : return TRUE;
899 :
900 0 : case 0x4d44U: // msdos
901 0 : return FALSE;
902 :
903 0 : case 0x53464846U: // Windows Subsystem for Linux fs
904 : {
905 : static bool bUnknownFSEmitted = false;
906 0 : if (!bUnknownFSEmitted)
907 : {
908 0 : CPLDebug("VSI",
909 : "Windows Subsystem for Linux FS is at "
910 : "the time of writing not known to support sparse "
911 : "files");
912 0 : bUnknownFSEmitted = true;
913 : }
914 0 : return FALSE;
915 : }
916 :
917 0 : default:
918 : {
919 : static bool bUnknownFSEmitted = false;
920 0 : if (!bUnknownFSEmitted)
921 : {
922 0 : CPLDebug("VSI",
923 : "Filesystem with type %X unknown. "
924 : "Assuming it does not support sparse files",
925 0 : static_cast<int>(sStatFS.f_type));
926 0 : bUnknownFSEmitted = true;
927 : }
928 0 : return FALSE;
929 : }
930 : }
931 : }
932 0 : return FALSE;
933 : #else
934 : static bool bMessageEmitted = false;
935 : if (!bMessageEmitted)
936 : {
937 : CPLDebug("VSI", "Sorry: SupportsSparseFiles() not implemented "
938 : "for this operating system");
939 : bMessageEmitted = true;
940 : }
941 : return FALSE;
942 : #endif
943 : }
944 :
945 : /************************************************************************/
946 : /* IsLocal() */
947 : /************************************************************************/
948 :
949 106 : bool VSIUnixStdioFilesystemHandler::IsLocal(const char *
950 : #ifdef __linux
951 : pszPath
952 : #endif
953 : )
954 : {
955 : #ifdef __linux
956 : struct statfs sStatFS;
957 106 : if (statfs(pszPath, &sStatFS) == 0)
958 : {
959 : // See http://en.wikipedia.org/wiki/Comparison_of_file_systems
960 104 : switch (static_cast<unsigned>(sStatFS.f_type))
961 : {
962 : // Codes from http://man7.org/linux/man-pages/man2/statfs.2.html
963 0 : case 0x6969U: // NFS
964 : case 0x517bU: // SMB
965 : case 0xff534d42U: // CIFS
966 : case 0xfe534d42U: // SMB2
967 : // (https://github.com/libuv/libuv/blob/97dcdb1926f6aca43171e1614338bcef067abd59/src/unix/fs.c#L960)
968 0 : return false;
969 : }
970 : }
971 : #else
972 : static bool bMessageEmitted = false;
973 : if (!bMessageEmitted)
974 : {
975 : CPLDebug("VSI", "Sorry: IsLocal() not implemented "
976 : "for this operating system");
977 : bMessageEmitted = true;
978 : }
979 : #endif
980 106 : return true;
981 : }
982 :
983 : /************************************************************************/
984 : /* SupportsSequentialWrite() */
985 : /************************************************************************/
986 :
987 50 : bool VSIUnixStdioFilesystemHandler::SupportsSequentialWrite(
988 : const char *pszPath, bool /* bAllowLocalTempFile */)
989 : {
990 : VSIStatBufL sStat;
991 50 : if (VSIStatL(pszPath, &sStat) == 0)
992 46 : return access(pszPath, W_OK) == 0;
993 4 : return access(CPLGetDirname(pszPath), W_OK) == 0;
994 : }
995 :
996 : /************************************************************************/
997 : /* SupportsRandomWrite() */
998 : /************************************************************************/
999 :
1000 4 : bool VSIUnixStdioFilesystemHandler::SupportsRandomWrite(
1001 : const char *pszPath, bool /* bAllowLocalTempFile */)
1002 : {
1003 4 : return SupportsSequentialWrite(pszPath, false);
1004 : }
1005 :
1006 : /************************************************************************/
1007 : /* VSIDIRUnixStdio */
1008 : /************************************************************************/
1009 :
1010 : struct VSIDIRUnixStdio final : public VSIDIR
1011 : {
1012 : CPLString osRootPath{};
1013 : CPLString osBasePath{};
1014 : DIR *m_psDir = nullptr;
1015 : int nRecurseDepth = 0;
1016 : VSIDIREntry entry{};
1017 : std::vector<VSIDIRUnixStdio *> aoStackSubDir{};
1018 : VSIUnixStdioFilesystemHandler *poFS = nullptr;
1019 : std::string m_osFilterPrefix{};
1020 : bool m_bNameAndTypeOnly = false;
1021 :
1022 229 : explicit VSIDIRUnixStdio(VSIUnixStdioFilesystemHandler *poFSIn)
1023 229 : : poFS(poFSIn)
1024 : {
1025 229 : }
1026 :
1027 : ~VSIDIRUnixStdio();
1028 :
1029 : const VSIDIREntry *NextDirEntry() override;
1030 :
1031 : VSIDIRUnixStdio(const VSIDIRUnixStdio &) = delete;
1032 : VSIDIRUnixStdio &operator=(const VSIDIRUnixStdio &) = delete;
1033 : };
1034 :
1035 : /************************************************************************/
1036 : /* ~VSIDIRUnixStdio() */
1037 : /************************************************************************/
1038 :
1039 687 : VSIDIRUnixStdio::~VSIDIRUnixStdio()
1040 : {
1041 229 : while (!aoStackSubDir.empty())
1042 : {
1043 0 : delete aoStackSubDir.back();
1044 0 : aoStackSubDir.pop_back();
1045 : }
1046 229 : closedir(m_psDir);
1047 458 : }
1048 :
1049 : /************************************************************************/
1050 : /* OpenDir() */
1051 : /************************************************************************/
1052 :
1053 240 : VSIDIR *VSIUnixStdioFilesystemHandler::OpenDir(const char *pszPath,
1054 : int nRecurseDepth,
1055 : const char *const *papszOptions)
1056 : {
1057 240 : DIR *psDir = opendir(pszPath);
1058 240 : if (psDir == nullptr)
1059 : {
1060 11 : return nullptr;
1061 : }
1062 229 : VSIDIRUnixStdio *dir = new VSIDIRUnixStdio(this);
1063 229 : dir->osRootPath = pszPath;
1064 229 : dir->nRecurseDepth = nRecurseDepth;
1065 229 : dir->m_psDir = psDir;
1066 229 : dir->m_osFilterPrefix = CSLFetchNameValueDef(papszOptions, "PREFIX", "");
1067 229 : dir->m_bNameAndTypeOnly = CPLTestBool(
1068 : CSLFetchNameValueDef(papszOptions, "NAME_AND_TYPE_ONLY", "NO"));
1069 229 : return dir;
1070 : }
1071 :
1072 : /************************************************************************/
1073 : /* NextDirEntry() */
1074 : /************************************************************************/
1075 :
1076 65110 : const VSIDIREntry *VSIDIRUnixStdio::NextDirEntry()
1077 : {
1078 65110 : begin:
1079 65110 : if (VSI_ISDIR(entry.nMode) && nRecurseDepth != 0)
1080 : {
1081 169 : CPLString osCurFile(osRootPath);
1082 169 : if (!osCurFile.empty())
1083 169 : osCurFile += '/';
1084 169 : osCurFile += entry.pszName;
1085 : auto subdir = static_cast<VSIDIRUnixStdio *>(
1086 169 : poFS->VSIUnixStdioFilesystemHandler::OpenDir(
1087 169 : osCurFile, nRecurseDepth - 1, nullptr));
1088 169 : if (subdir)
1089 : {
1090 169 : subdir->osRootPath = osRootPath;
1091 169 : subdir->osBasePath = entry.pszName;
1092 169 : subdir->m_osFilterPrefix = m_osFilterPrefix;
1093 169 : subdir->m_bNameAndTypeOnly = m_bNameAndTypeOnly;
1094 169 : aoStackSubDir.push_back(subdir);
1095 : }
1096 169 : entry.nMode = 0;
1097 : }
1098 :
1099 65279 : while (!aoStackSubDir.empty())
1100 : {
1101 43144 : auto l_entry = aoStackSubDir.back()->NextDirEntry();
1102 43144 : if (l_entry)
1103 : {
1104 42975 : return l_entry;
1105 : }
1106 169 : delete aoStackSubDir.back();
1107 169 : aoStackSubDir.pop_back();
1108 : }
1109 :
1110 : while (true)
1111 : {
1112 22594 : const auto *psEntry = readdir(m_psDir);
1113 22594 : if (psEntry == nullptr)
1114 : {
1115 224 : return nullptr;
1116 : }
1117 : // Skip . and ..entries
1118 22370 : if (psEntry->d_name[0] == '.' &&
1119 481 : (psEntry->d_name[1] == '\0' ||
1120 254 : (psEntry->d_name[1] == '.' && psEntry->d_name[2] == '\0')))
1121 : {
1122 : // do nothing
1123 : }
1124 : else
1125 : {
1126 21916 : CPLFree(entry.pszName);
1127 21916 : CPLString osName(osBasePath);
1128 21916 : if (!osName.empty())
1129 21506 : osName += '/';
1130 21916 : osName += psEntry->d_name;
1131 :
1132 21916 : entry.pszName = CPLStrdup(osName);
1133 21916 : entry.nMode = 0;
1134 21916 : entry.nSize = 0;
1135 21916 : entry.nMTime = 0;
1136 21916 : entry.bModeKnown = false;
1137 21916 : entry.bSizeKnown = false;
1138 21916 : entry.bMTimeKnown = false;
1139 :
1140 21916 : CPLString osCurFile(osRootPath);
1141 21916 : if (!osCurFile.empty())
1142 21916 : osCurFile += '/';
1143 21916 : osCurFile += entry.pszName;
1144 :
1145 : #if !defined(__sun) && !defined(__HAIKU__)
1146 21916 : if (psEntry->d_type == DT_REG)
1147 21728 : entry.nMode = S_IFREG;
1148 188 : else if (psEntry->d_type == DT_DIR)
1149 188 : entry.nMode = S_IFDIR;
1150 0 : else if (psEntry->d_type == DT_LNK)
1151 0 : entry.nMode = S_IFLNK;
1152 : #endif
1153 :
1154 43346 : const auto StatFile = [&osCurFile, this]()
1155 : {
1156 : VSIStatBufL sStatL;
1157 21673 : if (VSIStatL(osCurFile, &sStatL) == 0)
1158 : {
1159 21673 : entry.nMode = sStatL.st_mode;
1160 21673 : entry.nSize = sStatL.st_size;
1161 21673 : entry.nMTime = sStatL.st_mtime;
1162 21673 : entry.bModeKnown = true;
1163 21673 : entry.bSizeKnown = true;
1164 21673 : entry.bMTimeKnown = true;
1165 : }
1166 21673 : };
1167 :
1168 21928 : if (!m_osFilterPrefix.empty() &&
1169 12 : m_osFilterPrefix.size() > osName.size())
1170 : {
1171 6 : if (STARTS_WITH(m_osFilterPrefix.c_str(), osName.c_str()) &&
1172 2 : m_osFilterPrefix[osName.size()] == '/')
1173 : {
1174 : #if !defined(__sun) && !defined(__HAIKU__)
1175 1 : if (psEntry->d_type == DT_UNKNOWN)
1176 : #endif
1177 : {
1178 0 : StatFile();
1179 : }
1180 1 : if (VSI_ISDIR(entry.nMode))
1181 : {
1182 1 : goto begin;
1183 : }
1184 : }
1185 3 : continue;
1186 : }
1187 21920 : if (!m_osFilterPrefix.empty() &&
1188 8 : !STARTS_WITH(osName.c_str(), m_osFilterPrefix.c_str()))
1189 : {
1190 2 : continue;
1191 : }
1192 :
1193 21910 : if (!m_bNameAndTypeOnly
1194 : #if !defined(__sun) && !defined(__HAIKU__)
1195 237 : || psEntry->d_type == DT_UNKNOWN
1196 : #endif
1197 : )
1198 : {
1199 21673 : StatFile();
1200 : }
1201 :
1202 21910 : break;
1203 : }
1204 459 : }
1205 :
1206 21910 : return &(entry);
1207 : }
1208 :
1209 : #ifdef VSI_COUNT_BYTES_READ
1210 : /************************************************************************/
1211 : /* AddToTotal() */
1212 : /************************************************************************/
1213 :
1214 : void VSIUnixStdioFilesystemHandler::AddToTotal(vsi_l_offset nBytes)
1215 : {
1216 : CPLMutexHolder oHolder(&hMutex);
1217 : nTotalBytesRead += nBytes;
1218 : }
1219 :
1220 : #endif
1221 :
1222 : /************************************************************************/
1223 : /* GetCanonicalFilename() */
1224 : /************************************************************************/
1225 :
1226 : #ifdef HAS_CASE_INSENSITIVE_FILE_SYSTEM
1227 : std::string VSIUnixStdioFilesystemHandler::GetCanonicalFilename(
1228 : const std::string &osFilename) const
1229 : {
1230 : char szResolvedPath[PATH_MAX];
1231 : const char *pszFilename = osFilename.c_str();
1232 : if (realpath(pszFilename, szResolvedPath))
1233 : {
1234 : const char *pszFilenameLastPart = strrchr(pszFilename, '/');
1235 : const char *pszResolvedFilenameLastPart = strrchr(szResolvedPath, '/');
1236 : if (pszFilenameLastPart && pszResolvedFilenameLastPart &&
1237 : EQUAL(pszFilenameLastPart, pszResolvedFilenameLastPart))
1238 : {
1239 : std::string osRet;
1240 : osRet.assign(pszFilename, pszFilenameLastPart - pszFilename);
1241 : osRet += pszResolvedFilenameLastPart;
1242 : return osRet;
1243 : }
1244 : return szResolvedPath;
1245 : }
1246 : return osFilename;
1247 : }
1248 : #endif
1249 :
1250 : /************************************************************************/
1251 : /* VSIInstallLargeFileHandler() */
1252 : /************************************************************************/
1253 :
1254 1228 : void VSIInstallLargeFileHandler()
1255 :
1256 : {
1257 1228 : VSIFileManager::InstallHandler("", new VSIUnixStdioFilesystemHandler());
1258 1228 : }
1259 :
1260 : #endif // ndef WIN32
1261 :
1262 : //! @endcond
|