#include "rar.hpp" File::File() { hFile=FILE_BAD_HANDLE; *FileName=0; NewFile=false; LastWrite=false; HandleType=FILE_HANDLENORMAL; SkipClose=false; IgnoreReadErrors=false; ErrorType=FILE_SUCCESS; OpenShared=false; AllowDelete=true; AllowExceptions=true; #ifdef _WIN_ALL NoSequentialRead=false; CreateMode=FMF_UNDEFINED; #endif } File::~File() { if (hFile!=FILE_BAD_HANDLE && !SkipClose) if (NewFile) Delete(); else Close(); } void File::operator = (File &SrcFile) { hFile=SrcFile.hFile; NewFile=SrcFile.NewFile; LastWrite=SrcFile.LastWrite; HandleType=SrcFile.HandleType; wcsncpyz(FileName,SrcFile.FileName,ASIZE(FileName)); SrcFile.SkipClose=true; } bool File::Open(const wchar *Name,uint Mode) { ErrorType=FILE_SUCCESS; FileHandle hNewFile; bool OpenShared=File::OpenShared || (Mode & FMF_OPENSHARED)!=0; bool UpdateMode=(Mode & FMF_UPDATE)!=0; bool WriteMode=(Mode & FMF_WRITE)!=0; #ifdef _WIN_ALL uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ; if (UpdateMode) Access|=GENERIC_WRITE; uint ShareMode=(Mode & FMF_OPENEXCLUSIVE) ? 0 : FILE_SHARE_READ; if (OpenShared) ShareMode|=FILE_SHARE_WRITE; uint Flags=NoSequentialRead ? 0:FILE_FLAG_SEQUENTIAL_SCAN; hNewFile=CreateFile(Name,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL); DWORD LastError; if (hNewFile==FILE_BAD_HANDLE) { LastError=GetLastError(); wchar LongName[NM]; if (GetWinLongPath(Name,LongName,ASIZE(LongName))) { hNewFile=CreateFile(LongName,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL); // For archive names longer than 260 characters first CreateFile // (without \\?\) fails and sets LastError to 3 (access denied). // We need the correct "file not found" error code to decide // if we create a new archive or quit with "cannot create" error. // So we need to check the error code after \\?\ CreateFile again, // otherwise we'll fail to create new archives with long names. // But we cannot simply assign the new code to LastError, // because it would break "..\arcname.rar" relative names processing. // First CreateFile returns the correct "file not found" code for such // names, but "\\?\" CreateFile returns ERROR_INVALID_NAME treating // dots as a directory name. So we check only for "file not found" // error here and for other errors use the first CreateFile result. if (GetLastError()==ERROR_FILE_NOT_FOUND) LastError=ERROR_FILE_NOT_FOUND; } } if (hNewFile==FILE_BAD_HANDLE && LastError==ERROR_FILE_NOT_FOUND) ErrorType=FILE_NOTFOUND; #else int flags=UpdateMode ? O_RDWR:(WriteMode ? O_WRONLY:O_RDONLY); #ifdef O_BINARY flags|=O_BINARY; #if defined(_AIX) && defined(_LARGE_FILE_API) flags|=O_LARGEFILE; #endif #endif char NameA[NM]; WideToChar(Name,NameA,ASIZE(NameA)); int handle=open(NameA,flags); #ifdef LOCK_EX #ifdef _OSF_SOURCE extern "C" int flock(int, int); #endif if (!OpenShared && UpdateMode && handle>=0 && flock(handle,LOCK_EX|LOCK_NB)==-1) { close(handle); return false; } #endif if (handle==-1) hNewFile=FILE_BAD_HANDLE; else { #ifdef FILE_USE_OPEN hNewFile=handle; #else hNewFile=fdopen(handle,UpdateMode ? UPDATEBINARY:READBINARY); #endif } if (hNewFile==FILE_BAD_HANDLE && errno==ENOENT) ErrorType=FILE_NOTFOUND; #endif NewFile=false; HandleType=FILE_HANDLENORMAL; SkipClose=false; bool Success=hNewFile!=FILE_BAD_HANDLE; if (Success) { hFile=hNewFile; wcsncpyz(FileName,Name,ASIZE(FileName)); } return Success; } #if !defined(SFX_MODULE) void File::TOpen(const wchar *Name) { if (!WOpen(Name)) ErrHandler.Exit(RARX_OPEN); } #endif bool File::WOpen(const wchar *Name) { if (Open(Name)) return true; ErrHandler.OpenErrorMsg(Name); return false; } bool File::Create(const wchar *Name,uint Mode) { // OpenIndiana based NAS and CIFS shares fail to set the file time if file // was created in read+write mode and some data was written and not flushed // before SetFileTime call. So we should use the write only mode if we plan // SetFileTime call and do not need to read from file. bool WriteMode=(Mode & FMF_WRITE)!=0; bool ShareRead=(Mode & FMF_SHAREREAD)!=0 || File::OpenShared; #ifdef _WIN_ALL CreateMode=Mode; uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ|GENERIC_WRITE; DWORD ShareMode=ShareRead ? FILE_SHARE_READ:0; // Windows automatically removes dots and spaces in the end of file name, // So we detect such names and process them with \\?\ prefix. wchar *LastChar=PointToLastChar(Name); bool Special=*LastChar=='.' || *LastChar==' '; if (Special && (Mode & FMF_STANDARDNAMES)==0) hFile=FILE_BAD_HANDLE; else hFile=CreateFile(Name,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL); if (hFile==FILE_BAD_HANDLE) { wchar LongName[NM]; if (GetWinLongPath(Name,LongName,ASIZE(LongName))) hFile=CreateFile(LongName,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL); } #else char NameA[NM]; WideToChar(Name,NameA,ASIZE(NameA)); #ifdef FILE_USE_OPEN hFile=open(NameA,(O_CREAT|O_TRUNC) | (WriteMode ? O_WRONLY : O_RDWR),0666); #else hFile=fopen(NameA,WriteMode ? WRITEBINARY:CREATEBINARY); #endif #endif NewFile=true; HandleType=FILE_HANDLENORMAL; SkipClose=false; wcsncpyz(FileName,Name,ASIZE(FileName)); return hFile!=FILE_BAD_HANDLE; } #if !defined(SFX_MODULE) void File::TCreate(const wchar *Name,uint Mode) { if (!WCreate(Name,Mode)) ErrHandler.Exit(RARX_FATAL); } #endif bool File::WCreate(const wchar *Name,uint Mode) { if (Create(Name,Mode)) return true; ErrHandler.CreateErrorMsg(Name); return false; } bool File::Close() { bool Success=true; if (hFile!=FILE_BAD_HANDLE) { if (!SkipClose) { #ifdef _WIN_ALL // We use the standard system handle for stdout in Windows // and it must not be closed here. if (HandleType==FILE_HANDLENORMAL) Success=CloseHandle(hFile)==TRUE; #else #ifdef FILE_USE_OPEN Success=close(hFile)!=-1; #else Success=fclose(hFile)!=EOF; #endif #endif } hFile=FILE_BAD_HANDLE; } HandleType=FILE_HANDLENORMAL; if (!Success && AllowExceptions) ErrHandler.CloseError(FileName); return Success; } bool File::Delete() { if (HandleType!=FILE_HANDLENORMAL) return false; if (hFile!=FILE_BAD_HANDLE) Close(); if (!AllowDelete) return false; return DelFile(FileName); } bool File::Rename(const wchar *NewName) { // No need to rename if names are already same. bool Success=wcscmp(FileName,NewName)==0; if (!Success) Success=RenameFile(FileName,NewName); if (Success) wcsncpyz(FileName,NewName,ASIZE(FileName)); return Success; } bool File::Write(const void *Data,size_t Size) { if (Size==0) return true; if (HandleType==FILE_HANDLESTD) { #ifdef _WIN_ALL hFile=GetStdHandle(STD_OUTPUT_HANDLE); #else // Cannot use the standard stdout here, because it already has wide orientation. if (hFile==FILE_BAD_HANDLE) { #ifdef FILE_USE_OPEN hFile=dup(STDOUT_FILENO); // Open new stdout stream. #else hFile=fdopen(dup(STDOUT_FILENO),"w"); // Open new stdout stream. #endif } #endif } bool Success; while (1) { Success=false; #ifdef _WIN_ALL DWORD Written=0; if (HandleType!=FILE_HANDLENORMAL) { // writing to stdout can fail in old Windows if data block is too large const size_t MaxSize=0x4000; for (size_t I=0;ISize && FilePos-Size<=0xffffffff && FilePos+Size>0xffffffff) ErrHandler.WriteErrorFAT(FileName); #endif if (ErrHandler.AskRepeatWrite(FileName,false)) { #if !defined(_WIN_ALL) && !defined(FILE_USE_OPEN) clearerr(hFile); #endif if (Written0) Seek(Tell()-Written,SEEK_SET); continue; } ErrHandler.WriteError(NULL,FileName); } break; } LastWrite=true; return Success; // It can return false only if AllowExceptions is disabled. } int File::Read(void *Data,size_t Size) { int64 FilePos=0; // Initialized only to suppress some compilers warning. if (IgnoreReadErrors) FilePos=Tell(); int ReadSize; while (true) { ReadSize=DirectRead(Data,Size); if (ReadSize==-1) { ErrorType=FILE_READERROR; if (AllowExceptions) if (IgnoreReadErrors) { ReadSize=0; for (size_t I=0;IMaxDeviceRead) // Size=MaxDeviceRead; hFile=GetStdHandle(STD_INPUT_HANDLE); #else #ifdef FILE_USE_OPEN hFile=STDIN_FILENO; #else hFile=stdin; #endif #endif } #ifdef _WIN_ALL // For pipes like 'type file.txt | rar -si arcname' ReadFile may return // data in small ~4KB blocks. It may slightly reduce the compression ratio. DWORD Read; if (!ReadFile(hFile,Data,(DWORD)Size,&Read,NULL)) { if (IsDevice() && Size>MaxDeviceRead) return DirectRead(Data,MaxDeviceRead); if (HandleType==FILE_HANDLESTD && GetLastError()==ERROR_BROKEN_PIPE) return 0; // We had a bug report about failure to archive 1C database lock file // 1Cv8tmp.1CL, which is a zero length file with a region above 200 KB // permanently locked. If our first read request uses too large buffer // and if we are in -dh mode, so we were able to open the file, // we'll fail with "Read error". So now we use try a smaller buffer size // in case of lock error. if (HandleType==FILE_HANDLENORMAL && Size>MaxLockedRead && GetLastError()==ERROR_LOCK_VIOLATION) return DirectRead(Data,MaxLockedRead); return -1; } return Read; #else #ifdef FILE_USE_OPEN ssize_t ReadSize=read(hFile,Data,Size); if (ReadSize==-1) return -1; return (int)ReadSize; #else if (LastWrite) { fflush(hFile); LastWrite=false; } clearerr(hFile); size_t ReadSize=fread(Data,1,Size,hFile); if (ferror(hFile)) return -1; return (int)ReadSize; #endif #endif } void File::Seek(int64 Offset,int Method) { if (!RawSeek(Offset,Method) && AllowExceptions) ErrHandler.SeekError(FileName); } bool File::RawSeek(int64 Offset,int Method) { if (hFile==FILE_BAD_HANDLE) return true; if (Offset<0 && Method!=SEEK_SET) { Offset=(Method==SEEK_CUR ? Tell():FileLength())+Offset; Method=SEEK_SET; } #ifdef _WIN_ALL LONG HighDist=(LONG)(Offset>>32); if (SetFilePointer(hFile,(LONG)Offset,&HighDist,Method)==0xffffffff && GetLastError()!=NO_ERROR) return false; #else LastWrite=false; #ifdef FILE_USE_OPEN if (lseek(hFile,(off_t)Offset,Method)==-1) return false; #elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE) && !defined(__VMS) if (fseeko(hFile,Offset,Method)!=0) return false; #else if (fseek(hFile,(long)Offset,Method)!=0) return false; #endif #endif return true; } int64 File::Tell() { if (hFile==FILE_BAD_HANDLE) if (AllowExceptions) ErrHandler.SeekError(FileName); else return -1; #ifdef _WIN_ALL LONG HighDist=0; uint LowDist=SetFilePointer(hFile,0,&HighDist,FILE_CURRENT); if (LowDist==0xffffffff && GetLastError()!=NO_ERROR) if (AllowExceptions) ErrHandler.SeekError(FileName); else return -1; return INT32TO64(HighDist,LowDist); #else #ifdef FILE_USE_OPEN return lseek(hFile,0,SEEK_CUR); #elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE) return ftello(hFile); #else return ftell(hFile); #endif #endif } void File::Prealloc(int64 Size) { #ifdef _WIN_ALL if (RawSeek(Size,SEEK_SET)) { Truncate(); Seek(0,SEEK_SET); } #endif #if defined(_UNIX) && defined(USE_FALLOCATE) // fallocate is rather new call. Only latest kernels support it. // So we are not using it by default yet. int fd = GetFD(); if (fd >= 0) fallocate(fd, 0, 0, Size); #endif } byte File::GetByte() { byte Byte=0; Read(&Byte,1); return Byte; } void File::PutByte(byte Byte) { Write(&Byte,1); } bool File::Truncate() { #ifdef _WIN_ALL return SetEndOfFile(hFile)==TRUE; #else return ftruncate(GetFD(),(off_t)Tell())==0; #endif } void File::Flush() { #ifdef _WIN_ALL FlushFileBuffers(hFile); #else #ifndef FILE_USE_OPEN fflush(hFile); #endif fsync(GetFD()); #endif } void File::SetOpenFileTime(RarTime *ftm,RarTime *ftc,RarTime *fta) { #ifdef _WIN_ALL // Workaround for OpenIndiana NAS time bug. If we cannot create a file // in write only mode, we need to flush the write buffer before calling // SetFileTime or file time will not be changed. if (CreateMode!=FMF_UNDEFINED && (CreateMode & FMF_WRITE)==0) FlushFileBuffers(hFile); bool sm=ftm!=NULL && ftm->IsSet(); bool sc=ftc!=NULL && ftc->IsSet(); bool sa=fta!=NULL && fta->IsSet(); FILETIME fm,fc,fa; if (sm) ftm->GetWinFT(&fm); if (sc) ftc->GetWinFT(&fc); if (sa) fta->GetWinFT(&fa); SetFileTime(hFile,sc ? &fc:NULL,sa ? &fa:NULL,sm ? &fm:NULL); #endif } void File::SetCloseFileTime(RarTime *ftm,RarTime *fta) { // Android APP_PLATFORM := android-14 does not support futimens and futimes. // Newer platforms support futimens, but fail on Android 4.2. // We have to use utime for Android. // Also we noticed futimens fail to set timestamps on NTFS partition // mounted to virtual Linux x86 machine, but utimensat worked correctly. // So we set timestamps for already closed files in Unix. #ifdef _UNIX SetCloseFileTimeByName(FileName,ftm,fta); #endif } void File::SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta) { #ifdef _UNIX bool setm=ftm!=NULL && ftm->IsSet(); bool seta=fta!=NULL && fta->IsSet(); if (setm || seta) { char NameA[NM]; WideToChar(Name,NameA,ASIZE(NameA)); #ifdef UNIX_TIME_NS timespec times[2]; times[0].tv_sec=seta ? fta->GetUnix() : 0; times[0].tv_nsec=seta ? long(fta->GetUnixNS()%1000000000) : UTIME_NOW; times[1].tv_sec=setm ? ftm->GetUnix() : 0; times[1].tv_nsec=setm ? long(ftm->GetUnixNS()%1000000000) : UTIME_NOW; utimensat(AT_FDCWD,NameA,times,0); #else utimbuf ut; if (setm) ut.modtime=ftm->GetUnix(); else ut.modtime=fta->GetUnix(); // Need to set something, cannot left it 0. if (seta) ut.actime=fta->GetUnix(); else ut.actime=ut.modtime; // Need to set something, cannot left it 0. utime(NameA,&ut); #endif } #endif } void File::GetOpenFileTime(RarTime *ft) { #ifdef _WIN_ALL FILETIME FileTime; GetFileTime(hFile,NULL,NULL,&FileTime); ft->SetWinFT(&FileTime); #endif #if defined(_UNIX) || defined(_EMX) struct stat st; fstat(GetFD(),&st); ft->SetUnix(st.st_mtime); #endif } int64 File::FileLength() { SaveFilePos SavePos(*this); Seek(0,SEEK_END); return Tell(); } bool File::IsDevice() { if (hFile==FILE_BAD_HANDLE) return false; #ifdef _WIN_ALL uint Type=GetFileType(hFile); return Type==FILE_TYPE_CHAR || Type==FILE_TYPE_PIPE; #else return isatty(GetFD()); #endif } #ifndef SFX_MODULE int64 File::Copy(File &Dest,int64 Length) { Array Buffer(File::CopyBufferSize()); int64 CopySize=0; bool CopyAll=(Length==INT64NDF); while (CopyAll || Length>0) { Wait(); size_t SizeToRead=(!CopyAll && Length<(int64)Buffer.Size()) ? (size_t)Length:Buffer.Size(); byte *Buf=&Buffer[0]; int ReadSize=Read(Buf,SizeToRead); if (ReadSize==0) break; size_t WriteSize=ReadSize; #ifdef _WIN_ALL // For FAT32 USB flash drives in Windows if first write is 4 KB or more, // write caching is disabled and "write through" is enabled, resulting // in bad performance, especially for many small files. It happens when // we create SFX archive on USB drive, because SFX module is written first. // So we split the first write to small 1 KB followed by rest of data. if (CopySize==0 && WriteSize>=4096) { const size_t FirstWrite=1024; Dest.Write(Buf,FirstWrite); Buf+=FirstWrite; WriteSize-=FirstWrite; } #endif Dest.Write(Buf,WriteSize); CopySize+=ReadSize; if (!CopyAll) Length-=ReadSize; } return CopySize; } #endif