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