1 /********************************************************************************
2 *                                                                               *
3 *                             F i l e   C l a s s                               *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 2000,2020 by Jeroen van der Zijp.   All Rights Reserved.        *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or modify          *
9 * it under the terms of the GNU Lesser General Public License as published by   *
10 * the Free Software Foundation; either version 3 of the License, or             *
11 * (at your option) any later version.                                           *
12 *                                                                               *
13 * This library is distributed in the hope that it will be useful,               *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of                *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
16 * GNU Lesser General Public License for more details.                           *
17 *                                                                               *
18 * You should have received a copy of the GNU Lesser General Public License      *
19 * along with this program.  If not, see <http://www.gnu.org/licenses/>          *
20 ********************************************************************************/
21 #include "xincs.h"
22 #include "fxver.h"
23 #include "fxdefs.h"
24 #include "fxmath.h"
25 #include "fxascii.h"
26 #include "FXArray.h"
27 #include "FXHash.h"
28 #include "FXStream.h"
29 #include "FXString.h"
30 #include "FXPath.h"
31 #include "FXIO.h"
32 #include "FXIODevice.h"
33 #include "FXStat.h"
34 #include "FXFile.h"
35 #include "FXPipe.h"
36 #include "FXDir.h"
37 
38 
39 /*
40   Notes:
41 
42   - Implemented many functions in terms of FXFile and FXDir so we won't have to worry about
43     unicode stuff.
44 
45 */
46 
47 // Bad handle value
48 #ifdef WIN32
49 #define BadHandle INVALID_HANDLE_VALUE
50 #else
51 #define BadHandle -1
52 #endif
53 
54 #ifdef WIN32
55 #ifndef INVALID_SET_FILE_POINTER
56 #define INVALID_SET_FILE_POINTER ((DWORD)-1)
57 #endif
58 #endif
59 
60 using namespace FX;
61 
62 /*******************************************************************************/
63 
64 namespace FX {
65 
66 
67 // Construct file and attach existing handle h
FXFile(FXInputHandle h,FXuint m)68 FXFile::FXFile(FXInputHandle h,FXuint m){
69   attach(h,m);
70   }
71 
72 
73 // Construct and open a file
FXFile(const FXString & file,FXuint m,FXuint perm)74 FXFile::FXFile(const FXString& file,FXuint m,FXuint perm){
75   open(file,m,perm);
76   }
77 
78 
79 // Open file
open(const FXString & file,FXuint m,FXuint perm)80 FXbool FXFile::open(const FXString& file,FXuint m,FXuint perm){
81   if(__likely(device==BadHandle) && __likely(!file.empty())){
82 #if defined(WIN32)
83     SECURITY_ATTRIBUTES sat;
84     DWORD flags=GENERIC_READ;
85     DWORD creation=OPEN_EXISTING;
86 
87     // Basic access mode
88     switch(m&(ReadOnly|WriteOnly)){
89       case ReadOnly: flags=GENERIC_READ; break;
90       case WriteOnly: flags=GENERIC_WRITE; break;
91       case ReadWrite: flags=GENERIC_READ|GENERIC_WRITE; break;
92       }
93 
94     // Creation and truncation mode
95     switch(m&(Create|Truncate|Exclusive)){
96       case Create: creation=OPEN_ALWAYS; break;
97       case Truncate: creation=TRUNCATE_EXISTING; break;
98       case Create|Truncate: creation=CREATE_ALWAYS; break;
99       case Create|Truncate|Exclusive: creation=CREATE_NEW; break;
100       }
101 
102     // Inheritable
103     sat.nLength=sizeof(SECURITY_ATTRIBUTES);
104     sat.bInheritHandle=(m&Inheritable)?true:false;
105     sat.lpSecurityDescriptor=NULL;
106 
107     // Non-blocking mode
108     if(m&NonBlocking){
109       // FIXME
110       }
111 
112     // Do it
113     access=NoAccess;
114     pointer=0L;
115 #if defined(UNICODE)
116     FXnchar unifile[MAXPATHLEN];
117     utf2ncs(unifile,file.text(),MAXPATHLEN);
118     device=::CreateFileW(unifile,flags,FILE_SHARE_READ|FILE_SHARE_WRITE,&sat,creation,FILE_ATTRIBUTE_NORMAL,NULL);
119 #else
120     device=::CreateFileA(file.text(),flags,FILE_SHARE_READ|FILE_SHARE_WRITE,&sat,creation,FILE_ATTRIBUTE_NORMAL,NULL);
121 #endif
122     if(device!=BadHandle){
123       if(m&Append){ position(0,FXIO::End); }    // Appending
124       access=(m|OwnHandle);                     // Own handle
125       return true;
126       }
127 #else
128     FXint bits=perm&0777;
129     FXint flags=0;
130 
131     // Basic access mode
132     switch(m&(ReadOnly|WriteOnly)){
133       case ReadOnly: flags=O_RDONLY; break;
134       case WriteOnly: flags=O_WRONLY; break;
135       case ReadWrite: flags=O_RDWR; break;
136       }
137 
138     // Appending and truncation
139     if(m&Append) flags|=O_APPEND;
140     if(m&Truncate) flags|=O_TRUNC;
141 
142     // Non-blocking mode
143     if(m&NonBlocking) flags|=O_NONBLOCK;
144 
145     // Change access time
146 #if defined(O_NOATIME)
147     if(m&NoAccessTime) flags|=O_NOATIME;
148 #endif
149 
150     // Inheritable only if specified
151 #if defined(O_CLOEXEC)
152     if(!(m&Inheritable)) flags|=O_CLOEXEC;
153 #endif
154 
155     // Creation mode
156     if(m&Create){
157       flags|=O_CREAT;
158       if(m&Exclusive) flags|=O_EXCL;
159       }
160 
161     // Permission bits
162     if(perm&FXIO::SetUser) bits|=S_ISUID;
163     if(perm&FXIO::SetGroup) bits|=S_ISGID;
164     if(perm&FXIO::Sticky) bits|=S_ISVTX;
165 
166     // Do it
167     access=NoAccess;
168     pointer=0L;
169     device=::open(file.text(),flags,bits);
170     if(device!=BadHandle){
171       if(m&Append){ position(0,FXIO::End); }    // Appending
172       access=(m|OwnHandle);                     // Own handle
173       return true;
174       }
175 #endif
176     }
177   return false;
178   }
179 
180 
181 // Open device with access mode and handle
open(FXInputHandle h,FXuint m)182 FXbool FXFile::open(FXInputHandle h,FXuint m){
183   return FXIODevice::open(h,m);
184   }
185 
186 
187 // Return true if serial access only
isSerial() const188 FXbool FXFile::isSerial() const {
189   return false;
190   }
191 
192 
193 // Get position
position() const194 FXlong FXFile::position() const {
195   return pointer;
196   }
197 
198 
199 // Move to position
position(FXlong offset,FXuint from)200 FXlong FXFile::position(FXlong offset,FXuint from){
201   if(__likely(device!=BadHandle)){
202 #if defined(WIN32)
203     LARGE_INTEGER pos;
204     pos.QuadPart=offset;
205     pos.LowPart=::SetFilePointer(device,pos.LowPart,&pos.HighPart,from);
206     if(pos.LowPart!=INVALID_SET_FILE_POINTER || GetLastError()==NO_ERROR){
207       pointer=pos.QuadPart;
208       return pointer;
209       }
210 #else
211     FXlong pos;
212     if(0<=(pos=::lseek(device,offset,from))){
213       pointer=pos;
214       return pointer;
215       }
216 #endif
217     }
218   return FXIO::Error;
219   }
220 
221 
222 // Truncate file
truncate(FXlong sz)223 FXlong FXFile::truncate(FXlong sz){
224   if(__likely(device!=BadHandle)){
225 #if defined(WIN32)
226     LARGE_INTEGER pos;
227     pos.QuadPart=sz;
228     pos.LowPart=::SetFilePointer(device,pos.LowPart,&pos.HighPart,FILE_BEGIN);
229     if(pos.LowPart!=INVALID_SET_FILE_POINTER || GetLastError()==NO_ERROR){
230       if(::SetEndOfFile(device)!=0){
231         position(pointer);
232         return sz;
233         }
234       }
235 #else
236     if(::ftruncate(device,sz)==0){
237       position(pointer);
238       return sz;
239       }
240 #endif
241     }
242   return FXIO::Error;
243   }
244 
245 
246 // Flush to disk
flush()247 FXbool FXFile::flush(){
248   if(__likely(device!=BadHandle)){
249 #if defined(WIN32)
250     return ::FlushFileBuffers(device)!=0;
251 #elif defined(_BSD_SOURCE) || defined(_XOPEN_SOURCE) || (_POSIX_C_SOURCE >= 200112L)
252     return ::fsync(device)==0;
253 #endif
254     }
255   return false;
256   }
257 
258 
259 // Test if we're at the end; -1 if error
eof()260 FXint FXFile::eof(){
261   if(__likely(device!=BadHandle)){
262     return !(pointer<size());
263     }
264   return FXIO::Error;
265   }
266 
267 
268 // Return file size
size()269 FXlong FXFile::size(){
270   if(__likely(device!=BadHandle)){
271 #if defined(WIN32)
272     ULARGE_INTEGER result;
273     result.LowPart=::GetFileSize(device,&result.HighPart);
274     return result.QuadPart;
275 #else
276     struct stat data;
277     if(::fstat(device,&data)==0) return data.st_size;
278 #endif
279     }
280   return FXIO::Error;
281   }
282 
283 /*******************************************************************************/
284 
285 // Create new (empty) file
create(const FXString & file,FXuint perm)286 FXbool FXFile::create(const FXString& file,FXuint perm){
287   if(!file.empty()){
288 #if defined(WIN32)
289 #if defined(UNICODE)
290     FXnchar unifile[MAXPATHLEN];
291     utf2ncs(unifile,file.text(),MAXPATHLEN);
292     FXInputHandle h=::CreateFileW(unifile,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
293 #else
294     FXInputHandle h=::CreateFileA(file.text(),GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
295 #endif
296     if(h!=BadHandle){ ::CloseHandle(h); return true; }
297 #else
298     FXInputHandle h=::open(file.text(),O_CREAT|O_WRONLY|O_TRUNC|O_EXCL,perm);
299     if(h!=BadHandle){ ::close(h); return true; }
300 #endif
301     }
302   return false;
303   }
304 
305 /*******************************************************************************/
306 
307 // Link file
link(const FXString & srcfile,const FXString & dstfile)308 FXbool FXFile::link(const FXString& srcfile,const FXString& dstfile){
309   if(srcfile!=dstfile){
310 #if defined(WIN32)
311 #if defined(UNICODE)
312     FXnchar srcname[MAXPATHLEN];
313     FXnchar dstname[MAXPATHLEN];
314     utf2ncs(srcname,srcfile.text(),MAXPATHLEN);
315     utf2ncs(dstname,dstfile.text(),MAXPATHLEN);
316     return CreateHardLinkW(dstname,srcname,NULL)!=0;
317 #else
318     return CreateHardLinkA(dstfile.text(),srcfile.text(),NULL)!=0;
319 #endif
320 #else
321     return ::link(srcfile.text(),dstfile.text())==0;
322 #endif
323     }
324   return false;
325   }
326 
327 
328 // Read symbolic link
symlink(const FXString & file)329 FXString FXFile::symlink(const FXString& file){
330   if(!file.empty()){
331 #if !defined(WIN32)
332     FXchar lnk[MAXPATHLEN+1];
333     FXint len=::readlink(file.text(),lnk,MAXPATHLEN);
334     if(0<=len){
335       return FXString(lnk,len);
336       }
337 #endif
338     }
339   return FXString::null;
340   }
341 
342 
343 // Symbolic Link file
symlink(const FXString & srcfile,const FXString & dstfile)344 FXbool FXFile::symlink(const FXString& srcfile,const FXString& dstfile){
345   if(dstfile!=srcfile){
346 #if !defined(WIN32)
347     return ::symlink(srcfile.text(),dstfile.text())==0;
348 #endif
349     }
350   return false;
351   }
352 
353 /*******************************************************************************/
354 
355 // Return true if files are identical (identical node on disk)
identical(const FXString & file1,const FXString & file2)356 FXbool FXFile::identical(const FXString& file1,const FXString& file2){
357   if(file1!=file2){
358     FXStat info1;
359     FXStat info2;
360     if(FXStat::statFile(file1,info1) && FXStat::statFile(file2,info2)){
361       return info1.index()==info2.index() && info1.volume()==info2.volume();
362       }
363     return false;
364     }
365   return true;
366   }
367 
368 /*******************************************************************************/
369 
370 // Copy srcfile to dstfile, overwriting dstfile if allowed
copy(const FXString & srcfile,const FXString & dstfile,FXbool overwrite)371 FXbool FXFile::copy(const FXString& srcfile,const FXString& dstfile,FXbool overwrite){
372   if(srcfile!=dstfile){
373     FXFile src(srcfile,FXIO::Reading);
374     if(src.isOpen()){
375       FXStat stat;
376       if(FXStat::stat(src,stat)){
377         FXFile dst(dstfile,overwrite?FXIO::Writing:FXIO::Writing|FXIO::Exclusive,stat.mode());
378         if(dst.isOpen()){
379           FXuchar buffer[4096];
380           FXival  nwritten;
381           FXival  nread;
382           while(1){
383             nread=src.readBlock(buffer,sizeof(buffer));
384             if(nread<0) return false;
385             if(nread==0) break;
386             nwritten=dst.writeBlock(buffer,nread);
387             if(nwritten<nread) return false;
388             }
389           return true;
390           }
391         }
392       }
393     }
394   return false;
395   }
396 
397 
398 // Recursively copy files or directories from srcfile to dstfile, overwriting dstfile if allowed
copyFiles(const FXString & srcfile,const FXString & dstfile,FXbool overwrite)399 FXbool FXFile::copyFiles(const FXString& srcfile,const FXString& dstfile,FXbool overwrite){
400   if(srcfile!=dstfile){
401     FXStat srcstat;
402     FXStat dststat;
403 
404     // Source file information
405     if(FXStat::statLink(srcfile,srcstat)){
406 
407       // Destination file information
408       if(FXStat::statLink(dstfile,dststat)){
409 
410         // Destination is a directory?
411         if(!dststat.isDirectory()){
412           if(!overwrite) return false;
413           if(!FXFile::remove(dstfile)) return false;
414           }
415         }
416 
417       // Source is a directory
418       if(srcstat.isDirectory()){
419         FXString name;
420         FXDir dir;
421 
422         // Open source directory
423         if(!dir.open(srcfile)) return false;
424 
425         // Make destination directory if needed
426         if(!dststat.isDirectory()){
427 
428           // Make directory
429           if(!FXDir::create(dstfile,srcstat.mode()|FXIO::OwnerWrite)) return false;
430           }
431 
432         // Copy contents of source directory
433         while(dir.next(name)){
434 
435           // Skip '.' and '..'
436           if(name[0]=='.' && (name[1]=='\0' || (name[1]=='.' && name[2]=='\0'))) continue;
437 
438           // Recurse
439           if(!FXFile::copyFiles(srcfile+PATHSEP+name,dstfile+PATHSEP+name,overwrite)) return false;
440           }
441 
442         // OK
443         return true;
444         }
445 
446       // Source is a file
447       if(srcstat.isFile()){
448 
449         // Simply copy
450         if(!FXFile::copy(srcfile,dstfile,overwrite)) return false;
451 
452         // OK
453         return true;
454         }
455 
456       // Source is symbolic link: make a new one
457       if(srcstat.isLink()){
458         FXString lnkfile=FXFile::symlink(srcfile);
459 
460         // New symlink to whatever old one referred to
461         if(!FXFile::symlink(lnkfile,dstfile)) return false;
462 
463         // OK
464         return true;
465         }
466 
467       // Source is fifo: make a new one
468       if(srcstat.isFifo()){
469 
470         // Make named pipe
471         if(!FXPipe::create(dstfile,srcstat.mode())) return false;
472 
473         // OK
474         return true;
475         }
476 
477       // Source is device/socket; only on UNIX
478 #if !defined(WIN32)
479       if(srcstat.isDevice() || srcstat.isSocket()){
480         struct stat data;
481         if(::lstat(srcfile.text(),&data)==0){
482           return ::mknod(dstfile.text(),data.st_mode,data.st_rdev)==0;
483           }
484         }
485 #endif
486       }
487     }
488   return false;
489   }
490 
491 /*******************************************************************************/
492 
493 // Move or rename srcfile to dstfile, overwriting dstfile if allowed
move(const FXString & srcfile,const FXString & dstfile,FXbool overwrite)494 FXbool FXFile::move(const FXString& srcfile,const FXString& dstfile,FXbool overwrite){
495   if(srcfile!=dstfile){
496 #if defined(WIN32)
497 #if defined(UNICODE)
498     FXnchar srcname[MAXPATHLEN];
499     FXnchar dstname[MAXPATHLEN];
500     utf2ncs(srcname,srcfile.text(),MAXPATHLEN);
501     utf2ncs(dstname,dstfile.text(),MAXPATHLEN);
502     return ::MoveFileExW(srcname,dstname,overwrite?MOVEFILE_REPLACE_EXISTING:0)!=0;
503 #else
504     return ::MoveFileExA(srcfile.text(),dstfile.text(),overwrite?MOVEFILE_REPLACE_EXISTING:0)!=0;
505 #endif
506 #else
507     if(overwrite || !FXStat::exists(dstfile)){
508       return ::rename(srcfile.text(),dstfile.text())==0;
509       }
510 #endif
511     }
512   return false;
513   }
514 
515 
516 // Recursively copy or move files or directories from srcfile to dstfile, overwriting dstfile if allowed
moveFiles(const FXString & srcfile,const FXString & dstfile,FXbool overwrite)517 FXbool FXFile::moveFiles(const FXString& srcfile,const FXString& dstfile,FXbool overwrite){
518   if(srcfile!=dstfile){
519     if(FXFile::move(srcfile,dstfile,overwrite)) return true;
520     if(FXFile::copyFiles(srcfile,dstfile,overwrite)){
521       return FXFile::removeFiles(srcfile,true);
522       }
523     }
524   return false;
525   }
526 
527 /*******************************************************************************/
528 
529 // Remove a file
remove(const FXString & file)530 FXbool FXFile::remove(const FXString& file){
531   if(!file.empty()){
532 #if defined(WIN32)
533 #if defined(UNICODE)
534     FXnchar unifile[MAXPATHLEN];
535     utf2ncs(unifile,file.text(),MAXPATHLEN);
536     return ::DeleteFileW(unifile)!=0;
537 #else
538     return ::DeleteFileA(file.text())!=0;
539 #endif
540 #else
541     return ::unlink(file.text())==0;
542 #endif
543     }
544   return false;
545   }
546 
547 
548 // Remove file or directory, recursively if allowed
removeFiles(const FXString & path,FXbool recursive)549 FXbool FXFile::removeFiles(const FXString& path,FXbool recursive){
550   FXStat stat;
551   if(FXStat::statLink(path,stat)){
552     if(stat.isDirectory()){
553       if(recursive){
554         FXDir dir(path);
555         FXString name;
556         while(dir.next(name)){
557           if(name[0]=='.' && (name[1]=='\0' || (name[1]=='.' && name[2]=='\0'))) continue;
558           if(!FXFile::removeFiles(path+PATHSEP+name,true)) return false;
559           }
560         }
561       return FXDir::remove(path);
562       }
563     return FXFile::remove(path);
564     }
565   return false;
566   }
567 
568 /*******************************************************************************/
569 
570 // Concatenate srcfile1 and srcfile2 to dstfile, overwriting dstfile if allowed
concat(const FXString & srcfile1,const FXString & srcfile2,const FXString & dstfile,FXbool overwrite)571 FXbool FXFile::concat(const FXString& srcfile1,const FXString& srcfile2,const FXString& dstfile,FXbool overwrite){
572   FXuchar buffer[4096]; FXival nwritten,nread;
573   if(srcfile1!=dstfile && srcfile2!=dstfile){
574     FXFile src1(srcfile1,FXIO::Reading);
575     if(src1.isOpen()){
576       FXFile src2(srcfile2,FXIO::Reading);
577       if(src2.isOpen()){
578         FXFile dst(dstfile,overwrite?FXIO::Writing:FXIO::Writing|FXIO::Exclusive);
579         if(dst.isOpen()){
580           while(1){
581             nread=src1.readBlock(buffer,sizeof(buffer));
582             if(nread<0) return false;
583             if(nread==0) break;
584             nwritten=dst.writeBlock(buffer,nread);
585             if(nwritten<0) return false;
586             }
587           while(1){
588             nread=src2.readBlock(buffer,sizeof(buffer));
589             if(nread<0) return false;
590             if(nread==0) break;
591             nwritten=dst.writeBlock(buffer,nread);
592             if(nwritten<0) return false;
593             }
594           return true;
595           }
596         }
597       }
598     }
599   return false;
600   }
601 
602 }
603