1 /********************************************************************************
2 *                                                                               *
3 *      F i l e   I n f o r m a t i o n   a n d   M a n i p u l a t i o n        *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 2000,2005 by Jeroen van der Zijp.   All Rights Reserved.        *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or                 *
9 * modify it under the terms of the GNU Lesser General Public                    *
10 * License as published by the Free Software Foundation; either                  *
11 * version 2.1 of the License, or (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 GNU             *
16 * Lesser General Public License for more details.                               *
17 *                                                                               *
18 * You should have received a copy of the GNU Lesser General Public              *
19 * License along with this library; if not, write to the Free Software           *
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.    *
21 *********************************************************************************
22 * $Id: FXFile.cpp,v 1.190.2.1 2005/04/10 03:24:47 fox Exp $                         *
23 ********************************************************************************/
24 #include "xincs.h"
25 #include "fxver.h"
26 #include "fxdefs.h"
27 #include "FXHash.h"
28 #include "FXStream.h"
29 #include "FXString.h"
30 #include "FXFile.h"
31 #ifdef WIN32
32 #include <shellapi.h>
33 #endif
34 
35 
36 
37 /*
38   Notes:
39   - Thanks to Sean Hubbell for the original impetus for these functions.
40   - Windows flavors of some of these functions are not perfect yet.
41   - Windows 95 and NT:
42       -  1 to 255 character name.
43       -  Complete path for a file or project name cannot exceed 259
44          characters, including the separators.
45       -  May not begin or end with a space.
46       -  May not begin with a $
47       -  May contain 1 or more file extensions (eg. MyFile.Ext1.Ext2.Ext3.Txt).
48       -  Legal characters in the range of 32 - 255 but not ?"/\<>*|:
49       -  Filenames may be mixed case.
50       -  Filename comparisons are case insensitive (eg. ThIs.TXT = this.txt).
51   - MS-DOS and Windows 3.1:
52       -  1 to 11 characters in the 8.3 naming convention.
53       -  Legal characters are A-Z, 0-9, Double Byte Character Set (DBCS)
54          characters (128 - 255), and _^$~!#%&-{}@'()
55       -  May not contain spaces, 0 - 31, and "/\[]:;|=,
56       -  Must not begin with $
57       -  Uppercase only filename.
58   - Perhaps use GetEnvironmentVariable instead of getenv?
59   - FXFile::search() what if some paths are quoted, like
60 
61       \this\dir;"\that\dir with a ;";\some\other\dir
62 
63   - Need function to contract filenames, e.g. change:
64 
65       /home/jeroen/junk
66       /home/someoneelse/junk
67 
68     to:
69 
70       ~/junk
71       ~someoneelse/junk
72 
73   - Perhaps also taking into account certain environment variables in the
74     contraction function?
75   - FXFile::copy( "C:\tmp", "c:\tmp\tmp" ) results infinite-loop.
76 
77 */
78 
79 
80 #ifndef TIMEFORMAT
81 #define TIMEFORMAT "%m/%d/%Y %H:%M:%S"
82 #endif
83 
84 
85 using namespace FX;
86 
87 /*******************************************************************************/
88 
89 namespace FX {
90 
91 #ifdef __SC__
92 using namespace FXFile;
93 #endif
94 
95 
96 // Return value of environment variable name
getEnvironment(const FXString & name)97 FXString FXFile::getEnvironment(const FXString& name){
98   return FXString(getenv(name.text()));
99   }
100 
101 
102 // Get current user name
getCurrentUserName()103 FXString FXFile::getCurrentUserName(){
104 #ifndef WIN32
105 #if defined(FOX_THREAD_SAFE) && !defined(__FreeBSD__)
106   struct passwd pwdresult,*pwd;
107   char buffer[1024];
108   if(getpwuid_r(geteuid(),&pwdresult,buffer,sizeof(buffer),&pwd)==0 && pwd) return pwd->pw_name;
109 #else
110   struct passwd *pwd=getpwuid(geteuid());
111   if(pwd) return pwd->pw_name;
112 #endif
113 #else
114   char buffer[1024];
115   DWORD size=sizeof(buffer);
116   if(GetUserName(buffer,&size)) return buffer;
117 #endif
118   return FXString::null;
119   }
120 
121 
122 // Get current working directory
getCurrentDirectory()123 FXString FXFile::getCurrentDirectory(){
124   FXchar buffer[MAXPATHLEN];
125 #ifndef WIN32
126   if(getcwd(buffer,MAXPATHLEN)) return FXString(buffer);
127 #else
128   if(GetCurrentDirectory(MAXPATHLEN,buffer)) return FXString(buffer);
129 #endif
130   return FXString::null;
131   }
132 
133 
134 // Change current directory
setCurrentDirectory(const FXString & path)135 FXbool FXFile::setCurrentDirectory(const FXString& path){
136 #ifdef WIN32
137   return !path.empty() && SetCurrentDirectory(path.text());
138 #else
139   return !path.empty() && chdir(path.text())==0;
140 #endif
141   }
142 
143 
144 // Get current drive prefix "a:", if any
145 // This is the same method as used in VC++ CRT.
getCurrentDrive()146 FXString FXFile::getCurrentDrive(){
147 #ifdef WIN32
148   FXchar buffer[MAXPATHLEN];
149   if(GetCurrentDirectory(MAXPATHLEN,buffer) && isalpha((FXuchar)buffer[0]) && buffer[1]==':') return FXString(buffer,2);
150 #endif
151   return FXString::null;
152   }
153 
154 
155 #ifdef WIN32
156 
157 // Change current drive prefix "a:"
158 // This is the same method as used in VC++ CRT.
setCurrentDrive(const FXString & prefix)159 FXbool FXFile::setCurrentDrive(const FXString& prefix){
160   FXchar buffer[3];
161   if(!prefix.empty() && isalpha((FXuchar)prefix[0]) && prefix[1]==':'){
162     buffer[0]=prefix[0];
163     buffer[1]=':';
164     buffer[2]='\0';
165     return SetCurrentDirectory(buffer);
166     }
167   return FALSE;
168   }
169 
170 #else
171 
172 // Change current drive prefix "a:"
setCurrentDrive(const FXString &)173 FXbool FXFile::setCurrentDrive(const FXString&){
174   return TRUE;
175   }
176 
177 #endif
178 
179 
180 // Get home directory for a given user
getUserDirectory(const FXString & user)181 FXString FXFile::getUserDirectory(const FXString& user){
182 #ifndef WIN32
183 #if defined(FOX_THREAD_SAFE) && !defined(__FreeBSD__)
184   struct passwd pwdresult,*pwd;
185   char buffer[1024];
186   if(user.empty()){
187     register const FXchar* str;
188     if((str=getenv("HOME"))!=NULL) return str;
189     if((str=getenv("USER"))!=NULL || (str=getenv("LOGNAME"))!=NULL){
190       if(getpwnam_r(str,&pwdresult,buffer,sizeof(buffer),&pwd)==0 && pwd) return pwd->pw_dir;
191       }
192     if(getpwuid_r(getuid(),&pwdresult,buffer,sizeof(buffer),&pwd)==0 && pwd) return pwd->pw_dir;
193     return PATHSEPSTRING;
194     }
195   if(getpwnam_r(user.text(),&pwdresult,buffer,sizeof(buffer),&pwd)==0 && pwd) return pwd->pw_dir;
196   return PATHSEPSTRING;
197 #else
198   register struct passwd *pwd;
199   if(user.empty()){
200     register const FXchar* str;
201     if((str=getenv("HOME"))!=NULL) return str;
202     if((str=getenv("USER"))!=NULL || (str=getenv("LOGNAME"))!=NULL){
203       if((pwd=getpwnam(str))!=NULL) return pwd->pw_dir;
204       }
205     if((pwd=getpwuid(getuid()))!=NULL) return pwd->pw_dir;
206     return PATHSEPSTRING;
207     }
208   if((pwd=getpwnam(user.text()))!=NULL) return pwd->pw_dir;
209   return PATHSEPSTRING;
210 #endif
211 #else
212   if(user.empty()){
213     register const FXchar *str1,*str2;
214     if((str1=getenv("USERPROFILE"))!=NULL) return str1; // Dani�l H�rchner <dbjh@gmx.net>
215     if((str1=getenv("HOME"))!=NULL) return str1;
216     if((str2=getenv("HOMEPATH"))!=NULL){      // This should be good for WinNT, Win2K according to MSDN
217       if((str1=getenv("HOMEDRIVE"))==NULL) str1="c:";
218       return FXString(str1,str2);
219       }
220 //  FXchar buffer[MAX_PATH]
221 //  if(SHGetFolderPath(NULL,CSIDL_PERSONAL|CSIDL_FLAG_CREATE,NULL,O,buffer)==S_OK){
222 //    return buffer;
223 //    }
224     HKEY hKey;
225     if(RegOpenKeyEx(HKEY_CURRENT_USER,"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",0,KEY_READ,&hKey)==ERROR_SUCCESS){
226       FXchar home[MAXPATHLEN];
227       DWORD size=MAXPATHLEN;
228       LONG result=RegQueryValueEx(hKey,"Personal",NULL,NULL,(LPBYTE)home,&size);  // Change "Personal" to "Desktop" if you want...
229       RegCloseKey(hKey);
230       if(result==ERROR_SUCCESS) return home;
231       }
232     return "c:" PATHSEPSTRING;
233     }
234   return "c:" PATHSEPSTRING;
235 #endif
236   }
237 
238 
239 // Return the home directory for the current user.
getHomeDirectory()240 FXString FXFile::getHomeDirectory(){
241   return getUserDirectory(FXString::null);
242   }
243 
244 
245 // Get executable path
getExecPath()246 FXString FXFile::getExecPath(){
247   return FXString(getenv("PATH"));
248   }
249 
250 
251 // Return temporary directory.
getTempDirectory()252 FXString FXFile::getTempDirectory(){
253 #ifndef WIN32
254   // Conform Linux File Hierarchy standard; this should be
255   // good for SUN, SGI, HP-UX, AIX, and OSF1 also.
256   return FXString("/tmp",4);
257 #else
258   FXchar buffer[MAXPATHLEN];
259   FXuint len=GetTempPath(MAXPATHLEN,buffer);
260   if(1<len && ISPATHSEP(buffer[len-1]) && !ISPATHSEP(buffer[len-2])) len--;
261   return FXString(buffer,len);
262 #endif
263   }
264 
265 
266 // Return directory part of pathname, assuming full pathname.
267 // Note that directory("/bla/bla/") is "/bla/bla" and NOT "/bla".
268 // However, directory("/bla/bla") is "/bla" as we expect!
directory(const FXString & file)269 FXString FXFile::directory(const FXString& file){
270   register FXint n,i;
271   if(!file.empty()){
272     i=0;
273 #ifdef WIN32
274     if(isalpha((FXuchar)file[0]) && file[1]==':') i=2;
275 #endif
276     if(ISPATHSEP(file[i])) i++;
277     n=i;
278     while(file[i]){
279       if(ISPATHSEP(file[i])) n=i;
280       i++;
281       }
282     return FXString(file.text(),n);
283     }
284   return FXString::null;
285   }
286 
287 
288 // Return name and extension part of pathname.
289 // Note that name("/bla/bla/") is "" and NOT "bla".
290 // However, name("/bla/bla") is "bla" as we expect!
name(const FXString & file)291 FXString FXFile::name(const FXString& file){
292   register FXint f,n;
293   if(!file.empty()){
294     n=0;
295 #ifdef WIN32
296     if(isalpha((FXuchar)file[0]) && file[1]==':') n=2;
297 #endif
298     f=n;
299     while(file[n]){
300       if(ISPATHSEP(file[n])) f=n+1;
301       n++;
302       }
303     return FXString(file.text()+f,n-f);
304     }
305   return FXString::null;
306   }
307 
308 
309 // Return file title, i.e. document name only:
310 //
311 //  /path/aa        -> aa
312 //  /path/aa.bb     -> aa
313 //  /path/aa.bb.cc  -> aa.bb
314 //  /path/.aa       -> .aa
title(const FXString & file)315 FXString FXFile::title(const FXString& file){
316   register FXint f,e,b,i;
317   if(!file.empty()){
318     i=0;
319 #ifdef WIN32
320     if(isalpha((FXuchar)file[0]) && file[1]==':') i=2;
321 #endif
322     f=i;
323     while(file[i]){
324       if(ISPATHSEP(file[i])) f=i+1;
325       i++;
326       }
327     b=f;
328     if(file[b]=='.') b++;     // Leading '.'
329     e=i;
330     while(b<i){
331       if(file[--i]=='.'){ e=i; break; }
332       }
333     return FXString(file.text()+f,e-f);
334     }
335   return FXString::null;
336   }
337 
338 
339 // Return extension, if there is one:
340 //
341 //  /path/aa        -> ""
342 //  /path/aa.bb     -> bb
343 //  /path/aa.bb.cc  -> cc
344 //  /path/.aa       -> ""
extension(const FXString & file)345 FXString FXFile::extension(const FXString& file){
346   register FXint f,e,i,n;
347   if(!file.empty()){
348     n=0;
349 #ifdef WIN32
350     if(isalpha((FXuchar)file[0]) && file[1]==':') n=2;
351 #endif
352     f=n;
353     while(file[n]){
354       if(ISPATHSEP(file[n])) f=n+1;
355       n++;
356       }
357     if(file[f]=='.') f++;     // Leading '.'
358     e=i=n;
359     while(f<i){
360       if(file[--i]=='.'){ e=i+1; break; }
361       }
362     return FXString(file.text()+e,n-e);
363     }
364   return FXString::null;
365   }
366 
367 
368 // Return file name less the extension
369 //
370 //  /path/aa        -> /path/aa
371 //  /path/aa.bb     -> /path/aa
372 //  /path/aa.bb.cc  -> /path/aa.bb
373 //  /path/.aa       -> /path/.aa
stripExtension(const FXString & file)374 FXString FXFile::stripExtension(const FXString& file){
375   register FXint f,e,n;
376   if(!file.empty()){
377     n=0;
378 #ifdef WIN32
379     if(isalpha((FXuchar)file[0]) && file[1]==':') n=2;
380 #endif
381     f=n;
382     while(file[n]){
383       if(ISPATHSEP(file[n])) f=n+1;
384       n++;
385       }
386     if(file[f]=='.') f++;     // Leading '.'
387     e=n;
388     while(f<n){
389       if(file[--n]=='.'){ e=n; break; }
390       }
391     return FXString(file.text(),e);
392     }
393   return FXString::null;
394   }
395 
396 
397 #ifdef WIN32
398 
399 // Return drive letter prefix "c:"
drive(const FXString & file)400 FXString FXFile::drive(const FXString& file){
401   FXchar buffer[3];
402   if(isalpha((FXuchar)file[0]) && file[1]==':'){
403     buffer[0]=tolower((FXuchar)file[0]);
404     buffer[1]=':';
405     buffer[2]='\0';
406     return FXString(buffer,2);
407     }
408   return FXString::null;
409   }
410 
411 #else
412 
413 // Return drive letter prefix "c:"
drive(const FXString &)414 FXString FXFile::drive(const FXString&){
415   return FXString::null;
416   }
417 
418 #endif
419 
420 // Perform tilde or environment variable expansion
expand(const FXString & file)421 FXString FXFile::expand(const FXString& file){
422 #ifndef WIN32
423   if(!file.empty()){
424     register FXint b,e,n;
425     FXString result;
426 
427     // Expand leading tilde of the form ~/filename or ~user/filename
428     n=0;
429     if(file[n]=='~'){
430       n++;
431       b=n;
432       while(file[n] && !ISPATHSEP(file[n])) n++;
433       e=n;
434       result.append(getUserDirectory(file.mid(b,e-b)));
435       }
436 
437     // Expand environment variables of the form $HOME, ${HOME}, or $(HOME)
438     while(file[n]){
439       if(file[n]=='$'){
440         n++;
441         if(file[n]=='{' || file[n]=='(') n++;
442         b=n;
443         while(isalnum((FXuchar)file[n]) || file[n]=='_') n++;
444         e=n;
445         if(file[n]=='}' || file[n]==')') n++;
446         result.append(getEnvironment(file.mid(b,e-b)));
447         continue;
448         }
449       result.append(file[n]);
450       n++;
451       }
452     return result;
453     }
454   return FXString::null;
455 #else
456   if(!file.empty()){
457     FXchar buffer[2048];
458 
459     // Expand environment variables of the form %HOMEPATH%
460     if(ExpandEnvironmentStrings(file.text(),buffer,sizeof(buffer))){
461       return buffer;
462       }
463     return file;
464     }
465   return FXString::null;
466 #endif
467   }
468 
469 
470 
471 // Simplify a file path; the path will remain relative if it was relative,
472 // or absolute if it was absolute.  Also, a trailing "/" will be preserved
473 // as this is important in other functions.
474 //
475 // Examples:
476 //
477 //  /aa/bb/../cc    -> /aa/cc
478 //  /aa/bb/../cc/   -> /aa/cc/
479 //  /aa/bb/../..    -> /
480 //  ../../bb        -> ../../bb
481 //  ../../bb/       -> ../../bb/
482 //  /../            -> /
483 //  ./aa/bb/../../  -> ./
484 //  a/..            -> .
485 //  a/../           -> ./
486 //  ./a             -> ./a
487 //  /////./././     -> /
488 //  c:/../          -> c:/
489 //  c:a/..          -> c:
490 //  /.              -> /
simplify(const FXString & file)491 FXString FXFile::simplify(const FXString& file){
492   if(!file.empty()){
493     FXString result=file;
494     register FXint p,q,s;
495     p=q=0;
496 #ifndef WIN32
497     if(ISPATHSEP(result[q])){
498       result[p++]=PATHSEP;
499       while(ISPATHSEP(result[q])) q++;
500       }
501 #else
502     if(ISPATHSEP(result[q])){         // UNC
503       result[p++]=PATHSEP;
504       q++;
505       if(ISPATHSEP(result[q])){
506         result[p++]=PATHSEP;
507         while(ISPATHSEP(result[q])) q++;
508         }
509       }
510     else if(isalpha((FXuchar)result[q]) && result[q+1]==':'){
511       result[p++]=result[q++];
512       result[p++]=':';
513       q++;
514       if(ISPATHSEP(result[q])){
515         result[p++]=PATHSEP;
516         while(ISPATHSEP(result[q])) q++;
517         }
518       }
519 #endif
520     s=p;
521     while(result[q]){
522       while(result[q] && !ISPATHSEP(result[q])){
523         result[p++]=result[q++];
524         }
525       if(2<=p && result[p-1]=='.' && ISPATHSEP(result[p-2]) && result[q]==0){
526         p-=1;
527         }
528       else if(2<=p && result[p-1]=='.' && ISPATHSEP(result[p-2]) && ISPATHSEP(result[q])){
529         p-=2;
530         }
531       else if(3<=p && result[p-1]=='.' && result[p-2]=='.' && ISPATHSEP(result[p-3]) && !(5<=p && result[p-4]=='.' && result[p-5]=='.')){
532         p-=2;
533         if(s+2<=p){
534           p-=2;
535           while(s<p && !ISPATHSEP(result[p])) p--;
536           if(p==0) result[p++]='.';
537           }
538         }
539       if(ISPATHSEP(result[q])){
540         while(ISPATHSEP(result[q])) q++;
541         if(!ISPATHSEP(result[p-1])) result[p++]=PATHSEP;
542         }
543       }
544     return result.trunc(p);
545     }
546   return FXString::null;
547   }
548 
549 
550 // Build absolute pathname
absolute(const FXString & file)551 FXString FXFile::absolute(const FXString& file){
552   if(file.empty()) return FXFile::getCurrentDirectory();
553 #ifndef WIN32
554   if(ISPATHSEP(file[0])) return FXFile::simplify(file);
555 #else
556   if(ISPATHSEP(file[0])){
557     if(ISPATHSEP(file[1])) return FXFile::simplify(file);   // UNC
558     return FXFile::simplify(FXFile::getCurrentDrive()+file);
559     }
560   if(isalpha((FXuchar)file[0]) && file[1]==':'){
561     if(ISPATHSEP(file[2])) return FXFile::simplify(file);
562     return FXFile::simplify(file.mid(0,2)+PATHSEPSTRING+file.mid(2,2147483647));
563     }
564 #endif
565   return FXFile::simplify(FXFile::getCurrentDirectory()+PATHSEPSTRING+file);
566   }
567 
568 
569 // Build absolute pathname from parts
absolute(const FXString & base,const FXString & file)570 FXString FXFile::absolute(const FXString& base,const FXString& file){
571   if(file.empty()) return FXFile::absolute(base);
572 #ifndef WIN32
573   if(ISPATHSEP(file[0])) return FXFile::simplify(file);
574 #else
575   if(ISPATHSEP(file[0])){
576     if(ISPATHSEP(file[1])) return FXFile::simplify(file);   // UNC
577     return FXFile::simplify(FXFile::getCurrentDrive()+file);
578     }
579   if(isalpha((FXuchar)file[0]) && file[1]==':'){
580     if(ISPATHSEP(file[2])) return FXFile::simplify(file);
581     return FXFile::simplify(file.mid(0,2)+PATHSEPSTRING+file.mid(2,2147483647));
582     }
583 #endif
584   return FXFile::simplify(FXFile::absolute(base)+PATHSEPSTRING+file);
585   }
586 
587 
588 #ifndef WIN32
589 
590 // Return root of given path; this is just "/" or "" if not absolute
root(const FXString & file)591 FXString FXFile::root(const FXString& file){
592   if(ISPATHSEP(file[0])){
593     return PATHSEPSTRING;
594     }
595   return FXString::null;
596   }
597 
598 #else
599 
600 // Return root of given path; this may be "\\" or "C:\" or "" if not absolute
root(const FXString & file)601 FXString FXFile::root(const FXString& file){
602   if(ISPATHSEP(file[0])){
603     if(ISPATHSEP(file[1])) return PATHSEPSTRING PATHSEPSTRING;   // UNC
604     return FXFile::getCurrentDrive()+PATHSEPSTRING;
605     }
606   if(isalpha((FXuchar)file[0]) && file[1]==':'){
607     if(ISPATHSEP(file[2])) return file.left(3);
608     return file.left(2)+PATHSEPSTRING;
609     }
610   return FXString::null;
611   }
612 
613 #endif
614 
615 
616 // Return relative path of file to given base directory
617 //
618 // Examples:
619 //
620 //  Base       File         Result
621 //  /a/b/c     /a/b/c/d     d
622 //  /a/b/c/    /a/b/c/d     d
623 //  /a/b/c/d   /a/b/c       ../
624 //  ../a/b/c   ../a/b/c/d   d
625 //  /a/b/c/d   /a/b/q       ../../q
626 //  /a/b/c     /a/b/c       .
627 //  /a/b/c/    /a/b/c/      .
628 //  ./a        ./b          ../b
629 //  a          b            ../b
relative(const FXString & base,const FXString & file)630 FXString FXFile::relative(const FXString& base,const FXString& file){
631   register FXint p,q,b;
632   FXString result;
633 
634   // Find branch point
635 #ifndef WIN32
636   for(p=b=0; base[p] && base[p]==file[p]; p++){
637     if(ISPATHSEP(file[p])) b=p;
638     }
639 #else
640   for(p=b=0; base[p] && tolower((FXuchar)base[p])==tolower((FXuchar)file[p]); p++){
641     if(ISPATHSEP(file[p])) b=p;
642     }
643 #endif
644 
645   // Paths are equal
646   if((base[p]=='\0' || (ISPATHSEP(base[p]) && base[p+1]=='\0')) && (file[p]=='\0' || (ISPATHSEP(file[p]) && file[p+1]=='\0'))){
647     return ".";
648     }
649 
650   // Directory base is prefix of file
651   if((base[p]=='\0' && ISPATHSEP(file[p])) || (file[p]=='\0' && ISPATHSEP(base[p]))){
652     b=p;
653     }
654 
655   // Up to branch point
656   for(p=q=b; base[p]; p=q){
657     while(base[q] && !ISPATHSEP(base[q])) q++;
658     if(q>p) result.append(".." PATHSEPSTRING);
659     while(base[q] && ISPATHSEP(base[q])) q++;
660     }
661 
662   // Strip leading path character off, if any
663   while(ISPATHSEP(file[b])) b++;
664 
665   // Append tail end
666   result.append(&file[b]);
667 
668   return result;
669   }
670 
671 
672 // Return relative path of file to the current directory
relative(const FXString & file)673 FXString FXFile::relative(const FXString& file){
674   return FXFile::relative(getCurrentDirectory(),file);
675   }
676 
677 
678 // Generate unique filename of the form pathnameXXX.ext, where
679 // pathname.ext is the original input file, and XXX is a number,
680 // possibly empty, that makes the file unique.
681 // (From: Mathew Robertson <mathew.robertson@mi-services.com>)
unique(const FXString & file)682 FXString FXFile::unique(const FXString& file){
683   if(!exists(file)) return file;
684   FXString ext=extension(file);
685   FXString path=stripExtension(file);           // Use the new API (Jeroen)
686   FXString filename;
687   register FXint count=0;
688   if(!ext.empty()) ext.prepend('.');            // Only add period when non-empty extension
689   while(count<1000){
690     filename.format("%s%i%s",path.text(),count,ext.text());
691     if(!exists(filename)) return filename;      // Return result here (Jeroen)
692     count++;
693     }
694   return FXString::null;
695   }
696 
697 
698 // Search pathlist for file
search(const FXString & pathlist,const FXString & file)699 FXString FXFile::search(const FXString& pathlist,const FXString& file){
700   if(!file.empty()){
701     FXString path;
702     FXint beg,end;
703 #ifndef WIN32
704     if(ISPATHSEP(file[0])){
705       if(exists(file)) return file;
706       return FXString::null;
707       }
708 #else
709     if(ISPATHSEP(file[0])){
710       if(ISPATHSEP(file[1])){
711         if(exists(file)) return file;   // UNC
712         return FXString::null;
713         }
714       path=FXFile::getCurrentDrive()+file;
715       if(exists(path)) return path;
716       return FXString::null;
717       }
718     if(isalpha((FXuchar)file[0]) && file[1]==':'){
719       if(exists(file)) return file;
720       return FXString::null;
721       }
722 #endif
723     for(beg=0; pathlist[beg]; beg=end){
724       while(pathlist[beg]==PATHLISTSEP) beg++;
725       for(end=beg; pathlist[end] && pathlist[end]!=PATHLISTSEP; end++);
726       if(beg==end) break;
727       path=absolute(expand(pathlist.mid(beg,end-beg)),file);
728       if(exists(path)) return path;
729       }
730     }
731   return FXString::null;
732   }
733 
734 
735 // Up one level, given absolute path
upLevel(const FXString & file)736 FXString FXFile::upLevel(const FXString& file){
737   if(!file.empty()){
738     FXint beg=0;
739     FXint end=file.length();
740 #ifndef WIN32
741     if(ISPATHSEP(file[0])) beg++;
742 #else
743     if(ISPATHSEP(file[0])){
744       beg++;
745       if(ISPATHSEP(file[1])) beg++;     // UNC
746       }
747     else if(isalpha((FXuchar)file[0]) && file[1]==':'){
748       beg+=2;
749       if(ISPATHSEP(file[2])) beg++;
750       }
751 #endif
752     if(beg<end && ISPATHSEP(file[end-1])) end--;
753     while(beg<end){ --end; if(ISPATHSEP(file[end])) break; }
754     return file.left(end);
755     }
756   return PATHSEPSTRING;
757   }
758 
759 
760 // Check if file represents absolute pathname
isAbsolute(const FXString & file)761 FXbool FXFile::isAbsolute(const FXString& file){
762 #ifndef WIN32
763   return !file.empty() && ISPATHSEP(file[0]);
764 #else
765   return !file.empty() && (ISPATHSEP(file[0]) || (isalpha((FXuchar)file[0]) && file[1]==':'));
766 #endif
767   }
768 
769 
770 // Does file represent topmost directory
isTopDirectory(const FXString & file)771 FXbool FXFile::isTopDirectory(const FXString& file){
772 #ifndef WIN32
773   return !file.empty() && ISPATHSEP(file[0]) && file[1]=='\0';
774 #else
775   return !file.empty() && ((ISPATHSEP(file[0]) && (file[1]=='\0' || (ISPATHSEP(file[1]) && file[2]=='\0'))) || (isalpha((FXuchar)file[0]) && file[1]==':' && (file[2]=='\0' || (ISPATHSEP(file[2]) && file[3]=='\0'))));
776 #endif
777   }
778 
779 
780 // Check if file represents a file
isFile(const FXString & file)781 FXbool FXFile::isFile(const FXString& file){
782 #ifndef WIN32
783   struct stat status;
784   return !file.empty() && (::stat(file.text(),&status)==0) && S_ISREG(status.st_mode);
785 #else
786   DWORD atts;
787   return !file.empty() && ((atts=GetFileAttributes(file.text()))!=0xFFFFFFFF) && !(atts&FILE_ATTRIBUTE_DIRECTORY);
788 #endif
789   }
790 
791 
792 // Check if file represents a link
isLink(const FXString & file)793 FXbool FXFile::isLink(const FXString& file){
794 #ifndef WIN32
795   struct stat status;
796   return !file.empty() && (::lstat(file.text(),&status)==0) && S_ISLNK(status.st_mode);
797 #else
798   return FALSE;
799 #endif
800   }
801 
802 
803 // Check if file represents a file share
isShare(const FXString & file)804 FXbool FXFile::isShare(const FXString& file){
805 #ifndef WIN32
806   return FALSE;
807 #else
808   return ISPATHSEP(file[0]) && ISPATHSEP(file[1]) && file.find(PATHSEP,2)<0;
809 #endif
810   }
811 
812 
813 /*
814 
815 
816 // Return true if input path represents a file share of the form "\\" or "\\server"
817 FXbool FXFile::isShare(const FXString& file){
818 #ifndef WIN32
819   return FALSE;
820 #else
821   if(ISPATHSEP(file[0]) && ISPATHSEP(file[1]) && file.find(PATHSEP,2)<0){
822     HANDLE hEnum;
823     NETRESOURCE host;
824     host.dwScope=RESOURCE_GLOBALNET;
825     host.dwType=RESOURCETYPE_DISK;
826     host.dwDisplayType=RESOURCEDISPLAYTYPE_GENERIC;
827     host.dwUsage=RESOURCEUSAGE_CONTAINER;
828     host.lpLocalName=NULL;
829     host.lpRemoteName=(char*)file.text();
830     host.lpComment=NULL;
831     host.lpProvider=NULL;
832 
833     // This shit thows "First-chance exception in blabla.exe (KERNEL32.DLL): 0x000006BA: (no name)"
834     // when non-existing server name is passed in.  Don't know if this is dangerous...
835     if(WNetOpenEnum((file[2]?RESOURCE_GLOBALNET:RESOURCE_CONTEXT),RESOURCETYPE_DISK,0,(file[2]?&host:NULL),&hEnum)==NO_ERROR){
836       WNetCloseEnum(hEnum);
837       return TRUE;
838       }
839     }
840   return FALSE;
841 #endif
842   }
843 */
844 
845 
846 // Check if file represents a directory
isDirectory(const FXString & file)847 FXbool FXFile::isDirectory(const FXString& file){
848 #ifndef WIN32
849   struct stat status;
850   return !file.empty() && (::stat(file.text(),&status)==0) && S_ISDIR(status.st_mode);
851 #else
852   DWORD atts;
853   return !file.empty() && ((atts=GetFileAttributes(file.text()))!=0xFFFFFFFF) && (atts&FILE_ATTRIBUTE_DIRECTORY);
854 #endif
855   }
856 
857 
858 // Return true if file is readable (thanks to gehriger@linkcad.com)
isReadable(const FXString & file)859 FXbool FXFile::isReadable(const FXString& file){
860   return !file.empty() && access(file.text(),R_OK)==0;
861   }
862 
863 
864 // Return true if file is writable (thanks to gehriger@linkcad.com)
isWritable(const FXString & file)865 FXbool FXFile::isWritable(const FXString& file){
866   return !file.empty() && access(file.text(),W_OK)==0;
867   }
868 
869 
870 // Return true if file is executable (thanks to gehriger@linkcad.com)
isExecutable(const FXString & file)871 FXbool FXFile::isExecutable(const FXString& file){
872 #ifndef WIN32
873   return !file.empty() && access(file.text(),X_OK)==0;
874 #else
875   SHFILEINFO sfi;
876   return !file.empty() && SHGetFileInfo(file.text(),0,&sfi,sizeof(SHFILEINFO),SHGFI_EXETYPE)!=0;
877 #endif
878   }
879 
880 
881 // Check if owner has full permissions
isOwnerReadWriteExecute(const FXString & file)882 FXbool FXFile::isOwnerReadWriteExecute(const FXString& file){
883 #ifndef WIN32
884   struct stat status;
885   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IRUSR) && (status.st_mode&S_IWUSR) && (status.st_mode&S_IXUSR);
886 #else
887   return TRUE;
888 #endif
889   }
890 
891 
892 // Check if owner can read
isOwnerReadable(const FXString & file)893 FXbool FXFile::isOwnerReadable(const FXString& file){
894 #ifndef WIN32
895   struct stat status;
896   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IRUSR);
897 #else
898   DWORD atts;
899   return !file.empty() && ((atts=GetFileAttributes(file.text()))!=0xFFFFFFFF);
900 #endif
901   }
902 
903 
904 // Check if owner can write
isOwnerWritable(const FXString & file)905 FXbool FXFile::isOwnerWritable(const FXString& file){
906 #ifndef WIN32
907   struct stat status;
908   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IWUSR);
909 #else
910   DWORD atts;
911   return !file.empty() && ((atts=GetFileAttributes(file.text()))!=0xFFFFFFFF) && !(atts&FILE_ATTRIBUTE_READONLY);
912 #endif
913   }
914 
915 
916 // Check if owner can execute
isOwnerExecutable(const FXString & file)917 FXbool FXFile::isOwnerExecutable(const FXString& file){
918 #ifndef WIN32
919   struct stat status;
920   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IXUSR);
921 #else
922   SHFILEINFO sfi;
923   return !file.empty() && SHGetFileInfo(file.text(),0,&sfi,sizeof(SHFILEINFO),SHGFI_EXETYPE)!=0;
924 #endif
925   }
926 
927 
928 // Check if group has full permissions
isGroupReadWriteExecute(const FXString & file)929 FXbool FXFile::isGroupReadWriteExecute(const FXString& file){
930 #ifndef WIN32
931   struct stat status;
932   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IRGRP) && (status.st_mode&S_IWGRP) && (status.st_mode&S_IXGRP);
933 #else
934   return TRUE;
935 #endif
936   }
937 
938 
939 // Check if group can read
isGroupReadable(const FXString & file)940 FXbool FXFile::isGroupReadable(const FXString& file){
941 #ifndef WIN32
942   struct stat status;
943   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IRGRP);
944 #else
945   DWORD atts;
946   return !file.empty() && ((atts=GetFileAttributes(file.text()))!=0xFFFFFFFF);
947 #endif
948   }
949 
950 
951 // Check if group can write
isGroupWritable(const FXString & file)952 FXbool FXFile::isGroupWritable(const FXString& file){
953 #ifndef WIN32
954   struct stat status;
955   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IWGRP);
956 #else
957   DWORD atts;
958   return !file.empty() && ((atts=GetFileAttributes(file.text()))!=0xFFFFFFFF) && !(atts&FILE_ATTRIBUTE_READONLY);
959 #endif
960   }
961 
962 
963 // Check if group can execute
isGroupExecutable(const FXString & file)964 FXbool FXFile::isGroupExecutable(const FXString& file){
965 #ifndef WIN32
966   struct stat status;
967   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IXGRP);
968 #else
969   SHFILEINFO sfi;
970   return !file.empty() && SHGetFileInfo(file.text(),0,&sfi,sizeof(SHFILEINFO),SHGFI_EXETYPE)!=0;
971 #endif
972   }
973 
974 
975 // Check if everybody has full permissions
isOtherReadWriteExecute(const FXString & file)976 FXbool FXFile::isOtherReadWriteExecute(const FXString& file){
977 #ifndef WIN32
978   struct stat status;
979   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IROTH) && (status.st_mode&S_IWOTH) && (status.st_mode&S_IXOTH);
980 #else
981   return TRUE;
982 #endif
983   }
984 
985 
986 // Check if everybody can read
isOtherReadable(const FXString & file)987 FXbool FXFile::isOtherReadable(const FXString& file){
988 #ifndef WIN32
989   struct stat status;
990   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IROTH);
991 #else
992   DWORD atts;
993   return !file.empty() && ((atts=GetFileAttributes(file.text()))!=0xFFFFFFFF);
994 #endif
995   }
996 
997 
998 // Check if everybody can write
isOtherWritable(const FXString & file)999 FXbool FXFile::isOtherWritable(const FXString& file){
1000 #ifndef WIN32
1001   struct stat status;
1002   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IWOTH);
1003 #else
1004   DWORD atts;
1005   return !file.empty() && ((atts=GetFileAttributes(file.text()))!=0xFFFFFFFF) && !(atts&FILE_ATTRIBUTE_READONLY);
1006 #endif
1007   }
1008 
1009 
1010 // Check if everybody can execute
isOtherExecutable(const FXString & file)1011 FXbool FXFile::isOtherExecutable(const FXString& file){
1012 #ifndef WIN32
1013   struct stat status;
1014   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_IXOTH);
1015 #else
1016   SHFILEINFO sfi;
1017   return !file.empty() && SHGetFileInfo(file.text(),0,&sfi,sizeof(SHFILEINFO),SHGFI_EXETYPE)!=0;
1018 #endif
1019   }
1020 
1021 
1022 // These 5 functions below contributed by calvin@users.sourceforge.net
1023 
1024 
1025 // Test if suid bit set
isSetUid(const FXString & file)1026 FXbool FXFile::isSetUid(const FXString& file){
1027 #ifndef WIN32
1028   struct stat status;
1029   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_ISUID);
1030 #else
1031   return FALSE;
1032 #endif
1033   }
1034 
1035 
1036 // Test if sgid bit set
isSetGid(const FXString & file)1037 FXbool FXFile::isSetGid(const FXString& file){
1038 #ifndef WIN32
1039   struct stat status;
1040   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_ISGID);
1041 #else
1042   return FALSE;
1043 #endif
1044   }
1045 
1046 
1047 // Test if sticky bit set
isSetSticky(const FXString & file)1048 FXbool FXFile::isSetSticky(const FXString& file){
1049 #ifndef WIN32
1050   struct stat status;
1051   return !file.empty() && (::stat(file.text(),&status)==0) && (status.st_mode&S_ISVTX);
1052 #else
1053   return FALSE;
1054 #endif
1055   }
1056 
1057 
1058 // Return owner name from uid
owner(FXuint uid)1059 FXString FXFile::owner(FXuint uid){
1060   FXchar result[64];
1061 #ifndef WIN32
1062 #if defined(FOX_THREAD_SAFE) && !defined(__FreeBSD__)
1063   struct passwd pwdresult,*pwd;
1064   char buffer[1024];
1065   if(getpwuid_r(uid,&pwdresult,buffer,sizeof(buffer),&pwd)==0 && pwd) return pwd->pw_name;
1066 #else
1067   struct passwd *pwd=getpwuid(uid);
1068   if(pwd) return pwd->pw_name;
1069 #endif
1070 #endif
1071   sprintf(result,"%u",uid);
1072   return result;
1073   }
1074 
1075 
1076 // Return group name from gid
group(FXuint gid)1077 FXString FXFile::group(FXuint gid){
1078   FXchar result[64];
1079 #ifndef WIN32
1080 #if defined(FOX_THREAD_SAFE) && !defined(__FreeBSD__)
1081   ::group grpresult;
1082   ::group *grp;
1083   char buffer[1024];
1084   if(getgrgid_r(gid,&grpresult,buffer,sizeof(buffer),&grp)==0 && grp) return grp->gr_name;
1085 #else
1086   ::group *grp=getgrgid(gid);
1087   if(grp) return grp->gr_name;
1088 #endif
1089 #endif
1090   sprintf(result,"%u",gid);
1091   return result;
1092   }
1093 
1094 
1095 // Return owner name of file
owner(const FXString & file)1096 FXString FXFile::owner(const FXString& file){
1097   struct stat status;
1098   if(!file.empty() && ::stat(file.text(),&status)==0){
1099     return FXFile::owner(status.st_uid);
1100     }
1101   return FXString::null;
1102   }
1103 
1104 
1105 // Return group name of file
group(const FXString & file)1106 FXString FXFile::group(const FXString& file){
1107   struct stat status;
1108   if(!file.empty() && ::stat(file.text(),&status)==0){
1109     return FXFile::group(status.st_gid);
1110     }
1111   return FXString::null;
1112   }
1113 
1114 
1115 /// Return permissions string
permissions(FXuint mode)1116 FXString FXFile::permissions(FXuint mode){
1117   FXchar result[11];
1118 #ifndef WIN32
1119   result[0]=S_ISLNK(mode) ? 'l' : S_ISREG(mode) ? '-' : S_ISDIR(mode) ? 'd' : S_ISCHR(mode) ? 'c' : S_ISBLK(mode) ? 'b' : S_ISFIFO(mode) ? 'p' : S_ISSOCK(mode) ? 's' : '?';
1120   result[1]=(mode&S_IRUSR) ? 'r' : '-';
1121   result[2]=(mode&S_IWUSR) ? 'w' : '-';
1122   result[3]=(mode&S_ISUID) ? 's' : (mode&S_IXUSR) ? 'x' : '-';
1123   result[4]=(mode&S_IRGRP) ? 'r' : '-';
1124   result[5]=(mode&S_IWGRP) ? 'w' : '-';
1125   result[6]=(mode&S_ISGID) ? 's' : (mode&S_IXGRP) ? 'x' : '-';
1126   result[7]=(mode&S_IROTH) ? 'r' : '-';
1127   result[8]=(mode&S_IWOTH) ? 'w' : '-';
1128   result[9]=(mode&S_ISVTX) ? 't' : (mode&S_IXOTH) ? 'x' : '-';
1129   result[10]=0;
1130 #else
1131   result[0]='-';
1132 #ifdef _S_IFDIR
1133   if(mode&_S_IFDIR) result[0]='d';
1134 #endif
1135 #ifdef _S_IFCHR
1136   if(mode&_S_IFCHR) result[0]='c';
1137 #endif
1138 #ifdef _S_IFIFO
1139   if(mode&_S_IFIFO) result[0]='p';
1140 #endif
1141   result[1]='r';
1142   result[2]='w';
1143   result[3]='x';
1144   result[4]='r';
1145   result[5]='w';
1146   result[6]='x';
1147   result[7]='r';
1148   result[8]='w';
1149   result[9]='x';
1150   result[10]=0;
1151 #endif
1152   return result;
1153   }
1154 
1155 /*
1156 // Convert FILETIME (# 100ns since 01/01/1601) to time_t (# s since 01/01/1970)
1157 static time_t fxfiletime(const FILETIME& ft){
1158   FXlong ll=(((FXlong)ft.dwHighDateTime)<<32) | (FXlong)ft.dwLowDateTime;
1159 #if defined(__CYGWIN__) || defined(__MINGW32__)
1160   ll=ll-116444736000000000LL;
1161 #else
1162   ll=ll-116444736000000000L;    // 0x19DB1DED53E8000
1163 #endif
1164   ll=ll/10000000;
1165   if(ll<0) ll=0;
1166   return (time_t)ll;
1167   }
1168 */
1169 
1170 //    hFile=CreateFile(pathname,GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
1171 //    if(hFile!=INVALID_HANDLE_VALUE){
1172 //      GetFileTime(hFile,NULL,NULL,&ftLastWriteTime);
1173 //      CloseHandle(hFile);
1174 
1175 //       filetime=fxfiletime(ftLastWriteTime);
1176 
1177 // Return time file was last modified
modified(const FXString & file)1178 FXTime FXFile::modified(const FXString& file){
1179 #ifndef WIN32
1180   struct stat status;
1181   return !file.empty() && (::stat(file.text(),&status)==0) ? (FXTime)status.st_mtime : 0L;
1182 #else
1183   struct stat status;
1184   return !file.empty() && (::stat(file.text(),&status)==0) ? (FXTime)status.st_mtime : 0L;
1185 #endif
1186   }
1187 
1188 
1189 // Return time file was last accessed
accessed(const FXString & file)1190 FXTime FXFile::accessed(const FXString& file){
1191 #ifndef WIN32
1192   struct stat status;
1193   return !file.empty() && (::stat(file.text(),&status)==0) ? (FXTime)status.st_atime : 0L;
1194 #else
1195   struct stat status;
1196   return !file.empty() && (::stat(file.text(),&status)==0) ? (FXTime)status.st_atime : 0L;
1197 #endif
1198   }
1199 
1200 
1201 // Return time when created
created(const FXString & file)1202 FXTime FXFile::created(const FXString& file){
1203 #ifndef WIN32
1204   struct stat status;
1205   return !file.empty() && (::stat(file.text(),&status)==0) ? (FXTime)status.st_ctime : 0L;
1206 #else
1207   struct stat status;
1208   return !file.empty() && (::stat(file.text(),&status)==0) ? (FXTime)status.st_ctime : 0L;
1209 #endif
1210   }
1211 
1212 
1213 // Return time when "touched"
touched(const FXString & file)1214 FXTime FXFile::touched(const FXString& file){
1215 #ifndef WIN32
1216   struct stat status;
1217   return !file.empty() && (::stat(file.text(),&status)==0) ? (FXTime)FXMAX(status.st_ctime,status.st_mtime) : 0L;
1218 #else
1219   struct stat status;
1220   return !file.empty() && (::stat(file.text(),&status)==0) ? (FXTime)FXMAX(status.st_ctime,status.st_mtime) : 0L;
1221 #endif
1222   }
1223 
1224 
1225 
1226 #ifndef WIN32                 // UNIX
1227 
1228 
1229 // List all the files in directory
listFiles(FXString * & filelist,const FXString & path,const FXString & pattern,FXuint flags)1230 FXint FXFile::listFiles(FXString*& filelist,const FXString& path,const FXString& pattern,FXuint flags){
1231   FXuint matchmode=FILEMATCH_FILE_NAME|FILEMATCH_NOESCAPE;
1232   FXString pathname;
1233   FXString name;
1234   struct dirent *dp;
1235   FXString *newlist;
1236   FXint count=0;
1237   FXint size=0;
1238   DIR *dirp;
1239   struct stat inf;
1240 
1241   // Initialize to empty
1242   filelist=NULL;
1243 /*
1244   // One single root under Unix
1245   if(path.empty()){
1246     filelist=new FXString[2];
1247     list[count++]=PATHSEPSTRING;
1248     return count;
1249     }
1250 */
1251   // Folding case
1252   if(flags&LIST_CASEFOLD) matchmode|=FILEMATCH_CASEFOLD;
1253 
1254   // Get directory stream pointer
1255   dirp=opendir(path.text());
1256   if(dirp){
1257 
1258     // Loop over directory entries
1259 #ifdef FOX_THREAD_SAFE
1260     struct fxdirent dirresult;
1261     while(!readdir_r(dirp,&dirresult,&dp) && dp){
1262 #else
1263     while((dp=readdir(dirp))!=NULL){
1264 #endif
1265 
1266       // Get name
1267       name=dp->d_name;
1268 
1269       // Build full pathname
1270       pathname=path;
1271       if(!ISPATHSEP(pathname[pathname.length()-1])) pathname+=PATHSEPSTRING;
1272       pathname+=name;
1273 
1274       // Get info on file
1275       if(!info(pathname,inf)) continue;
1276 
1277       // Filter out files; a bit tricky...
1278       if(!S_ISDIR(inf.st_mode) && ((flags&LIST_NO_FILES) || (name[0]=='.' && !(flags&LIST_HIDDEN_FILES)) || (!(flags&LIST_ALL_FILES) && !match(pattern,name,matchmode)))) continue;
1279 
1280       // Filter out directories; even more tricky!
1281       if(S_ISDIR(inf.st_mode) && ((flags&LIST_NO_DIRS) || (name[0]=='.' && (name[1]==0 || (name[1]=='.' && name[2]==0 && (flags&LIST_NO_PARENT)) || (name[1]!='.' && !(flags&LIST_HIDDEN_DIRS)))) || (!(flags&LIST_ALL_DIRS) && !match(pattern,name,matchmode)))) continue;
1282 
1283       // Grow list
1284       if(count+1>=size){
1285         size=size?(size<<1):256;
1286         newlist=new FXString [size];
1287         for(int i=0; i<count; i++) newlist[i]=filelist[i];
1288         delete [] filelist;
1289         filelist=newlist;
1290         }
1291 
1292       // Add to list
1293       filelist[count++]=name;
1294       }
1295     closedir(dirp);
1296     }
1297   return count;
1298   }
1299 
1300 
1301 #else                         // WINDOWS
1302 
1303 
1304 // List all the files in directory
1305 FXint FXFile::listFiles(FXString*& filelist,const FXString& path,const FXString& pattern,FXuint flags){
1306   FXuint matchmode=FILEMATCH_FILE_NAME|FILEMATCH_NOESCAPE;
1307   FXString pathname;
1308   FXString name;
1309   FXString *newlist;
1310   FXint count=0;
1311   FXint size=0;
1312   WIN32_FIND_DATA ffData;
1313   DWORD nCount,nSize,i,j;
1314   HANDLE hFindFile,hEnum;
1315   FXchar server[200];
1316 
1317   // Initialize to empty
1318   filelist=NULL;
1319 
1320 /*
1321   // Each drive is a root on windows
1322   if(path.empty()){
1323     FXchar letter[4];
1324     letter[0]='a';
1325     letter[1]=':';
1326     letter[2]=PATHSEP;
1327     letter[3]='\0';
1328     filelist=new FXString[28];
1329     for(DWORD mask=GetLogicalDrives(); mask; mask>>=1,letter[0]++){
1330       if(mask&1) list[count++]=letter;
1331       }
1332     filelist[count++]=PATHSEPSTRING PATHSEPSTRING;    // UNC for file shares
1333     return count;
1334     }
1335 */
1336 /*
1337   // A UNC name was given of the form "\\" or "\\server"
1338   if(ISPATHSEP(path[0]) && ISPATHSEP(path[1]) && path.find(PATHSEP,2)<0){
1339     NETRESOURCE host;
1340 
1341     // Fill in
1342     host.dwScope=RESOURCE_GLOBALNET;
1343     host.dwType=RESOURCETYPE_DISK;
1344     host.dwDisplayType=RESOURCEDISPLAYTYPE_GENERIC;
1345     host.dwUsage=RESOURCEUSAGE_CONTAINER;
1346     host.lpLocalName=NULL;
1347     host.lpRemoteName=(char*)path.text();
1348     host.lpComment=NULL;
1349     host.lpProvider=NULL;
1350 
1351     // Open network enumeration
1352     if(WNetOpenEnum((path[2]?RESOURCE_GLOBALNET:RESOURCE_CONTEXT),RESOURCETYPE_DISK,0,(path[2]?&host:NULL),&hEnum)==NO_ERROR){
1353       NETRESOURCE resource[16384/sizeof(NETRESOURCE)];
1354       FXTRACE((1,"Enumerating=%s\n",path.text()));
1355       while(1){
1356         nCount=-1;    // Read as many as will fit
1357         nSize=sizeof(resource);
1358         if(WNetEnumResource(hEnum,&nCount,resource,&nSize)!=NO_ERROR) break;
1359         for(i=0; i<nCount; i++){
1360 
1361           // Dump what we found
1362           FXTRACE((1,"dwScope=%s\n",resource[i].dwScope==RESOURCE_CONNECTED?"RESOURCE_CONNECTED":resource[i].dwScope==RESOURCE_GLOBALNET?"RESOURCE_GLOBALNET":resource[i].dwScope==RESOURCE_REMEMBERED?"RESOURCE_REMEMBERED":"?"));
1363           FXTRACE((1,"dwType=%s\n",resource[i].dwType==RESOURCETYPE_ANY?"RESOURCETYPE_ANY":resource[i].dwType==RESOURCETYPE_DISK?"RESOURCETYPE_DISK":resource[i].dwType==RESOURCETYPE_PRINT?"RESOURCETYPE_PRINT":"?"));
1364           FXTRACE((1,"dwDisplayType=%s\n",resource[i].dwDisplayType==RESOURCEDISPLAYTYPE_DOMAIN?"RESOURCEDISPLAYTYPE_DOMAIN":resource[i].dwDisplayType==RESOURCEDISPLAYTYPE_SERVER?"RESOURCEDISPLAYTYPE_SERVER":resource[i].dwDisplayType==RESOURCEDISPLAYTYPE_SHARE?"RESOURCEDISPLAYTYPE_SHARE":resource[i].dwDisplayType==RESOURCEDISPLAYTYPE_GENERIC?"RESOURCEDISPLAYTYPE_GENERIC":resource[i].dwDisplayType==6?"RESOURCEDISPLAYTYPE_NETWORK":resource[i].dwDisplayType==7?"RESOURCEDISPLAYTYPE_ROOT":resource[i].dwDisplayType==8?"RESOURCEDISPLAYTYPE_SHAREADMIN":resource[i].dwDisplayType==9?"RESOURCEDISPLAYTYPE_DIRECTORY":resource[i].dwDisplayType==10?"RESOURCEDISPLAYTYPE_TREE":resource[i].dwDisplayType==11?"RESOURCEDISPLAYTYPE_NDSCONTAINER":"?"));
1365           FXTRACE((1,"dwUsage=%s\n",resource[i].dwUsage==RESOURCEUSAGE_CONNECTABLE?"RESOURCEUSAGE_CONNECTABLE":resource[i].dwUsage==RESOURCEUSAGE_CONTAINER?"RESOURCEUSAGE_CONTAINER":"?"));
1366           FXTRACE((1,"lpLocalName=%s\n",resource[i].lpLocalName));
1367           FXTRACE((1,"lpRemoteName=%s\n",resource[i].lpRemoteName));
1368           FXTRACE((1,"lpComment=%s\n",resource[i].lpComment));
1369           FXTRACE((1,"lpProvider=%s\n\n",resource[i].lpProvider));
1370 
1371           // Grow list
1372           if(count+1>=size){
1373             size=size?(size<<1):256;
1374             newlist=new FXString[size];
1375             for(j=0; j<count; j++) newlist[j]=list[j];
1376             delete [] filelist;
1377             filelist=newlist;
1378             }
1379 
1380           // Add remote name to list
1381           filelist[count]=resource[i].lpRemoteName;
1382           count++;
1383           }
1384         }
1385       WNetCloseEnum(hEnum);
1386       }
1387     return count;
1388     }
1389 */
1390   // Folding case
1391   if(flags&LIST_CASEFOLD) matchmode|=FILEMATCH_CASEFOLD;
1392 
1393   // Copy directory name
1394   pathname=path;
1395   if(!ISPATHSEP(pathname[pathname.length()-1])) pathname+=PATHSEPSTRING;
1396   pathname+="*";
1397 
1398   // Open directory
1399   hFindFile=FindFirstFile(pathname.text(),&ffData);
1400   if(hFindFile!=INVALID_HANDLE_VALUE){
1401 
1402     // Loop over directory entries
1403     do{
1404 
1405       // Get name
1406       name=ffData.cFileName;
1407 
1408       // Filter out files; a bit tricky...
1409       if(!(ffData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) && ((flags&LIST_NO_FILES) || ((ffData.dwFileAttributes&FILE_ATTRIBUTE_HIDDEN) && !(flags&LIST_HIDDEN_FILES)) || (!(flags&LIST_ALL_FILES) && !match(pattern,name,matchmode)))) continue;
1410 
1411       // Filter out directories; even more tricky!
1412       if((ffData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) && ((flags&LIST_NO_DIRS) || ((ffData.dwFileAttributes&FILE_ATTRIBUTE_HIDDEN) && !(flags&LIST_HIDDEN_DIRS)) || (name[0]=='.' && (name[1]==0 || (name[1]=='.' && name[2]==0 && (flags&LIST_NO_PARENT)))) || (!(flags&LIST_ALL_DIRS) && !match(pattern,name,matchmode)))) continue;
1413 
1414       // Grow list
1415       if(count+1>=size){
1416         size=size?(size<<1):256;
1417         newlist=new FXString[size];
1418         for(int f=0; f<count; f++) newlist[f]=filelist[f];
1419         delete [] filelist;
1420         filelist=newlist;
1421         }
1422 
1423       // Add to list
1424       filelist[count++]=name;
1425       }
1426     while(FindNextFile(hFindFile,&ffData));
1427     FindClose(hFindFile);
1428     }
1429   return count;
1430   }
1431 
1432 #endif
1433 
1434 
1435 // Convert file time to string as per strftime format
1436 FXString FXFile::time(const FXchar *format,FXTime filetime){
1437 #ifndef WIN32
1438 #if defined(FOX_THREAD_SAFE) && !defined(__FreeBSD__)
1439   time_t tmp=(time_t)FXMAX(filetime,0);
1440   struct tm tmresult;
1441   FXchar buffer[512];
1442   FXint len=strftime(buffer,sizeof(buffer),format,localtime_r(&tmp,&tmresult));
1443   return FXString(buffer,len);
1444 #else
1445   time_t tmp=(time_t)FXMAX(filetime,0);
1446   FXchar buffer[512];
1447   FXint len=strftime(buffer,sizeof(buffer),format,localtime(&tmp));
1448   return FXString(buffer,len);
1449 #endif
1450 #else
1451   time_t tmp=(time_t)FXMAX(filetime,0);
1452   FXchar buffer[512];
1453   FXint len=strftime(buffer,sizeof(buffer),format,localtime(&tmp));
1454   return FXString(buffer,len);
1455 #endif
1456   }
1457 
1458 
1459 
1460 // Convert file time to string
1461 FXString FXFile::time(FXTime filetime){
1462   return FXFile::time(TIMEFORMAT,filetime);
1463   }
1464 
1465 
1466 // Return current time
1467 FXTime FXFile::now(){
1468   return (FXTime)::time(NULL);
1469   }
1470 
1471 
1472 // Get file info
1473 FXbool FXFile::info(const FXString& file,struct stat& inf){
1474 #ifndef WIN32
1475   return !file.empty() && (::stat(file.text(),&inf)==0);
1476 #else
1477   return !file.empty() && (::stat(file.text(),&inf)==0);
1478 #endif
1479   }
1480 
1481 
1482 // Get file info
1483 FXbool FXFile::linkinfo(const FXString& file,struct stat& inf){
1484 #ifndef WIN32
1485   return !file.empty() && (::lstat(file.text(),&inf)==0);
1486 #else
1487   return !file.empty() && (::stat(file.text(),&inf)==0);
1488 #endif
1489   }
1490 
1491 
1492 // Get file size
1493 FXlong FXFile::size(const FXString& file){
1494   if(!file.empty()){
1495 #ifndef WIN32
1496     struct stat status;
1497     if(::stat(file.text(),&status)==0) return (FXlong)status.st_size;
1498 #else
1499     HANDLE fh=CreateFile(file.text(),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,0);
1500     if(fh!=INVALID_HANDLE_VALUE){
1501       DWORD lo,hi;
1502       lo=GetFileSize(fh,&hi);
1503       CloseHandle(fh);
1504       return (((FXlong)hi)<<32)+((FXlong)lo);
1505       }
1506 #endif
1507     }
1508   return 0L;
1509   }
1510 
1511 
1512 FXbool FXFile::exists(const FXString& file){
1513 #ifndef WIN32
1514   struct stat status;
1515   return !file.empty() && (::stat(file.text(),&status)==0);
1516 #else
1517   return !file.empty() && (GetFileAttributes(file.text())!=0xFFFFFFFF);
1518 #endif
1519   }
1520 
1521 
1522 
1523 #ifndef WIN32                 // UNIX
1524 
1525 // Enquote filename to make safe for shell
1526 FXString FXFile::enquote(const FXString& file,FXbool forcequotes){
1527   FXString result;
1528   register FXint i,c;
1529   for(i=0; (c=file[i])!='\0'; i++){
1530     switch(c){
1531       case '\'':              // Quote needs to be escaped
1532         result+="\\\'";
1533         break;
1534       case '\\':              // Backspace needs to be escaped, of course
1535         result+="\\\\";
1536         break;
1537       case '#':
1538       case '~':
1539         if(i) goto noquote;   // Only quote if at begin of filename
1540       case '!':               // Special in csh
1541       case '"':
1542       case '$':               // Variable substitution
1543       case '&':
1544       case '(':
1545       case ')':
1546       case ';':
1547       case '<':               // Redirections, pipe
1548       case '>':
1549       case '|':
1550       case '`':               // Command substitution
1551       case '^':               // Special in sh
1552       case '*':               // Wildcard characters
1553       case '?':
1554       case '[':
1555       case ']':
1556       case '\t':              // White space
1557       case '\n':
1558       case ' ':
1559         forcequotes=TRUE;
1560       default:                // Normal characters just added
1561 noquote:result+=c;
1562         break;
1563       }
1564     }
1565   if(forcequotes) return "'"+result+"'";
1566   return result;
1567   }
1568 
1569 
1570 // Decode filename to get original again
1571 FXString FXFile::dequote(const FXString& file){
1572   FXString result;
1573   register FXint i,c;
1574   i=0;
1575   while((c=file[i])!='\0' && isspace((FXuchar)c)) i++;
1576   if(file[i]=='\''){
1577     i++;
1578     while((c=file[i])!='\0' && c!='\''){
1579       if(c=='\\' && file[i+1]!='\0') c=file[++i];
1580       result+=c;
1581       i++;
1582       }
1583     }
1584   else{
1585     while((c=file[i])!='\0' && !isspace((FXuchar)c)){
1586       if(c=='\\' && file[i+1]!='\0') c=file[++i];
1587       result+=c;
1588       i++;
1589       }
1590     }
1591   return result;
1592   }
1593 
1594 
1595 
1596 #else                         // WINDOWS
1597 
1598 // Enquote filename to make safe for shell
1599 FXString FXFile::enquote(const FXString& file,FXbool forcequotes){
1600   FXString result;
1601   register FXint i,c;
1602   for(i=0; (c=file[i])!='\0'; i++){
1603     switch(c){
1604       case '<':               // Redirections
1605       case '>':
1606       case '|':
1607       case '$':
1608       case ':':
1609       case '*':               // Wildcards
1610       case '?':
1611       case ' ':               // White space
1612         forcequotes=TRUE;
1613       default:                // Normal characters just added
1614         result+=c;
1615         break;
1616       }
1617     }
1618   if(forcequotes) return "\""+result+"\"";
1619   return result;
1620   }
1621 
1622 
1623 // Decode filename to get original again
1624 FXString FXFile::dequote(const FXString& file){
1625   register FXint i,c;
1626   FXString result;
1627   i=0;
1628   while((c=file[i])!='\0' && isspace((FXuchar)c)) i++;
1629   if(file[i]=='"'){
1630     i++;
1631     while((c=file[i])!='\0' && c!='"'){
1632       result+=c;
1633       i++;
1634       }
1635     }
1636   else{
1637     while((c=file[i])!='\0' && !isspace((FXuchar)c)){
1638       result+=c;
1639       i++;
1640       }
1641     }
1642   return result;
1643   }
1644 
1645 #endif
1646 
1647 
1648 // Match filenames using *, ?, [^a-z], and so on
1649 FXbool FXFile::match(const FXString& pattern,const FXString& file,FXuint flags){
1650   return fxfilematch(pattern.text(),file.text(),flags);
1651   }
1652 
1653 
1654 // Return true if files are identical
1655 FXbool FXFile::identical(const FXString& file1,const FXString& file2){
1656   if(file1!=file2){
1657 #ifndef WIN32
1658     struct stat stat1,stat2;
1659     return !::lstat(file1.text(),&stat1) && !::lstat(file2.text(),&stat2) && stat1.st_ino==stat2.st_ino && stat1.st_dev==stat2.st_dev;
1660 #else
1661     FXbool same=FALSE;
1662     HANDLE hFile1;
1663     HANDLE hFile2;
1664     hFile1=CreateFile(file1.text(),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
1665     if(hFile1!=INVALID_HANDLE_VALUE){
1666       hFile2=CreateFile(file2.text(),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
1667       if(hFile2!=INVALID_HANDLE_VALUE){
1668         BY_HANDLE_FILE_INFORMATION info1;
1669         BY_HANDLE_FILE_INFORMATION info2;
1670         if(GetFileInformationByHandle(hFile1,&info1) && GetFileInformationByHandle(hFile2,&info2)){
1671           same=(info1.nFileIndexLow==info2.nFileIndexLow && info1.nFileIndexHigh==info2.nFileIndexHigh && info1.dwVolumeSerialNumber==info2.dwVolumeSerialNumber);
1672           }
1673         CloseHandle(hFile2);
1674         }
1675       CloseHandle(hFile1);
1676       }
1677     return same;
1678 #endif
1679     }
1680   return TRUE;
1681   }
1682 
1683 
1684 // Return file mode flags
1685 FXuint FXFile::mode(const FXString& file){
1686 #ifndef WIN32
1687   struct stat status;
1688   return !file.empty() && (::stat(file.text(),&status)==0) ? status.st_mode : 0;
1689 #else
1690   struct stat status;
1691   return !file.empty() && (::stat(file.text(),&status)==0) ? status.st_mode : 0;
1692 #endif
1693   }
1694 
1695 
1696 // Change the mode flags for this file
1697 FXbool FXFile::mode(const FXString& file,FXuint mode){
1698 #ifndef WIN32
1699   return !file.empty() && chmod(file.text(),mode)==0;
1700 #else
1701   return FALSE; // Unimplemented yet
1702 #endif
1703   }
1704 
1705 
1706 // Create new directory
1707 FXbool FXFile::createDirectory(const FXString& path,FXuint mode){
1708 #ifndef WIN32
1709   return mkdir(path.text(),mode)==0;
1710 #else
1711   return CreateDirectory(path.text(),NULL)!=0;
1712 #endif
1713   }
1714 
1715 
1716 // Create new (empty) file
1717 FXbool FXFile::createFile(const FXString& file,FXuint mode){
1718 #ifndef WIN32
1719   FXint fd=open(file.text(),O_CREAT|O_WRONLY|O_TRUNC|O_EXCL,mode);
1720   if(fd>=0){ close(fd); return TRUE; }
1721   return FALSE;
1722 #else
1723   HANDLE hFile=CreateFile(file.text(),GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
1724   if(hFile!=INVALID_HANDLE_VALUE){ CloseHandle(hFile); return TRUE; }
1725   return FALSE;
1726 #endif
1727   }
1728 
1729 
1730 // Hack code below for testing if volume is mounted
1731 
1732 // #if defined (HKS_NT)
1733 //
1734 // static int check_nfs (const char* name)
1735 // {
1736 // char drive[8];
1737 //
1738 // char* cp = strchr (name, ':');
1739 // if (cp)
1740 // {
1741 // strncpy (drive, name, cp - name);
1742 // drive[cp - name] = '\0';
1743 // }
1744 // else
1745 // {
1746 // drive[0] = 'A' + _getdrive() - 1;
1747 // drive[1] = '\0';
1748 // }
1749 //
1750 // strcat (drive, ":\\");
1751 //
1752 // return GetDriveType(drive) == DRIVE_REMOTE;
1753 // }
1754 //
1755 // #elif defined(LINUX)
1756 //
1757 // static int check_nfs (int fd)
1758 // {
1759 // struct statfs statbuf;
1760 // if (fstatfs(fd,&statbuf) < 0)
1761 // {
1762 // RFM_RAISE_SYSTEM_ERROR("statfs");
1763 // return 0;
1764 // }
1765 // if (statbuf.f_type == NFS_SUPER_MAGIC)
1766 // return 1;
1767 // else
1768 // return 0;
1769 // }
1770 //
1771 // #else
1772 //
1773 // static int check_nfs (int fd)
1774 // {
1775 //
1776 // struct statvfs statbuf;
1777 //
1778 // if (fstatvfs (fd, &statbuf) < 0)
1779 // {
1780 // RFM_RAISE_SYSTEM_ERROR ("fstatvfs");
1781 // }
1782 // return strncmp (statbuf.f_basetype, "nfs", 3) == 0 || strncmp
1783 // (statbuf.f_basetype, "NFS", 3) == 0;
1784 // }
1785 // #endif
1786 
1787 
1788 
1789 
1790 
1791 
1792 #ifndef WIN32
1793 
1794 
1795 // Read bytes
1796 static long fullread(int fd,unsigned char *ptr,long len){
1797   long nread;
1798 #ifdef EINTR
1799   do{nread=read(fd,ptr,len);}while(nread<0 && errno==EINTR);
1800 #else
1801   nread=read(fd,ptr,len);
1802 #endif
1803   return nread;
1804   }
1805 
1806 
1807 // Write bytes
1808 static long fullwrite(int fd,const unsigned char *ptr,long len){
1809   long nwritten,ntotalwritten=0;
1810   while(len>0){
1811     nwritten=write(fd,ptr,len);
1812     if(nwritten<0){
1813 #ifdef EINTR
1814       if(errno==EINTR) continue;
1815 #endif
1816       return -1;
1817       }
1818     ntotalwritten+=nwritten;
1819     ptr+=nwritten;
1820     len-=nwritten;
1821     }
1822   return ntotalwritten;
1823   }
1824 
1825 
1826 // Concatenate srcfile1 and srcfile2 to a dstfile
1827 FXbool FXFile::concatenate(const FXString& srcfile1,const FXString& srcfile2,const FXString& dstfile,FXbool overwrite){
1828   unsigned char buffer[4096];
1829   struct stat status;
1830   int src1,src2,dst;
1831   long nread,nwritten;
1832   FXbool ok=FALSE;
1833   if(srcfile1==dstfile || srcfile2==dstfile) return FALSE;
1834   if(::lstat(dstfile.text(),&status)==0){
1835     if(!overwrite) return FALSE;
1836     }
1837   dst=open(dstfile.text(),O_CREAT|O_WRONLY|O_TRUNC,0777);
1838   if(0<=dst){
1839     src1=open(srcfile1.text(),O_RDONLY);
1840     if(0<=src1){
1841       src2=open(srcfile2.text(),O_RDONLY);
1842       if(0<=src2){
1843         while(1){
1844           nread=fullread(src1,buffer,sizeof(buffer));
1845           if(nread<0) goto err;
1846           if(nread==0) break;
1847           nwritten=fullwrite(dst,buffer,nread);
1848           if(nwritten<0) goto err;
1849           }
1850         while(1){
1851           nread=fullread(src2,buffer,sizeof(buffer));
1852           if(nread<0) goto err;
1853           if(nread==0) break;
1854           nwritten=fullwrite(dst,buffer,nread);
1855           if(nwritten<0) goto err;
1856           }
1857         ok=TRUE;
1858 err:    close(src2);
1859         }
1860       close(src1);
1861       }
1862     close(dst);
1863     }
1864   return ok;
1865   }
1866 
1867 
1868 // Copy ordinary file
1869 static FXbool copyfile(const FXString& oldfile,const FXString& newfile){
1870   unsigned char buffer[4096];
1871   struct stat status;
1872   long nread,nwritten;
1873   int src,dst;
1874   FXbool ok=FALSE;
1875   if((src=open(oldfile.text(),O_RDONLY))>=0){
1876     if(::stat(oldfile.text(),&status)==0){
1877       if((dst=open(newfile.text(),O_WRONLY|O_CREAT|O_TRUNC,status.st_mode))>=0){
1878         while(1){
1879           nread=fullread(src,buffer,sizeof(buffer));
1880           if(nread<0) goto err;
1881           if(nread==0) break;
1882           nwritten=fullwrite(dst,buffer,nread);
1883           if(nwritten<0) goto err;
1884           }
1885         ok=TRUE;
1886 err:    close(dst);
1887         }
1888       }
1889     close(src);
1890     }
1891   return ok;
1892   }
1893 
1894 
1895 // To search visited inodes
1896 struct inodelist {
1897   ino_t st_ino;
1898   inodelist *next;
1899   };
1900 
1901 
1902 // Forward declararion
1903 static FXbool copyrec(const FXString& oldfile,const FXString& newfile,FXbool overwrite,inodelist* inodes);
1904 
1905 
1906 // Copy directory
1907 static FXbool copydir(const FXString& oldfile,const FXString& newfile,FXbool overwrite,struct stat& parentstatus,inodelist* inodes){
1908   FXString oldchild,newchild;
1909   struct stat status;
1910   inodelist *in,inode;
1911   struct dirent *dp;
1912   DIR *dirp;
1913 
1914   // See if visited this inode already
1915   for(in=inodes; in; in=in->next){
1916     if(in->st_ino==parentstatus.st_ino) return TRUE;
1917     }
1918 
1919   // Try make directory, if none exists yet
1920   if(mkdir(newfile.text(),parentstatus.st_mode|S_IWUSR)!=0 && errno!=EEXIST) return FALSE;
1921 
1922   // Can we stat it
1923   if(::lstat(newfile.text(),&status)!=0 || !S_ISDIR(status.st_mode)) return FALSE;
1924 
1925   // Try open directory to copy
1926   dirp=opendir(oldfile.text());
1927   if(!dirp) return FALSE;
1928 
1929   // Add this to the list
1930   inode.st_ino=status.st_ino;
1931   inode.next=inodes;
1932 
1933   // Copy stuff
1934 #ifdef FOX_THREAD_SAFE
1935   struct fxdirent dirresult;
1936   while(!readdir_r(dirp,&dirresult,&dp) && dp){
1937 #else
1938   while((dp=readdir(dirp))!=NULL){
1939 #endif
1940     if(dp->d_name[0]!='.' || (dp->d_name[1]!='\0' && (dp->d_name[1]!='.' || dp->d_name[2]!='\0'))){
1941       oldchild=oldfile;
1942       if(!ISPATHSEP(oldchild[oldchild.length()-1])) oldchild.append(PATHSEP);
1943       oldchild.append(dp->d_name);
1944       newchild=newfile;
1945       if(!ISPATHSEP(newchild[newchild.length()-1])) newchild.append(PATHSEP);
1946       newchild.append(dp->d_name);
1947       if(!copyrec(oldchild,newchild,overwrite,&inode)){
1948         closedir(dirp);
1949         return FALSE;
1950         }
1951       }
1952     }
1953 
1954   // Close directory
1955   closedir(dirp);
1956 
1957   // Success
1958   return TRUE;
1959   }
1960 
1961 
1962 
1963 
1964 // Recursive copy
1965 static FXbool copyrec(const FXString& oldfile,const FXString& newfile,FXbool overwrite,inodelist* inodes){
1966   struct stat status1,status2;
1967 
1968   // Old file or directory does not exist
1969   if(::lstat(oldfile.text(),&status1)!=0) return FALSE;
1970 
1971   // If target is not a directory, remove it if allowed
1972   if(::lstat(newfile.text(),&status2)==0){
1973     if(!S_ISDIR(status2.st_mode)){
1974       if(!overwrite) return FALSE;
1975       FXTRACE((100,"unlink(%s)\n",newfile.text()));
1976       if(::unlink(newfile.text())!=0) return FALSE;
1977       }
1978     }
1979 
1980   // Source is direcotory: copy recursively
1981   if(S_ISDIR(status1.st_mode)){
1982     return copydir(oldfile,newfile,overwrite,status1,inodes);
1983     }
1984 
1985   // Source is regular file: copy block by block
1986   if(S_ISREG(status1.st_mode)){
1987     FXTRACE((100,"copyfile(%s,%s)\n",oldfile.text(),newfile.text()));
1988     return copyfile(oldfile,newfile);
1989     }
1990 
1991   // Source is fifo: make a new one
1992   if(S_ISFIFO(status1.st_mode)){
1993     FXTRACE((100,"mkfifo(%s)\n",newfile.text()));
1994     return ::mkfifo(newfile.text(),status1.st_mode);
1995     }
1996 
1997   // Source is device: make a new one
1998   if(S_ISBLK(status1.st_mode) || S_ISCHR(status1.st_mode) || S_ISSOCK(status1.st_mode)){
1999     FXTRACE((100,"mknod(%s)\n",newfile.text()));
2000     return ::mknod(newfile.text(),status1.st_mode,status1.st_rdev)==0;
2001     }
2002 
2003   // Source is symbolic link: make a new one
2004   if(S_ISLNK(status1.st_mode)){
2005     FXString lnkfile=FXFile::symlink(oldfile);
2006     FXTRACE((100,"symlink(%s,%s)\n",lnkfile.text(),newfile.text()));
2007     return ::symlink(lnkfile.text(),newfile.text())==0;
2008     }
2009 
2010   // This shouldn't happen
2011   return FALSE;
2012   }
2013 
2014 
2015 #else
2016 
2017 
2018 // Concatenate srcfile1 and srcfile2 to a dstfile
2019 FXbool FXFile::concatenate(const FXString& srcfile1,const FXString& srcfile2,const FXString& dstfile,FXbool overwrite){
2020   unsigned char buffer[4096];
2021   HANDLE src1,src2,dst;
2022   DWORD nread,nwritten;
2023   FXbool ok=FALSE;
2024   if(srcfile1==dstfile || srcfile2==dstfile) return FALSE;
2025   if(GetFileAttributes(dstfile.text())!=0xFFFFFFFF){
2026     if(!overwrite) return FALSE;
2027     }
2028   dst=CreateFile(dstfile.text(),GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
2029   if(dst!=INVALID_HANDLE_VALUE){
2030     src1=CreateFile(srcfile1.text(),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
2031     if(src1!=INVALID_HANDLE_VALUE){
2032       src2=CreateFile(srcfile2.text(),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
2033       if(src2!=INVALID_HANDLE_VALUE){
2034         while(1){
2035           if(!ReadFile(src1,buffer,sizeof(buffer),&nread,NULL)) goto err;
2036           if(nread==0) break;
2037           if(!WriteFile(dst,buffer,nread,&nwritten,NULL)) goto err;
2038           }
2039         while(1){
2040           if(!ReadFile(src2,buffer,sizeof(buffer),&nread,NULL)) goto err;
2041           if(nread==0) break;
2042           if(!WriteFile(dst,buffer,nread,&nwritten,NULL)) goto err;
2043           }
2044         ok=TRUE;
2045 err:    CloseHandle(src2);
2046         }
2047       CloseHandle(src1);
2048       }
2049     CloseHandle(dst);
2050     }
2051   return ok;
2052   }
2053 
2054 
2055 // Forward declararion
2056 static FXbool copyrec(const FXString& oldfile,const FXString& newfile,FXbool overwrite);
2057 
2058 
2059 // Copy directory
2060 static FXbool copydir(const FXString& oldfile,const FXString& newfile,FXbool overwrite){
2061   FXString oldchild,newchild;
2062   DWORD atts;
2063   WIN32_FIND_DATA ffData;
2064   HANDLE hFindFile;
2065 
2066   // Try make directory, if none exists yet
2067 //  if(CreateDirectory(newfile.text(),NULL)==0 && GetLastError()!=ERROR_FILE_EXISTS) return FALSE;
2068   if(CreateDirectory(newfile.text(),NULL)==0){  // patch from "Malcolm Dane" <danem@talk21.com>
2069     switch(GetLastError()){
2070       case ERROR_FILE_EXISTS:
2071       case ERROR_ALREADY_EXISTS: break;
2072       default: return FALSE;
2073       }
2074     }
2075 
2076   // Can we stat it
2077   if((atts=GetFileAttributes(newfile.text()))==0xffffffff || !(atts&FILE_ATTRIBUTE_DIRECTORY)) return FALSE;
2078 
2079   // Try open directory to copy
2080   hFindFile=FindFirstFile((oldfile+PATHSEPSTRING+"*").text(),&ffData);
2081   if(hFindFile==INVALID_HANDLE_VALUE) return FALSE;
2082 
2083   // Copy stuff
2084   do{
2085     if(ffData.cFileName[0]!='.' && (ffData.cFileName[1]!='\0' && (ffData.cFileName[1]!='.' || ffData.cFileName[2]!='\0'))){
2086       oldchild=oldfile;
2087       if(!ISPATHSEP(oldchild[oldchild.length()-1])) oldchild.append(PATHSEP);
2088       oldchild.append(ffData.cFileName);
2089       newchild=newfile;
2090       if(!ISPATHSEP(newchild[newchild.length()-1])) newchild.append(PATHSEP);
2091       newchild.append(ffData.cFileName);
2092       if(!copyrec(oldchild,newchild,overwrite)){
2093         FindClose(hFindFile);
2094         return FALSE;
2095         }
2096       }
2097     }
2098   while(FindNextFile(hFindFile,&ffData));
2099 
2100   // Close directory
2101   FindClose(hFindFile);
2102 
2103   // Success
2104   return TRUE;
2105   }
2106 
2107 
2108 // Recursive copy
2109 static FXbool copyrec(const FXString& oldfile,const FXString& newfile,FXbool overwrite){
2110   DWORD atts1,atts2;
2111 
2112   // Old file or directory does not exist
2113   if((atts1=GetFileAttributes(oldfile.text()))==0xffffffff) return FALSE;
2114 
2115   // If target is not a directory, remove it if allowed
2116   if((atts2=GetFileAttributes(newfile.text()))!=0xffffffff){
2117     if(!(atts2&FILE_ATTRIBUTE_DIRECTORY)){
2118       if(!overwrite) return FALSE;
2119       FXTRACE((100,"DeleteFile(%s)\n",newfile.text()));
2120       if(DeleteFile(newfile.text())==0) return FALSE;
2121       }
2122     }
2123 
2124   // Source is direcotory: copy recursively
2125   if(atts1&FILE_ATTRIBUTE_DIRECTORY){
2126     return copydir(oldfile,newfile,overwrite);
2127     }
2128 
2129   // Source is regular file: copy block by block
2130   if(!(atts1&FILE_ATTRIBUTE_DIRECTORY)){
2131     FXTRACE((100,"CopyFile(%s,%s)\n",oldfile.text(),newfile.text()));
2132     return CopyFile(oldfile.text(),newfile.text(),!overwrite);
2133     }
2134 
2135   // This shouldn't happen
2136   return FALSE;
2137   }
2138 
2139 
2140 #endif
2141 
2142 
2143 // Copy file
2144 FXbool FXFile::copy(const FXString& oldfile,const FXString& newfile,FXbool overwrite){
2145   if(newfile!=oldfile){
2146 #ifndef WIN32
2147     return copyrec(oldfile,newfile,overwrite,NULL);
2148 #else
2149     return copyrec(oldfile,newfile,overwrite);      // No symlinks, so no need to check if directories are visited already
2150 #endif
2151     }
2152   return FALSE;
2153   }
2154 
2155 
2156 // Remove file or directory
2157 FXbool FXFile::remove(const FXString& file){
2158 #ifndef WIN32
2159   struct stat status;
2160   if(::lstat(file.text(),&status)==0){
2161     if(S_ISDIR(status.st_mode)){
2162       DIR *dirp=::opendir(file.text());
2163       if(dirp){
2164         FXString child;
2165         struct dirent *dp;
2166 #ifdef FOX_THREAD_SAFE
2167         struct fxdirent dirresult;
2168         while(!readdir_r(dirp,&dirresult,&dp) && dp){
2169 #else
2170         while((dp=readdir(dirp))!=NULL){
2171 #endif
2172           if(dp->d_name[0]!='.' || (dp->d_name[1]!='\0' && (dp->d_name[1]!='.' || dp->d_name[2]!='\0'))){
2173             child=file;
2174             if(!ISPATHSEP(child[child.length()-1])) child.append(PATHSEP);
2175             child.append(dp->d_name);
2176             if(!FXFile::remove(child)){
2177               ::closedir(dirp);
2178               return FALSE;
2179               }
2180             }
2181           }
2182         ::closedir(dirp);
2183         }
2184       FXTRACE((100,"rmdir(%s)\n",file.text()));
2185       return ::rmdir(file.text())==0;
2186       }
2187     else{
2188       FXTRACE((100,"unlink(%s)\n",file.text()));
2189       return ::unlink(file.text())==0;
2190       }
2191     }
2192   return FALSE;
2193 #else
2194   DWORD atts;
2195   if((atts=GetFileAttributes(file.text()))!=0xffffffff){
2196     if(atts&FILE_ATTRIBUTE_DIRECTORY){
2197       WIN32_FIND_DATA ffData;
2198       HANDLE hFindFile;
2199       hFindFile=FindFirstFile((file+PATHSEPSTRING+"*").text(),&ffData); // FIXME we may want to formalize the "walk over directory" in a few API's here also...
2200       if(hFindFile!=INVALID_HANDLE_VALUE){
2201         FXString child;
2202         do{
2203           if(ffData.cFileName[0]!='.' && (ffData.cFileName[1]!='\0' && (ffData.cFileName[1]!='.' || ffData.cFileName[2]!='\0'))){
2204             child=file;
2205             if(!ISPATHSEP(child[child.length()-1])) child.append(PATHSEP);
2206             child.append(ffData.cFileName);
2207             if(!FXFile::remove(child)){
2208               FindClose(hFindFile);
2209               return FALSE;
2210               }
2211             }
2212           }
2213         while(FindNextFile(hFindFile,&ffData));
2214         FindClose(hFindFile);
2215         }
2216       FXTRACE((100,"RemoveDirectory(%s)\n",file.text()));
2217       return RemoveDirectory(file.text())!=0;
2218       }
2219     else{
2220       FXTRACE((100,"DeleteFile(%s)\n",file.text()));
2221       return DeleteFile(file.text())!=0;
2222       }
2223     }
2224   return FALSE;
2225 #endif
2226   }
2227 
2228 
2229 // Rename or move file, or copy and delete old if different file systems
2230 FXbool FXFile::move(const FXString& oldfile,const FXString& newfile,FXbool overwrite){
2231   if(newfile!=oldfile){
2232 #ifndef WIN32
2233     if(!FXFile::exists(oldfile)) return FALSE;
2234     if(FXFile::exists(newfile)){
2235       if(!overwrite) return FALSE;
2236       if(!FXFile::remove(newfile)) return FALSE;
2237       }
2238     FXTRACE((100,"rename(%s,%s)\n",oldfile.text(),newfile.text()));
2239     if(::rename(oldfile.text(),newfile.text())==0) return TRUE;
2240     if(errno!=EXDEV) return FALSE;
2241     if(FXFile::copy(oldfile,newfile)){
2242       return FXFile::remove(oldfile);
2243       }
2244 #else
2245     if(!FXFile::exists(oldfile)) return FALSE;
2246     if(FXFile::exists(newfile)){
2247       if(!overwrite) return FALSE;
2248       if(!FXFile::remove(newfile)) return FALSE;
2249       }
2250     FXTRACE((100,"MoveFile(%s,%s)\n",oldfile.text(),newfile.text()));
2251     if(::MoveFile(oldfile.text(),newfile.text())!=0) return TRUE;
2252     if(GetLastError()!=ERROR_NOT_SAME_DEVICE) return FALSE;
2253     if(FXFile::copy(oldfile,newfile)){
2254       return FXFile::remove(oldfile);
2255       }
2256 #endif
2257     }
2258   return FALSE;
2259   }
2260 
2261 
2262 // Link file
2263 FXbool FXFile::link(const FXString& oldfile,const FXString& newfile,FXbool overwrite){
2264   if(newfile!=oldfile){
2265 #ifndef WIN32
2266     if(!FXFile::exists(oldfile)) return FALSE;
2267     if(FXFile::exists(newfile)){
2268       if(!overwrite) return FALSE;
2269       if(!FXFile::remove(newfile)) return FALSE;
2270       }
2271     FXTRACE((100,"link(%s,%s)\n",oldfile.text(),newfile.text()));
2272     return ::link(oldfile.text(),newfile.text())==0;
2273 #else
2274     typedef BOOL (WINAPI *PFN_CHL)(LPCTSTR,LPCTSTR,LPSECURITY_ATTRIBUTES);
2275     static PFN_CHL chl=NULL;
2276     if(!chl){
2277       HMODULE hkernel=LoadLibraryA("Kernel32");
2278       if(!hkernel) return FALSE;
2279       chl=(PFN_CHL)::GetProcAddress(hkernel,"CreateHardLinkA");
2280       FreeLibrary(hkernel);
2281       }
2282     if(!FXFile::exists(oldfile)) return FALSE;
2283     if(FXFile::exists(newfile)){
2284       if(!overwrite) return FALSE;
2285       if(!FXFile::remove(newfile)) return FALSE;
2286       }
2287     FXTRACE((100,"CreateHardLink(%s,%s)\n",oldfile.text(),newfile.text()));
2288     return chl && (*chl)(newfile.text(),oldfile.text(),NULL)!=0;
2289 #endif
2290     }
2291   return FALSE;
2292   }
2293 
2294 
2295 // Symbolic Link file
2296 FXbool FXFile::symlink(const FXString& oldfile,const FXString& newfile,FXbool overwrite){
2297 #ifndef WIN32
2298   if(newfile!=oldfile){
2299     if(!FXFile::exists(oldfile)) return FALSE;
2300     if(FXFile::exists(newfile)){
2301       if(!overwrite) return FALSE;
2302       if(!FXFile::remove(newfile)) return FALSE;
2303       }
2304     FXTRACE((100,"symlink(%s,%s)\n",oldfile.text(),newfile.text()));
2305     return ::symlink(oldfile.text(),newfile.text())==0;
2306     }
2307 #endif
2308   return FALSE;
2309   }
2310 
2311 
2312 // Read symbolic link
2313 FXString FXFile::symlink(const FXString& file){
2314 #ifndef WIN32
2315   FXchar lnk[MAXPATHLEN+1];
2316   FXint len=::readlink(file.text(),lnk,MAXPATHLEN);
2317   if(0<=len) return FXString(lnk,len);
2318 #endif
2319   return FXString::null;
2320   }
2321 
2322 }
2323 
2324