MemoryMappedFileStreamUnix.cpp //CopyrightEpicGames,Inc.AllRightsReserved. //*INDENT-OFF* #ifdefTRIO_MMAP_AVAILABLE #ifdefTRIO_LARGE_FILE_SUPPORT_AVAILABLE #define_FILE_OFFSET_BITS64 #endif//TRIO_LARGE_FILE_SUPPORT #include"trio/streams/MemoryMappedFileStreamUnix.h" #include"trio/utils/NativeString.h" #include"trio/utils/ScopedEnumEx.h" #include<pma/PolyAllocator.h> #include<fcntl.h> #include<unistd.h> #include<sys/mman.h> #include<sys/stat.h> #include<sys/types.h> #ifdef_MSC_VER #pragmawarning(push) #pragmawarning(disable:43654987) #endif #include<algorithm> #include<cassert> #include<cstdint> #include<cstdio> #include<cstring> #include<ios> #include<limits> #include<type_traits> #ifdef_MSC_VER #pragmawarning(pop) #endif namespacetrio{ namespace{ constexprstd::size_tminViewSizeUnix=65536ul; inlinestd::uint64_tgetFileSizeUnix(constNativeCharacter*path){ structstatst{}; if(::stat(path,&st)!=0){ return0ul; } returnstatic_cast<std::uint64_t>(st.st_size); } inlinestd::uint64_tgetPageSizeUnix(){ #ifdefTRIO_PAGE_SIZE_UNIX returnstatic_cast<std::uint64_t>(TRIO_PAGE_SIZE_UNIX); #else returnstatic_cast<std::uint64_t>(sysconf(_SC_PAGE_SIZE)); #endif//TRIO_PAGE_SIZE_UNIX } inlinestd::uint64_talignOffsetUnix(std::uint64_toffset){ conststd::uint64_tpageSize=getPageSizeUnix(); returnoffset/pageSize*pageSize; } classMemoryReaderUnix:publicReadable{ public: explicitMemoryReaderUnix(constchar*source_):source{source_}{} std::size_tread(char*destination,std::size_tsize)override{ std::memcpy(destination,source,size); source+=size; returnsize; } std::size_tread(Writable*destination,std::size_tsize)override{ destination->write(source,size); source+=size; returnsize; } private: constchar*source; }; classMemoryWriterUnix:publicWritable{ public: explicitMemoryWriterUnix(char*destination_):destination{destination_}{} std::size_twrite(constchar*source,std::size_tsize)override{ std::memcpy(destination,source,size); destination+=size; returnsize; } std::size_twrite(Readable*source,std::size_tsize)override{ source->read(destination,size); destination+=size; returnsize; } private: char*destination; }; }//namespace MemoryMappedFileStreamUnix::MemoryMappedFileStreamUnix(constchar*path_,AccessModeaccessMode_,MemoryResource*memRes_): filePath{NativeStringConverter::from(path_,memRes_)}, fileAccessMode{accessMode_}, memRes{memRes_}, file{-1}, data{nullptr}, position{}, fileSize{getFileSizeUnix(filePath.c_str())}, viewOffset{}, viewSize{}, delayedMapping{false}, dirty{false}{ } MemoryMappedFileStreamUnix::~MemoryMappedFileStreamUnix(){ MemoryMappedFileStreamUnix::close(); } MemoryResource*MemoryMappedFileStreamUnix::getMemoryResource(){ returnmemRes; } std::uint64_tMemoryMappedFileStreamUnix::size(){ returnfileSize; } voidMemoryMappedFileStreamUnix::open(){ status->reset(); if(file!=-1){ status->set(AlreadyOpenError,filePath.c_str()); return; } delayedMapping=false; openFile(); if(file==-1){ status->set(OpenError,filePath.c_str()); return; } structstatst{}; if(::fstat(file,&st)!=0){ fileSize=0ul; closeFile(); status->set(OpenError,filePath.c_str()); return; } fileSize=static_cast<std::uint64_t>(st.st_size); //Mappingof0-lengthfilesisdelayeduntilthefileisresizedtoanon-zerosize. delayedMapping=(fileSize==0ul); if(delayedMapping){ return; } mapFile(0ul,fileSize); if(data==reinterpret_cast<void*>(-1)){ status->set(OpenError,filePath.c_str()); delayedMapping=false; unmapFile(); closeFile(); return; } MemoryMappedFileStreamUnix::seek(0ul); dirty=false; } voidMemoryMappedFileStreamUnix::close(){ delayedMapping=false; flush(); unmapFile(); closeFile(); } std::uint64_tMemoryMappedFileStreamUnix::tell(){ returnposition; } voidMemoryMappedFileStreamUnix::seek(std::uint64_tposition_){ constboolseekable=((position_==0ul)||(position_<=size()))&&(data!=nullptr); if(!seekable){ status->set(SeekError,filePath.c_str()); return; } position=position_; if((position<viewOffset)||(position>=(viewOffset+viewSize))){ flush(); if(dirty){ return; } unmapFile(); mapFile(position,fileSize-position); } } std::size_tMemoryMappedFileStreamUnix::read(char*destination,std::size_tsize){ if(destination==nullptr){ status->set(ReadError,filePath.c_str()); return0ul; } MemoryWriterUnixwriter{destination}; returnread(&writer,size); } std::size_tMemoryMappedFileStreamUnix::read(Writable*destination,std::size_tsize){ if((destination==nullptr)||!contains(fileAccessMode,AccessMode::Read)){ status->set(ReadError,filePath.c_str()); return0ul; } if(data==nullptr){ if(!delayedMapping){ status->set(ReadError,filePath.c_str()); } return0ul; } conststd::uint64_tbytesAvailable=fileSize-position; #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpush #pragmaGCCdiagnosticignored"-Wuseless-cast" #endif conststd::size_tbytesToRead=static_cast<std::size_t>(std::min(static_cast<std::uint64_t>(size),bytesAvailable)); #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpop #endif std::size_tbytesRead=0ul; while(bytesRead!=bytesToRead){ conststd::size_tbytesRemaining=bytesToRead-bytesRead; #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpush #pragmaGCCdiagnosticignored"-Wuseless-cast" #endif std::size_tviewPosition=static_cast<std::size_t>(position-viewOffset); std::size_tbytesReadable=static_cast<std::size_t>(viewSize-viewPosition); #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpop #endif //Iftheviewisexhaustedduringreading,remapanewviewtilltheendoffileifpossible, //startingatthecurrentposition if(bytesReadable==0ul){ unmapFile(); mapFile(position,fileSize-position); if(data==nullptr){ //Failedtomapnewview status->set(ReadError,filePath.c_str()); break; } #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpush #pragmaGCCdiagnosticignored"-Wuseless-cast" #endif viewPosition=static_cast<std::size_t>(position-viewOffset); bytesReadable=static_cast<std::size_t>(viewSize-viewPosition); #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpop #endif } conststd::size_tchunkSize=std::min(bytesRemaining,bytesReadable); conststd::size_tchunkCopied=destination->write(static_cast<char*>(data)+viewPosition,chunkSize); bytesRead+=chunkCopied; position+=chunkCopied; } returnbytesRead; } std::size_tMemoryMappedFileStreamUnix::write(constchar*source,std::size_tsize){ if(source==nullptr){ status->set(WriteError,filePath.c_str()); return0ul; } MemoryReaderUnixreader{source}; returnwrite(&reader,size); } std::size_tMemoryMappedFileStreamUnix::write(Readable*source,std::size_tsize){ if((source==nullptr)||!contains(fileAccessMode,AccessMode::Write)){ status->set(WriteError,filePath.c_str()); return0ul; } if((data==nullptr)&&!delayedMapping){ status->set(WriteError,filePath.c_str()); return0ul; } if(size==0ul){ return0ul; } if(position+size>fileSize){ resize(position+size); if(fileSize!=(position+size)){ //Resizenotsuccessful(resizesetsstatusinsuchcases) return0ul; } } std::size_tbytesWritten=0ul; while(bytesWritten!=size){ conststd::size_tbytesRemaining=size-bytesWritten; #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpush #pragmaGCCdiagnosticignored"-Wuseless-cast" #endif std::size_tviewPosition=static_cast<std::size_t>(position-viewOffset); std::size_tbytesWritable=static_cast<std::size_t>(viewSize-viewPosition); #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpop #endif //Iftheviewisexhaustedduringwriting,remapanewviewtilltheendoffileifpossible, //startingatthecurrentposition if(bytesWritable==0ul){ flush(); if(dirty){ break; } unmapFile(); mapFile(position,fileSize-position); if(data==nullptr){ //Failedtomapnewview status->set(WriteError,filePath.c_str()); break; } #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpush #pragmaGCCdiagnosticignored"-Wuseless-cast" #endif viewPosition=static_cast<std::size_t>(position-viewOffset); bytesWritable=static_cast<std::size_t>(viewSize-viewPosition); #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpop #endif } conststd::size_tchunkSize=std::min(bytesRemaining,bytesWritable); conststd::size_tchunkCopied=source->read(static_cast<char*>(data)+viewPosition,chunkSize); bytesWritten+=chunkCopied; position+=chunkCopied; } dirty=(bytesWritten>0ul); returnbytesWritten; } voidMemoryMappedFileStreamUnix::flush(){ if(data!=nullptr){ if(::msync(data,viewSize,MS_SYNC)!=0){ status->set(WriteError,filePath.c_str()); return; } } dirty=false; } voidMemoryMappedFileStreamUnix::resize(std::uint64_tsize){ if(fileAccessMode==AccessMode::Read){ status->set(WriteError,filePath.c_str()); return; } flush(); if(dirty){ return; } unmapFile(); resizeFile(size); if(fileSize!=size){ status->set(WriteError,filePath.c_str()); return; } //Eithermremapisnotavailable,ortherewasnodatapointertoberemappedinthe //firstplace.Inbothcases,fallbacktommap mapFile(position,fileSize-position); if(data==nullptr){ status->set(WriteError,filePath.c_str()); return; } } voidMemoryMappedFileStreamUnix::openFile(){ intopenFlags{}; if(fileAccessMode==AccessMode::ReadWrite){ openFlags=O_RDWR|O_CREAT; }elseif(fileAccessMode==AccessMode::Read){ openFlags=O_RDONLY; }elseif(fileAccessMode==AccessMode::Write){ //mmapneedsalsoreadpermissiontotheunderlyingfiledescriptor openFlags=O_RDWR|O_CREAT; } constintmode=(S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); file=::open(filePath.c_str(),openFlags,mode); } voidMemoryMappedFileStreamUnix::closeFile(){ if(file!=-1){ ::close(file); file=-1; } } voidMemoryMappedFileStreamUnix::mapFile(std::uint64_toffset,std::uint64_tsize){ intprot{}; prot|=(contains(fileAccessMode,AccessMode::Write)?PROT_WRITE:prot); prot|=(contains(fileAccessMode,AccessMode::Read)?PROT_READ:prot); constintflags=(fileAccessMode==AccessMode::Read?MAP_PRIVATE:MAP_SHARED); conststd::uint64_talignedOffset=alignOffsetUnix(offset); #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpush #pragmaGCCdiagnosticignored"-Wuseless-cast" #endif //Makesuresizedoesnotexceedsystemlimits std::size_tsafeSize=static_cast<std::size_t>(std::min(static_cast<std::uint64_t>(std::numeric_limits<std::size_t>::max()),size)); //Increaseto-be-mappedsizebythedifferencecausedbyalignment(guardagainstwrap-aroundonoverflow) safeSize=std::max(static_cast<std::size_t>(safeSize+(offset-alignedOffset)),safeSize); #if!defined(__clang__)&&defined(__GNUC__) #pragmaGCCdiagnosticpop #endif //Trymappingrequestedsize,butifitfailskeeprepeatingbyhalvingtheviewsizeeachtime(e.g.ifnotenoughVAspace) std::size_tnextSize=safeSize; do{ safeSize=nextSize; data=::mmap(nullptr,safeSize,prot,flags,file,static_cast<off_t>(alignedOffset)); if(data!=reinterpret_cast<void*>(-1)){ break; } nextSize=safeSize/2ul; } while(nextSize>minViewSizeUnix); if(data!=reinterpret_cast<void*>(-1)){ viewOffset=alignedOffset; viewSize=safeSize; } } voidMemoryMappedFileStreamUnix::unmapFile(){ if(data!=nullptr){ ::munmap(data,viewSize); data=nullptr; } viewOffset=0ul; viewSize=0ul; } voidMemoryMappedFileStreamUnix::resizeFile(std::uint64_tsize){ if(file==-1){ return; } if(::ftruncate(file,static_cast<off_t>(size))!=0){ return; } fileSize=size; } }//namespacetrio #endif//TRIO_MMAP_AVAILABLE //*INDENT-ON*