1 //-*****************************************************************************
2 //
3 // Copyright (c) 2013,
4 // Sony Pictures Imageworks Inc. and
5 // Industrial Light & Magic, a division of Lucasfilm Entertainment Company Ltd.
6 //
7 // All rights reserved.
8 //
9 // Redistribution and use in source and binary forms, with or without
10 // modification, are permitted provided that the following conditions are
11 // met:
12 // * Redistributions of source code must retain the above copyright
13 // notice, this list of conditions and the following disclaimer.
14 // * Redistributions in binary form must reproduce the above
15 // copyright notice, this list of conditions and the following disclaimer
16 // in the documentation and/or other materials provided with the
17 // distribution.
18 // * Neither the name of Industrial Light & Magic nor the names of
19 // its contributors may be used to endorse or promote products derived
20 // from this software without specific prior written permission.
21 //
22 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 //
34 //-*****************************************************************************
35
36 #include <Alembic/Ogawa/IStreams.h>
37 #include <fstream>
38 #include <stdexcept>
39
40
41 #if defined (__unix__) || defined (__HAIKU__) || \
42 (defined (__APPLE__) && defined (__MACH__))
43
44 #include <sys/mman.h>
45 #include <sys/stat.h>
46 #include <fcntl.h>
47 #include <unistd.h>
48 #include <errno.h>
49 #include <cstring>
50
51 #elif defined(_WIN32)
52
53 #include <windows.h>
54 #include <fcntl.h>
55 #include <io.h>
56 #include <sys/stat.h>
57
58 #else
59 #error Platform not supported.
60 #endif
61
62
63
64
65 namespace Alembic {
66 namespace Ogawa {
67 namespace ALEMBIC_VERSION_NS
68 {
69
70 namespace
71 {
72
73 class IStreamReader
74 {
75 public:
~IStreamReader()76 virtual ~IStreamReader() {}
77
78 virtual std::size_t numStreams() const = 0;
79
80 virtual bool isOpen() const = 0;
81
82 virtual bool read(std::size_t iThreadId, Alembic::Util::uint64_t iPos,
83 Alembic::Util::uint64_t iSize, void* oBuf) = 0;
84
85 // not all streams have a size
size()86 virtual Alembic::Util::uint64_t size() {return 0xffffffffffffffff;};
87 };
88
89 typedef Alembic::Util::shared_ptr<IStreamReader> IStreamReaderPtr;
90
91
92 class StdIStreamReader : public IStreamReader
93 {
94 public:
StdIStreamReader(const std::vector<std::istream * > & iStreams)95 StdIStreamReader(const std::vector<std::istream*>& iStreams) : streams(
96 iStreams)
97 {
98 locks = new Alembic::Util::mutex[streams.size()];
99
100 // preserve the initial position of these streams
101 offsets.reserve(streams.size());
102 for (size_t i = 0; i < streams.size(); i++)
103 {
104 offsets.push_back(streams[i]->tellg());
105 }
106 }
107
~StdIStreamReader()108 ~StdIStreamReader()
109 {
110 delete[] locks;
111 }
112
numStreams() const113 size_t numStreams() const
114 {
115 return streams.size();
116 }
117
isOpen() const118 bool isOpen() const
119 {
120 return !streams.empty();
121 }
122
read(std::size_t iTheadId,Alembic::Util::uint64_t iPos,Alembic::Util::uint64_t iSize,void * oBuf)123 bool read(std::size_t iTheadId, Alembic::Util::uint64_t iPos,
124 Alembic::Util::uint64_t iSize, void* oBuf)
125 {
126 std::size_t streamIndex = 0;
127 if (iTheadId < streams.size())
128 {
129 streamIndex = iTheadId;
130 }
131
132 Alembic::Util::scoped_lock l(locks[streamIndex]);
133 std::istream* stream = streams[streamIndex];
134
135 stream->seekg(iPos + offsets[streamIndex]);
136 if (!stream->good()) return false;
137
138 stream->read(static_cast<char*>(oBuf), iSize);
139 if (!stream->good()) return false;
140
141 return true;
142 }
143
144 private:
145 std::vector<std::istream*> streams;
146 std::vector<Alembic::Util::uint64_t> offsets;
147 Alembic::Util::mutex* locks;
148 };
149
150
151 class FileIStreamReader : public IStreamReader
152 {
153 private:
154
155 // Platform support functions for file access
156 #ifdef _WIN32
157 typedef int FileDescriptor;
158
openFile(const char * iFileName,Alembic::Util::int32_t iFlag)159 static FileDescriptor openFile(const char * iFileName,
160 Alembic::Util::int32_t iFlag)
161 {
162 FileDescriptor fid = -1;
163
164 // One way to prevent writing over a file opened for reading would be to
165 // pass in _SH_DENYWR instead of _SH_DENYNO. If we can find a posix
166 // equivalent we may have an interesting solution for that problem.
167 _sopen_s(&fid, iFileName, iFlag | _O_RANDOM, _SH_DENYNO, _S_IREAD);
168 return fid;
169 }
170
closeFile(FileDescriptor iFid)171 static void closeFile(FileDescriptor iFid)
172 {
173 if (iFid > -1)
174 {
175 _close(iFid);
176 }
177 }
178
getFileLength(FileDescriptor iFile,Alembic::Util::uint64_t & oLength)179 static int getFileLength(FileDescriptor iFile, Alembic::Util::uint64_t & oLength)
180 {
181 struct __stat64 buf;
182
183 int err = _fstat64(iFile, &buf);
184 if (err < 0) return -1;
185 if (buf.st_size < 0) return -1;
186
187 oLength = static_cast<Alembic::Util::uint64_t>(buf.st_size);
188 return 0;
189 }
190
readFile(FileDescriptor iFid,void * oBuf,Alembic::Util::uint64_t iOffset,Alembic::Util::uint64_t iSize)191 static bool readFile(FileDescriptor iFid,
192 void * oBuf,
193 Alembic::Util::uint64_t iOffset,
194 Alembic::Util::uint64_t iSize)
195 {
196 void * buf = oBuf;
197 Alembic::Util::uint64_t offset = iOffset;
198 Alembic::Util::uint64_t totalRead = 0;
199
200 HANDLE hFile = reinterpret_cast<HANDLE>(_get_osfhandle(iFid));
201 DWORD numRead = 0;
202 do
203 {
204 DWORD numToRead = 0;
205 if ((iSize - totalRead) > MAXDWORD)
206 {
207 numToRead = MAXDWORD;
208 }
209 else
210 {
211 numToRead = static_cast<DWORD>(iSize - totalRead);
212 }
213
214 OVERLAPPED overlapped;
215 memset( &overlapped, 0, sizeof(overlapped));
216 overlapped.Offset = static_cast<DWORD>(offset);
217 overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
218
219 if (!ReadFile(hFile, buf, numToRead, &numRead, &overlapped))
220 {
221 return false;
222 }
223 totalRead += numRead;
224 offset += numRead;
225 buf = static_cast< char * >( buf ) + numRead;
226 }
227 while(numRead > 0 && totalRead < iSize);
228
229 // if we couldn't read what we needed to then something went wrong
230 if (totalRead < iSize)
231 {
232 return false;
233 }
234
235 return true;
236 }
237
238 #else
239 typedef int FileDescriptor;
240
241 static FileDescriptor
242 openFile(const char* iFileName, Alembic::Util::int32_t iFlag)
243 {
244 return open(iFileName, iFlag);
245 }
246
247 static void closeFile(FileDescriptor iFid)
248 {
249 if (iFid > -1)
250 {
251 close(iFid);
252 }
253 }
254
255 static int getFileLength(FileDescriptor iFile, Alembic::Util::uint64_t & oLength)
256 {
257 struct stat buf;
258
259 int err = fstat(iFile, &buf);
260 if (err < 0) return -1;
261 if (buf.st_size < 0) return -1;
262
263 oLength = static_cast<size_t>(buf.st_size);
264 return 0;
265 }
266
267 static bool
268 readFile(FileDescriptor iFid, void* oBuf, Alembic::Util::uint64_t iOffset,
269 Alembic::Util::uint64_t iSize)
270 {
271 Alembic::Util::uint64_t totalRead = 0;
272 void* buf = oBuf;
273 off_t offset = iOffset;
274
275 ssize_t numRead = 0;
276 do
277 {
278 Alembic::Util::uint64_t readCount = iSize - totalRead;
279 // if over 1 GB read it 1 GB chunk at a time to accomodate OSX
280 if (readCount > 1073741824)
281 {
282 readCount = 1073741824;
283 }
284 numRead = pread(iFid, buf, readCount, offset);
285 if (numRead > 0)
286 {
287 totalRead += numRead;
288 offset += numRead;
289 buf = static_cast< char* >( buf ) + numRead;
290 }
291
292 if (numRead < 0 && errno != EINTR)
293 {
294 return false;
295 }
296 } while (numRead > 0 && totalRead < iSize);
297
298 // if we couldn't read what we needed to then something went wrong
299 if (totalRead < iSize)
300 {
301 return false;
302 }
303
304 return true;
305 }
306
307 #endif
308
309 public:
FileIStreamReader(const std::string & iFileName,std::size_t iNumStreams)310 FileIStreamReader(const std::string& iFileName, std::size_t iNumStreams)
311 : nstreams(iNumStreams)
312 {
313 fid = openFile(iFileName.c_str(), O_RDONLY);
314 fileLen = 0;
315 if (getFileLength(fid, fileLen) < 0)
316 {
317 fileLen = 0;
318 }
319 // don't check the return value here
320 // IStream::init() will check isOpen
321 }
322
~FileIStreamReader()323 ~FileIStreamReader()
324 {
325 closeFile(fid);
326 }
327
numStreams() const328 size_t numStreams() const
329 {
330 return nstreams;
331 }
332
isOpen() const333 bool isOpen() const
334 {
335 return (fid > -1);
336 }
337
size()338 Alembic::Util::uint64_t size()
339 {
340 return fileLen;
341 }
342
read(std::size_t,Alembic::Util::uint64_t iPos,Alembic::Util::uint64_t iSize,void * oBuf)343 bool read(std::size_t /*iTheadId*/, Alembic::Util::uint64_t iPos,
344 Alembic::Util::uint64_t iSize, void* oBuf)
345 {
346 // Ignore the iThread. There's no need to lock.
347 if (!isOpen()) return false;
348
349 if (fileLen < iSize && fileLen < iSize + iPos)
350 {
351 return false;
352 }
353
354 return readFile(fid, oBuf, iPos, iSize);
355 }
356
357 private:
358 FileDescriptor fid;
359 size_t nstreams;
360 Alembic::Util::uint64_t fileLen;
361 };
362
363
364
365 class MemoryMappedIStreamReader : public IStreamReader
366 {
367 private:
368
369 #ifndef _WIN32
370 typedef int FileHandle;
371 #define BAD_FILE_HANDLE (-1)
372
openFile(const std::string & iFileName)373 static FileHandle openFile(const std::string& iFileName)
374 {
375 int err = open(iFileName.c_str(), O_RDONLY);
376 return err < 0 ? BAD_FILE_HANDLE : err;
377 }
378
closeFile(FileHandle iFile)379 static void closeFile(FileHandle iFile)
380 {
381 if (iFile != BAD_FILE_HANDLE)
382 {
383 close(iFile);
384 }
385 }
386
getFileLength(FileHandle iFile,size_t & oLength)387 static int getFileLength(FileHandle iFile, size_t& oLength)
388 {
389 struct stat buf;
390
391 int err = fstat(iFile, &buf);
392 if (err < 0) return -1;
393 if (buf.st_size < 0) return -1;
394
395 oLength = static_cast<size_t>(buf.st_size);
396 return 0;
397 }
398
399 struct MappedRegion
400 {
401 size_t len;
402 void* p;
403
MappedRegionAlembic::Ogawa::ALEMBIC_VERSION_NS::__anonfaff58000111::MemoryMappedIStreamReader::MappedRegion404 MappedRegion() : len(0), p(NULL)
405 {
406 }
407
~MappedRegionAlembic::Ogawa::ALEMBIC_VERSION_NS::__anonfaff58000111::MemoryMappedIStreamReader::MappedRegion408 ~MappedRegion()
409 {
410 close();
411 }
412
isMappedAlembic::Ogawa::ALEMBIC_VERSION_NS::__anonfaff58000111::MemoryMappedIStreamReader::MappedRegion413 bool isMapped() const
414 {
415 return p != NULL;
416 }
417
mapAlembic::Ogawa::ALEMBIC_VERSION_NS::__anonfaff58000111::MemoryMappedIStreamReader::MappedRegion418 void map(FileHandle iFile, size_t iLength)
419 {
420 close();
421
422 void* m = mmap(NULL, iLength, PROT_READ, MAP_PRIVATE, iFile, 0);
423 if (m == MAP_FAILED) return;
424
425 p = m;
426 len = iLength;
427 }
428
closeAlembic::Ogawa::ALEMBIC_VERSION_NS::__anonfaff58000111::MemoryMappedIStreamReader::MappedRegion429 void close()
430 {
431 if (p)
432 {
433 munmap(p, len);
434 p = NULL;
435 }
436 }
437
438 };
439
440 #else // _WIN32 defined
441 typedef HANDLE FileHandle;
442 #define BAD_FILE_HANDLE (INVALID_HANDLE_VALUE)
443
444 static FileHandle openFile(const std::string& iFileName)
445 {
446 // Use both FILE_SHARE_READ and FILE_SHARE_WRITE as the share mode.
447 // Without FILE_SHARE_WRITE, this will fail when trying to open a file that is already open for writing.
448 return CreateFile(iFileName.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
449 }
450
451 static void closeFile(FileHandle iFile)
452 {
453 if (iFile != INVALID_HANDLE_VALUE)
454 {
455 CloseHandle(iFile);
456 }
457 }
458
459 static int getFileLength(FileHandle iFile, size_t& oLength)
460 {
461 LARGE_INTEGER length;
462 length.QuadPart = 0;
463
464 BOOL success = GetFileSizeEx(iFile, &length);
465 if (!success) return -1;
466 if (length.QuadPart < 0) return -1;
467
468 oLength = static_cast<size_t>(length.QuadPart);
469 return 0;
470 }
471
472 struct MappedRegion
473 {
474 Alembic::Util::uint64_t len;
475 void* p;
476
477 MappedRegion() : len(0), p(NULL)
478 {
479 }
480
481 ~MappedRegion()
482 {
483 close();
484 }
485
486 bool isMapped() const
487 {
488 return p != NULL;
489 }
490
491 void map(FileHandle iFile, Alembic::Util::uint64_t iLength)
492 {
493 close();
494
495 DWORD sizeHigh = static_cast<DWORD>(iLength >> 32);
496 DWORD sizeLow = static_cast<DWORD>(iLength);
497 HANDLE mapping = CreateFileMapping(iFile, NULL, PAGE_READONLY, sizeHigh, sizeLow, NULL);
498 if (mapping == NULL) return;
499
500 LPVOID view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, iLength);
501
502 // regardless of whether we have a successful view, close the mapping
503 // the underlying file mapping will remain open as long as the view is open
504 CloseHandle(mapping);
505
506 if (view != NULL)
507 {
508 p = view;
509 len = iLength;
510 }
511 }
512
513 void close()
514 {
515 if (p)
516 {
517 UnmapViewOfFile(p);
518 p = NULL;
519 }
520 }
521
522 };
523 #endif
524
525
526 public:
MemoryMappedIStreamReader(const std::string & iFileName,std::size_t iNumStreams)527 MemoryMappedIStreamReader(const std::string& iFileName,
528 std::size_t iNumStreams)
529 : nstreams(iNumStreams), fileName(iFileName),
530 fileHandle(BAD_FILE_HANDLE)
531 {
532 fileHandle = openFile(iFileName);
533 if (fileHandle == BAD_FILE_HANDLE) return;
534
535 size_t len = 0;
536 int err = getFileLength(fileHandle, len);
537 if (err < 0) return;
538
539 mappedRegion.map(fileHandle, len);
540 }
541
~MemoryMappedIStreamReader()542 ~MemoryMappedIStreamReader()
543 {
544 mappedRegion.close();
545 closeFile(fileHandle);
546 }
547
isOpen() const548 bool isOpen() const
549 {
550 return mappedRegion.isMapped();
551 }
552
numStreams() const553 size_t numStreams() const
554 {
555 // memory mapped files support 'unlimited' streams, but just report
556 // the number of streams we were opened with
557 return nstreams;
558 }
559
size()560 Alembic::Util::uint64_t size()
561 {
562 return static_cast<Alembic::Util::uint64_t>(mappedRegion.len);
563 }
564
read(std::size_t iStream,Alembic::Util::uint64_t iPos,Alembic::Util::uint64_t iSize,void * oBuf)565 bool read(std::size_t iStream, Alembic::Util::uint64_t iPos,
566 Alembic::Util::uint64_t iSize, void* oBuf)
567 {
568 if (iSize > mappedRegion.len || iPos > mappedRegion.len || iPos + iSize > mappedRegion.len) return false;
569
570 const char* p = static_cast<const char*>(mappedRegion.p) + iPos;
571 std::memcpy(oBuf, p, iSize);
572
573 return true;
574 }
575
576 private:
577 std::size_t nstreams;
578 std::string fileName;
579 FileHandle fileHandle;
580 MappedRegion mappedRegion;
581 };
582
583
constructStreamReader(const std::string & iFileName,std::size_t iNumStreams,bool iUseMMap)584 IStreamReaderPtr constructStreamReader(
585 const std::string & iFileName,
586 std::size_t iNumStreams,
587 bool iUseMMap)
588 {
589 // if allowed by the options, use memory mapped file access
590 if (iUseMMap)
591 {
592 return IStreamReaderPtr(
593 new MemoryMappedIStreamReader(iFileName, iNumStreams));
594 }
595
596 // otherwise, use file streams
597 return IStreamReaderPtr(new FileIStreamReader(iFileName, iNumStreams));
598 }
599
constructStreamReader(const std::vector<std::istream * > & iStreams)600 IStreamReaderPtr constructStreamReader(
601 const std::vector< std::istream * > & iStreams)
602 {
603 // This construction method only supports the std::istream reader
604 return IStreamReaderPtr(new StdIStreamReader(iStreams));
605 }
606
607
608 } // anonymous namespace
609
610
611
612 class IStreams::PrivateData
613 {
614 public:
PrivateData()615 PrivateData()
616 {
617 valid = false;
618 frozen = false;
619 version = 0;
620 size = 0;
621 }
622
init(IStreamReaderPtr iReader,size_t iNumStreams)623 void init(IStreamReaderPtr iReader, size_t iNumStreams)
624 {
625 // simple temporary endian check
626 union
627 {
628 Util::uint32_t l;
629 char c[4];
630 } u;
631
632 u.l = 0x01234567;
633
634 if (u.c[0] != 0x67)
635 {
636 throw std::runtime_error(
637 "Ogawa currently only supports little-endian reading.");
638 }
639
640 if (iNumStreams == 0 || iReader == NULL || !iReader->isOpen()) return;
641
642 Alembic::Util::uint64_t firstGroupPos = 0;
643
644 for (std::size_t i = 0; i < iNumStreams; ++i)
645 {
646 char header[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
647
648 iReader->read(i, 0, 16, static_cast<void*>(header));
649 std::string magicStr(header, 5);
650 if (magicStr != "Ogawa")
651 {
652 frozen = false;
653 valid = false;
654 version = 0;
655 return;
656 }
657 bool filefrozen = (header[5] == char(0xff));
658 Alembic::Util::uint16_t fileversion = (header[6] << 8) | header[7];
659 Alembic::Util::uint64_t groupPos = *((Alembic::Util::uint64_t*) (&(header[8])));
660 Alembic::Util::uint64_t filesize = iReader->size();
661
662 if (i == 0)
663 {
664 firstGroupPos = groupPos;
665 frozen = filefrozen;
666 version = fileversion;
667 size = filesize;
668 }
669 // all the streams have to agree, or we are invalid
670 else if (firstGroupPos != groupPos || frozen != filefrozen ||
671 version != fileversion || size != filesize)
672 {
673 frozen = false;
674 valid = false;
675 version = 0;
676 return;
677 }
678 }
679
680 // if we reach here, and we're a known version, then we're valid
681 if (version == 1)
682 {
683 reader = iReader; // preserve the reader
684 valid = true;
685 }
686 }
687
688
689 bool valid;
690 bool frozen;
691 Alembic::Util::uint16_t version;
692 Alembic::Util::uint64_t size;
693
694 IStreamReaderPtr reader;
695 };
696
IStreams(const std::string & iFileName,std::size_t iNumStreams,bool iUseMMap)697 IStreams::IStreams(const std::string & iFileName, std::size_t iNumStreams,
698 bool iUseMMap) :
699 mData(new IStreams::PrivateData())
700 {
701 IStreamReaderPtr reader = constructStreamReader(iFileName, iNumStreams,
702 iUseMMap);
703 mData->init(reader, 1);
704 }
705
IStreams(const std::vector<std::istream * > & iStreams)706 IStreams::IStreams(const std::vector< std::istream * > & iStreams) :
707 mData(new IStreams::PrivateData())
708 {
709 IStreamReaderPtr reader = constructStreamReader(iStreams);
710 mData->init(reader, reader->numStreams());
711 }
712
~IStreams()713 IStreams::~IStreams()
714 {
715 }
716
isValid()717 bool IStreams::isValid()
718 {
719 return mData->valid;
720 }
721
isFrozen()722 bool IStreams::isFrozen()
723 {
724 return mData->frozen;
725 }
726
getVersion()727 Alembic::Util::uint16_t IStreams::getVersion()
728 {
729 return mData->version;
730 }
731
getSize()732 Alembic::Util::uint64_t IStreams::getSize()
733 {
734 return mData->size;
735 }
736
read(std::size_t iThreadId,Alembic::Util::uint64_t iPos,Alembic::Util::uint64_t iSize,void * oBuf)737 void IStreams::read(std::size_t iThreadId, Alembic::Util::uint64_t iPos,
738 Alembic::Util::uint64_t iSize, void * oBuf)
739 {
740 if (!isValid())
741 {
742 return;
743 }
744
745 bool success = mData->reader->read(iThreadId, iPos, iSize, oBuf);
746 if (!success)
747 {
748 throw std::runtime_error(
749 "Ogawa IStreams::read failed.");
750 }
751 }
752
753 } // End namespace ALEMBIC_VERSION_NS
754 } // End namespace Ogawa
755 } // End namespace Alembic
756