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