1 #include "rar.hpp"
2
File()3 File::File()
4 {
5 hFile=FILE_BAD_HANDLE;
6 *FileName=0;
7 NewFile=false;
8 LastWrite=false;
9 HandleType=FILE_HANDLENORMAL;
10 SkipClose=false;
11 IgnoreReadErrors=false;
12 ErrorType=FILE_SUCCESS;
13 OpenShared=false;
14 AllowDelete=true;
15 AllowExceptions=true;
16 #ifdef _WIN_ALL
17 NoSequentialRead=false;
18 CreateMode=FMF_UNDEFINED;
19 #endif
20 }
21
22
~File()23 File::~File()
24 {
25 if (hFile!=FILE_BAD_HANDLE && !SkipClose)
26 if (NewFile)
27 Delete();
28 else
29 Close();
30 }
31
32
operator =(File & SrcFile)33 void File::operator = (File &SrcFile)
34 {
35 hFile=SrcFile.hFile;
36 NewFile=SrcFile.NewFile;
37 LastWrite=SrcFile.LastWrite;
38 HandleType=SrcFile.HandleType;
39 wcsncpyz(FileName,SrcFile.FileName,ASIZE(FileName));
40 SrcFile.SkipClose=true;
41 }
42
43
Open(const wchar * Name,uint Mode)44 bool File::Open(const wchar *Name,uint Mode)
45 {
46 ErrorType=FILE_SUCCESS;
47 FileHandle hNewFile;
48 bool OpenShared=File::OpenShared || (Mode & FMF_OPENSHARED)!=0;
49 bool UpdateMode=(Mode & FMF_UPDATE)!=0;
50 bool WriteMode=(Mode & FMF_WRITE)!=0;
51 #ifdef _WIN_ALL
52 uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ;
53 if (UpdateMode)
54 Access|=GENERIC_WRITE;
55 uint ShareMode=(Mode & FMF_OPENEXCLUSIVE) ? 0 : FILE_SHARE_READ;
56 if (OpenShared)
57 ShareMode|=FILE_SHARE_WRITE;
58 uint Flags=NoSequentialRead ? 0:FILE_FLAG_SEQUENTIAL_SCAN;
59 hNewFile=CreateFile(Name,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL);
60
61 DWORD LastError;
62 if (hNewFile==FILE_BAD_HANDLE)
63 {
64 LastError=GetLastError();
65
66 wchar LongName[NM];
67 if (GetWinLongPath(Name,LongName,ASIZE(LongName)))
68 {
69 hNewFile=CreateFile(LongName,Access,ShareMode,NULL,OPEN_EXISTING,Flags,NULL);
70
71 // For archive names longer than 260 characters first CreateFile
72 // (without \\?\) fails and sets LastError to 3 (access denied).
73 // We need the correct "file not found" error code to decide
74 // if we create a new archive or quit with "cannot create" error.
75 // So we need to check the error code after \\?\ CreateFile again,
76 // otherwise we'll fail to create new archives with long names.
77 // But we cannot simply assign the new code to LastError,
78 // because it would break "..\arcname.rar" relative names processing.
79 // First CreateFile returns the correct "file not found" code for such
80 // names, but "\\?\" CreateFile returns ERROR_INVALID_NAME treating
81 // dots as a directory name. So we check only for "file not found"
82 // error here and for other errors use the first CreateFile result.
83 if (GetLastError()==ERROR_FILE_NOT_FOUND)
84 LastError=ERROR_FILE_NOT_FOUND;
85 }
86 }
87
88 if (hNewFile==FILE_BAD_HANDLE && LastError==ERROR_FILE_NOT_FOUND)
89 ErrorType=FILE_NOTFOUND;
90 #else
91 int flags=UpdateMode ? O_RDWR:(WriteMode ? O_WRONLY:O_RDONLY);
92 #ifdef O_BINARY
93 flags|=O_BINARY;
94 #if defined(_AIX) && defined(_LARGE_FILE_API)
95 flags|=O_LARGEFILE;
96 #endif
97 #endif
98 char NameA[NM];
99 WideToChar(Name,NameA,ASIZE(NameA));
100
101 int handle=open(NameA,flags);
102 #ifdef LOCK_EX
103
104 #ifdef _OSF_SOURCE
105 extern "C" int flock(int, int);
106 #endif
107
108 if (!OpenShared && UpdateMode && handle>=0 && flock(handle,LOCK_EX|LOCK_NB)==-1)
109 {
110 close(handle);
111 return false;
112 }
113 #endif
114 if (handle==-1)
115 hNewFile=FILE_BAD_HANDLE;
116 else
117 {
118 #ifdef FILE_USE_OPEN
119 hNewFile=handle;
120 #else
121 hNewFile=fdopen(handle,UpdateMode ? UPDATEBINARY:READBINARY);
122 #endif
123 }
124 if (hNewFile==FILE_BAD_HANDLE && errno==ENOENT)
125 ErrorType=FILE_NOTFOUND;
126 #endif
127 NewFile=false;
128 HandleType=FILE_HANDLENORMAL;
129 SkipClose=false;
130 bool Success=hNewFile!=FILE_BAD_HANDLE;
131 if (Success)
132 {
133 hFile=hNewFile;
134 wcsncpyz(FileName,Name,ASIZE(FileName));
135 }
136 return Success;
137 }
138
139
140 #if !defined(SHELL_EXT) && !defined(SFX_MODULE)
TOpen(const wchar * Name)141 void File::TOpen(const wchar *Name)
142 {
143 if (!WOpen(Name))
144 ErrHandler.Exit(RARX_OPEN);
145 }
146 #endif
147
148
WOpen(const wchar * Name)149 bool File::WOpen(const wchar *Name)
150 {
151 if (Open(Name))
152 return true;
153 ErrHandler.OpenErrorMsg(Name);
154 return false;
155 }
156
157
Create(const wchar * Name,uint Mode)158 bool File::Create(const wchar *Name,uint Mode)
159 {
160 // OpenIndiana based NAS and CIFS shares fail to set the file time if file
161 // was created in read+write mode and some data was written and not flushed
162 // before SetFileTime call. So we should use the write only mode if we plan
163 // SetFileTime call and do not need to read from file.
164 bool WriteMode=(Mode & FMF_WRITE)!=0;
165 bool ShareRead=(Mode & FMF_SHAREREAD)!=0 || File::OpenShared;
166 #ifdef _WIN_ALL
167 CreateMode=Mode;
168 uint Access=WriteMode ? GENERIC_WRITE:GENERIC_READ|GENERIC_WRITE;
169 DWORD ShareMode=ShareRead ? FILE_SHARE_READ:0;
170
171 // Windows automatically removes dots and spaces in the end of file name,
172 // So we detect such names and process them with \\?\ prefix.
173 wchar *LastChar=PointToLastChar(Name);
174 bool Special=*LastChar=='.' || *LastChar==' ';
175
176 if (Special)
177 hFile=FILE_BAD_HANDLE;
178 else
179 hFile=CreateFile(Name,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL);
180
181 if (hFile==FILE_BAD_HANDLE)
182 {
183 wchar LongName[NM];
184 if (GetWinLongPath(Name,LongName,ASIZE(LongName)))
185 hFile=CreateFile(LongName,Access,ShareMode,NULL,CREATE_ALWAYS,0,NULL);
186 }
187
188 #else
189 char NameA[NM];
190 WideToChar(Name,NameA,ASIZE(NameA));
191 #ifdef FILE_USE_OPEN
192 hFile=open(NameA,(O_CREAT|O_TRUNC) | (WriteMode ? O_WRONLY : O_RDWR));
193 #ifdef _ANDROID
194 if (hFile==FILE_BAD_HANDLE)
195 hFile=JniCreateFile(Name); // If external card is read-only for usual file API.
196 #endif
197 #else
198 hFile=fopen(NameA,WriteMode ? WRITEBINARY:CREATEBINARY);
199 #endif
200 #endif
201 NewFile=true;
202 HandleType=FILE_HANDLENORMAL;
203 SkipClose=false;
204 wcsncpyz(FileName,Name,ASIZE(FileName));
205 return hFile!=FILE_BAD_HANDLE;
206 }
207
208
209 #if !defined(SHELL_EXT) && !defined(SFX_MODULE)
TCreate(const wchar * Name,uint Mode)210 void File::TCreate(const wchar *Name,uint Mode)
211 {
212 if (!WCreate(Name,Mode))
213 ErrHandler.Exit(RARX_FATAL);
214 }
215 #endif
216
217
WCreate(const wchar * Name,uint Mode)218 bool File::WCreate(const wchar *Name,uint Mode)
219 {
220 if (Create(Name,Mode))
221 return true;
222 ErrHandler.CreateErrorMsg(Name);
223 return false;
224 }
225
226
Close()227 bool File::Close()
228 {
229 bool Success=true;
230
231 if (hFile!=FILE_BAD_HANDLE)
232 {
233 if (!SkipClose)
234 {
235 #ifdef _WIN_ALL
236 // We use the standard system handle for stdout in Windows
237 // and it must not be closed here.
238 if (HandleType==FILE_HANDLENORMAL)
239 Success=CloseHandle(hFile)==TRUE;
240 #else
241 #ifdef FILE_USE_OPEN
242 Success=close(hFile)!=-1;
243 #else
244 Success=fclose(hFile)!=EOF;
245 #endif
246 #endif
247 }
248 hFile=FILE_BAD_HANDLE;
249 }
250 HandleType=FILE_HANDLENORMAL;
251 if (!Success && AllowExceptions)
252 ErrHandler.CloseError(FileName);
253 return Success;
254 }
255
256
Delete()257 bool File::Delete()
258 {
259 if (HandleType!=FILE_HANDLENORMAL)
260 return false;
261 if (hFile!=FILE_BAD_HANDLE)
262 Close();
263 if (!AllowDelete)
264 return false;
265 return DelFile(FileName);
266 }
267
268
Rename(const wchar * NewName)269 bool File::Rename(const wchar *NewName)
270 {
271 // No need to rename if names are already same.
272 bool Success=wcscmp(FileName,NewName)==0;
273
274 if (!Success)
275 Success=RenameFile(FileName,NewName);
276
277 if (Success)
278 wcscpy(FileName,NewName);
279
280 return Success;
281 }
282
283
Write(const void * Data,size_t Size)284 bool File::Write(const void *Data,size_t Size)
285 {
286 if (Size==0)
287 return true;
288 if (HandleType==FILE_HANDLESTD)
289 {
290 #ifdef _WIN_ALL
291 hFile=GetStdHandle(STD_OUTPUT_HANDLE);
292 #else
293 // Cannot use the standard stdout here, because it already has wide orientation.
294 if (hFile==FILE_BAD_HANDLE)
295 {
296 #ifdef FILE_USE_OPEN
297 hFile=dup(STDOUT_FILENO); // Open new stdout stream.
298 #else
299 hFile=fdopen(dup(STDOUT_FILENO),"w"); // Open new stdout stream.
300 #endif
301 }
302 #endif
303 }
304 bool Success;
305 while (1)
306 {
307 Success=false;
308 #ifdef _WIN_ALL
309 DWORD Written=0;
310 if (HandleType!=FILE_HANDLENORMAL)
311 {
312 // writing to stdout can fail in old Windows if data block is too large
313 const size_t MaxSize=0x4000;
314 for (size_t I=0;I<Size;I+=MaxSize)
315 {
316 Success=WriteFile(hFile,(byte *)Data+I,(DWORD)Min(Size-I,MaxSize),&Written,NULL)==TRUE;
317 if (!Success)
318 break;
319 }
320 }
321 else
322 Success=WriteFile(hFile,Data,(DWORD)Size,&Written,NULL)==TRUE;
323 #else
324 #ifdef FILE_USE_OPEN
325 ssize_t Written=write(hFile,Data,Size);
326 Success=Written==Size;
327 #else
328 int Written=fwrite(Data,1,Size,hFile);
329 Success=Written==Size && !ferror(hFile);
330 #endif
331 #endif
332 if (!Success && AllowExceptions && HandleType==FILE_HANDLENORMAL)
333 {
334 #if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(RARDLL)
335 int ErrCode=GetLastError();
336 int64 FilePos=Tell();
337 uint64 FreeSize=GetFreeDisk(FileName);
338 SetLastError(ErrCode);
339 if (FreeSize>Size && FilePos-Size<=0xffffffff && FilePos+Size>0xffffffff)
340 ErrHandler.WriteErrorFAT(FileName);
341 #endif
342 if (ErrHandler.AskRepeatWrite(FileName,false))
343 {
344 #if !defined(_WIN_ALL) && !defined(FILE_USE_OPEN)
345 clearerr(hFile);
346 #endif
347 if (Written<Size && Written>0)
348 Seek(Tell()-Written,SEEK_SET);
349 continue;
350 }
351 ErrHandler.WriteError(NULL,FileName);
352 }
353 break;
354 }
355 LastWrite=true;
356 return Success; // It can return false only if AllowExceptions is disabled.
357 }
358
359
Read(void * Data,size_t Size)360 int File::Read(void *Data,size_t Size)
361 {
362 int64 FilePos=0; // Initialized only to suppress some compilers warning.
363
364 if (IgnoreReadErrors)
365 FilePos=Tell();
366 int ReadSize;
367 while (true)
368 {
369 ReadSize=DirectRead(Data,Size);
370 if (ReadSize==-1)
371 {
372 ErrorType=FILE_READERROR;
373 if (AllowExceptions)
374 if (IgnoreReadErrors)
375 {
376 ReadSize=0;
377 for (size_t I=0;I<Size;I+=512)
378 {
379 Seek(FilePos+I,SEEK_SET);
380 size_t SizeToRead=Min(Size-I,512);
381 int ReadCode=DirectRead(Data,SizeToRead);
382 ReadSize+=(ReadCode==-1) ? 512:ReadCode;
383 }
384 }
385 else
386 {
387 if (HandleType==FILE_HANDLENORMAL && ErrHandler.AskRepeatRead(FileName))
388 continue;
389 ErrHandler.ReadError(FileName);
390 }
391 }
392 break;
393 }
394 return ReadSize;
395 }
396
397
398 // Returns -1 in case of error.
DirectRead(void * Data,size_t Size)399 int File::DirectRead(void *Data,size_t Size)
400 {
401 #ifdef _WIN_ALL
402 const size_t MaxDeviceRead=20000;
403 const size_t MaxLockedRead=32768;
404 #endif
405 if (HandleType==FILE_HANDLESTD)
406 {
407 #ifdef _WIN_ALL
408 // if (Size>MaxDeviceRead)
409 // Size=MaxDeviceRead;
410 hFile=GetStdHandle(STD_INPUT_HANDLE);
411 #else
412 #ifdef FILE_USE_OPEN
413 hFile=STDIN_FILENO;
414 #else
415 hFile=stdin;
416 #endif
417 #endif
418 }
419 #ifdef _WIN_ALL
420 // For pipes like 'type file.txt | rar -si arcname' ReadFile may return
421 // data in small ~4KB blocks. It may slightly reduce the compression ratio.
422 DWORD Read;
423 if (!ReadFile(hFile,Data,(DWORD)Size,&Read,NULL))
424 {
425 if (IsDevice() && Size>MaxDeviceRead)
426 return DirectRead(Data,MaxDeviceRead);
427 if (HandleType==FILE_HANDLESTD && GetLastError()==ERROR_BROKEN_PIPE)
428 return 0;
429
430 // We had a bug report about failure to archive 1C database lock file
431 // 1Cv8tmp.1CL, which is a zero length file with a region above 200 KB
432 // permanently locked. If our first read request uses too large buffer
433 // and if we are in -dh mode, so we were able to open the file,
434 // we'll fail with "Read error". So now we use try a smaller buffer size
435 // in case of lock error.
436 if (HandleType==FILE_HANDLENORMAL && Size>MaxLockedRead &&
437 GetLastError()==ERROR_LOCK_VIOLATION)
438 return DirectRead(Data,MaxLockedRead);
439
440 return -1;
441 }
442 return Read;
443 #else
444 #ifdef FILE_USE_OPEN
445 ssize_t ReadSize=read(hFile,Data,Size);
446 if (ReadSize==-1)
447 return -1;
448 return (int)ReadSize;
449 #else
450 if (LastWrite)
451 {
452 fflush(hFile);
453 LastWrite=false;
454 }
455 clearerr(hFile);
456 size_t ReadSize=fread(Data,1,Size,hFile);
457 if (ferror(hFile))
458 return -1;
459 return (int)ReadSize;
460 #endif
461 #endif
462 }
463
464
Seek(int64 Offset,int Method)465 void File::Seek(int64 Offset,int Method)
466 {
467 if (!RawSeek(Offset,Method) && AllowExceptions)
468 ErrHandler.SeekError(FileName);
469 }
470
471
RawSeek(int64 Offset,int Method)472 bool File::RawSeek(int64 Offset,int Method)
473 {
474 if (hFile==FILE_BAD_HANDLE)
475 return true;
476 if (Offset<0 && Method!=SEEK_SET)
477 {
478 Offset=(Method==SEEK_CUR ? Tell():FileLength())+Offset;
479 Method=SEEK_SET;
480 }
481 #ifdef _WIN_ALL
482 LONG HighDist=(LONG)(Offset>>32);
483 if (SetFilePointer(hFile,(LONG)Offset,&HighDist,Method)==0xffffffff &&
484 GetLastError()!=NO_ERROR)
485 return false;
486 #else
487 LastWrite=false;
488 #ifdef FILE_USE_OPEN
489 if (lseek64(hFile,Offset,Method)==-1)
490 return false;
491 #elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE) && !defined(__VMS)
492 if (fseeko(hFile,Offset,Method)!=0)
493 return false;
494 #else
495 if (fseek(hFile,(long)Offset,Method)!=0)
496 return false;
497 #endif
498 #endif
499 return true;
500 }
501
502
Tell()503 int64 File::Tell()
504 {
505 if (hFile==FILE_BAD_HANDLE)
506 if (AllowExceptions)
507 ErrHandler.SeekError(FileName);
508 else
509 return -1;
510 #ifdef _WIN_ALL
511 LONG HighDist=0;
512 uint LowDist=SetFilePointer(hFile,0,&HighDist,FILE_CURRENT);
513 if (LowDist==0xffffffff && GetLastError()!=NO_ERROR)
514 if (AllowExceptions)
515 ErrHandler.SeekError(FileName);
516 else
517 return -1;
518 return INT32TO64(HighDist,LowDist);
519 #else
520 #ifdef FILE_USE_OPEN
521 return lseek64(hFile,0,SEEK_CUR);
522 #elif defined(_LARGEFILE_SOURCE) && !defined(_OSF_SOURCE)
523 return ftello(hFile);
524 #else
525 return ftell(hFile);
526 #endif
527 #endif
528 }
529
530
Prealloc(int64 Size)531 void File::Prealloc(int64 Size)
532 {
533 #ifdef _WIN_ALL
534 if (RawSeek(Size,SEEK_SET))
535 {
536 Truncate();
537 Seek(0,SEEK_SET);
538 }
539 #endif
540
541 #if defined(_UNIX) && defined(USE_FALLOCATE)
542 // fallocate is rather new call. Only latest kernels support it.
543 // So we are not using it by default yet.
544 int fd = GetFD(hFile);
545 if (fd >= 0)
546 fallocate(fd, 0, 0, Size);
547 #endif
548 }
549
550
GetByte()551 byte File::GetByte()
552 {
553 byte Byte=0;
554 Read(&Byte,1);
555 return Byte;
556 }
557
558
PutByte(byte Byte)559 void File::PutByte(byte Byte)
560 {
561 Write(&Byte,1);
562 }
563
564
Truncate()565 bool File::Truncate()
566 {
567 #ifdef _WIN_ALL
568 return SetEndOfFile(hFile)==TRUE;
569 #else
570 return false;
571 #endif
572 }
573
574
SetOpenFileTime(RarTime * ftm,RarTime * ftc,RarTime * fta)575 void File::SetOpenFileTime(RarTime *ftm,RarTime *ftc,RarTime *fta)
576 {
577 #ifdef _WIN_ALL
578 // Workaround for OpenIndiana NAS time bug. If we cannot create a file
579 // in write only mode, we need to flush the write buffer before calling
580 // SetFileTime or file time will not be changed.
581 if (CreateMode!=FMF_UNDEFINED && (CreateMode & FMF_WRITE)==0)
582 FlushFileBuffers(hFile);
583
584 bool sm=ftm!=NULL && ftm->IsSet();
585 bool sc=ftc!=NULL && ftc->IsSet();
586 bool sa=fta!=NULL && fta->IsSet();
587 FILETIME fm,fc,fa;
588 if (sm)
589 ftm->GetWin32(&fm);
590 if (sc)
591 ftc->GetWin32(&fc);
592 if (sa)
593 fta->GetWin32(&fa);
594 SetFileTime(hFile,sc ? &fc:NULL,sa ? &fa:NULL,sm ? &fm:NULL);
595 #endif
596 }
597
598
SetCloseFileTime(RarTime * ftm,RarTime * fta)599 void File::SetCloseFileTime(RarTime *ftm,RarTime *fta)
600 {
601 #ifdef _UNIX
602 SetCloseFileTimeByName(FileName,ftm,fta);
603 #endif
604 }
605
606
SetCloseFileTimeByName(const wchar * Name,RarTime * ftm,RarTime * fta)607 void File::SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta)
608 {
609 #ifdef _UNIX
610 bool setm=ftm!=NULL && ftm->IsSet();
611 bool seta=fta!=NULL && fta->IsSet();
612 if (setm || seta)
613 {
614 utimbuf ut;
615 if (setm)
616 ut.modtime=ftm->GetUnix();
617 else
618 ut.modtime=fta->GetUnix();
619 if (seta)
620 ut.actime=fta->GetUnix();
621 else
622 ut.actime=ut.modtime;
623 char NameA[NM];
624 WideToChar(Name,NameA,ASIZE(NameA));
625 utime(NameA,&ut);
626 }
627 #endif
628 }
629
630
GetOpenFileTime(RarTime * ft)631 void File::GetOpenFileTime(RarTime *ft)
632 {
633 #ifdef _WIN_ALL
634 FILETIME FileTime;
635 GetFileTime(hFile,NULL,NULL,&FileTime);
636 *ft=FileTime;
637 #endif
638 #if defined(_UNIX) || defined(_EMX)
639 struct stat st;
640 fstat(GetFD(),&st);
641 *ft=st.st_mtime;
642 #endif
643 }
644
645
FileLength()646 int64 File::FileLength()
647 {
648 SaveFilePos SavePos(*this);
649 Seek(0,SEEK_END);
650 return Tell();
651 }
652
653
IsDevice()654 bool File::IsDevice()
655 {
656 if (hFile==FILE_BAD_HANDLE)
657 return false;
658 #ifdef _WIN_ALL
659 uint Type=GetFileType(hFile);
660 return Type==FILE_TYPE_CHAR || Type==FILE_TYPE_PIPE;
661 #else
662 return isatty(GetFD());
663 #endif
664 }
665
666
667 #ifndef SFX_MODULE
Copy(File & Dest,int64 Length)668 int64 File::Copy(File &Dest,int64 Length)
669 {
670 Array<char> Buffer(0x40000);
671 int64 CopySize=0;
672 bool CopyAll=(Length==INT64NDF);
673
674 while (CopyAll || Length>0)
675 {
676 Wait();
677 size_t SizeToRead=(!CopyAll && Length<(int64)Buffer.Size()) ? (size_t)Length:Buffer.Size();
678 char *Buf=&Buffer[0];
679 int ReadSize=Read(Buf,SizeToRead);
680 if (ReadSize==0)
681 break;
682 size_t WriteSize=ReadSize;
683 #ifdef _WIN_ALL
684 // For FAT32 USB flash drives in Windows if first write is 4 KB or more,
685 // write caching is disabled and "write through" is enabled, resulting
686 // in bad performance, especially for many small files. It happens when
687 // we create SFX archive on USB drive, because SFX module is written first.
688 // So we split the first write to small 1 KB followed by rest of data.
689 if (CopySize==0 && WriteSize>=4096)
690 {
691 const size_t FirstWrite=1024;
692 Dest.Write(Buf,FirstWrite);
693 Buf+=FirstWrite;
694 WriteSize-=FirstWrite;
695 }
696 #endif
697 Dest.Write(Buf,WriteSize);
698 CopySize+=ReadSize;
699 if (!CopyAll)
700 Length-=ReadSize;
701 }
702 return CopySize;
703 }
704 #endif
705