1 // VFSTools.cpp - useful functions and misc stuff
2 // For conditions of distribution and use, see copyright notice in VFS.h
3 
4 #include "VFSInternal.h"
5 #include "VFSTools.h"
6 
7 #include <algorithm>
8 #include <ctype.h>
9 
10 #if _WIN32
11 #   define WIN32_LEAN_AND_MEAN
12 #   include <windows.h>
13 #   include <io.h>
14 #else
15 #  include <dirent.h>
16 #  include <unistd.h>
17 #endif
18 
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 
22 VFS_NAMESPACE_START
23 
24 
25 #if !_WIN32
26 #ifdef DT_DIR
_IsFile(const char * path,dirent * dp)27 static bool _IsFile(const char *path, dirent *dp)
28 {
29     switch(dp->d_type)
30     {
31         case DT_DIR:
32             return false;
33         case DT_LNK:
34         {
35             std::string fullname = path;
36             fullname += '/';
37             fullname += dp->d_name;
38             struct stat statbuf;
39             if(stat(fullname.c_str(), &statbuf))
40                 return false; // error
41             return !S_ISDIR(statbuf.st_mode);
42         }
43         // TODO: for now, we consider other file types as regular files
44         default:
45             ;
46     }
47     return true;
48 }
49 
_IsDir(const char * path,dirent * dp)50 static bool _IsDir(const char *path, dirent *dp)
51 {
52     switch(dp->d_type)
53     {
54         case DT_DIR:
55             return true;
56         case DT_LNK:
57         {
58             std::string fullname = path;
59             fullname += '/';
60             fullname += dp->d_name;
61             struct stat statbuf;
62             if(stat(fullname.c_str(), &statbuf))
63                 return false; // error
64             return S_ISDIR(statbuf.st_mode);
65         }
66         default:
67             ;
68     }
69     return false;
70 }
71 
72 #else // No DT_DIR, assume plain POSIX
73 
74 static bool _IsDir(const char *path, dirent *dp)
75 {
76     const int len1 = strlen(path);
77     const int len2 = strlen(dp->d_name);
78 
79     char *pathname = (char*)alloca(len1 + 1 + len2 + 1 + 13);
80     strcpy (pathname, path);
81 
82     /* Avoid UNC-path "//name" on Cygwin.  */
83     if (len1 > 0 && pathname[len1 - 1] != '/')
84         strcat (pathname, "/");
85 
86     strcat (pathname, dp->d_name);
87 
88     struct stat st;
89     if (stat (pathname, &st))
90         return false;
91     return S_ISDIR (st.st_mode);
92 }
93 
94 static bool _IsFile(const char *path, dirent *dp)
95 {
96 	return !_IsDir(path, dp);
97 }
98 #endif // DT_DIR
99 
100 #endif // !_WIN32
101 
102 // returns list of *plain* file names in given directory,
103 // without paths, and without anything else
GetFileList(const char * path,StringList & files)104 bool GetFileList(const char *path, StringList& files)
105 {
106 #if !_WIN32
107     DIR * dirp;
108     struct dirent * dp;
109     dirp = opendir(path);
110     if(!dirp)
111         return false;
112 
113     while((dp=readdir(dirp)) != NULL)
114     {
115         if (_IsFile(path, dp)) // only add if it is not a directory
116         {
117             std::string s(dp->d_name);
118             files.push_back(s);
119         }
120     }
121     closedir(dirp);
122     return true;
123 
124 #else
125 
126     WIN32_FIND_DATA fil;
127     std::string search(path);
128     MakeSlashTerminated(search);
129     search += "*";
130     HANDLE hFil = FindFirstFile(search.c_str(),&fil);
131     if(hFil == INVALID_HANDLE_VALUE)
132         return false;
133 
134     do
135     {
136         if(!(fil.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
137         {
138             std::string s(fil.cFileName);
139             files.push_back(s);
140         }
141     }
142     while(FindNextFile(hFil, &fil));
143 
144     FindClose(hFil);
145     return true;
146 
147 #endif
148 }
149 
150 // returns a list of directory names in the given directory, *without* the source dir.
151 // if getting the dir list recursively, all paths are added, except *again* the top source dir beeing queried.
GetDirList(const char * path,StringList & dirs,int depth)152 bool GetDirList(const char *path, StringList &dirs, int depth /* = 0 */)
153 {
154 #if !_WIN32
155     DIR * dirp;
156     struct dirent * dp;
157     dirp = opendir(path);
158     if(!dirp)
159         return false;
160 
161     std::string pathstr(path);
162     MakeSlashTerminated(pathstr);
163     while((dp = readdir(dirp))) // assignment is intentional
164     {
165         if (_IsDir(path, dp)) // only add if it is a directory
166         {
167             if(strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0)
168             {
169                 dirs.push_back(dp->d_name);
170                 if (depth) // needing a better way to do that
171                 {
172                     std::string d = dp->d_name;
173                     std::string subdir = pathstr + d;
174                     MakeSlashTerminated(d);
175                     StringList newdirs;
176                     GetDirList(subdir.c_str(), newdirs, depth - 1);
177                     for(std::deque<std::string>::iterator it = newdirs.begin(); it != newdirs.end(); ++it)
178                         dirs.push_back(d + *it);
179                 }
180             }
181         }
182     }
183     closedir(dirp);
184     return true;
185 
186 #else
187 
188     std::string pathstr(path);
189     MakeSlashTerminated(pathstr);
190     WIN32_FIND_DATA fil;
191     HANDLE hFil = FindFirstFile((pathstr + '*').c_str(),&fil);
192     if(hFil == INVALID_HANDLE_VALUE)
193         return false;
194 
195     do
196     {
197         if( fil.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
198         {
199             if (!strcmp(fil.cFileName, ".") || !strcmp(fil.cFileName, ".."))
200                 continue;
201 
202             dirs.push_back(fil.cFileName);
203 
204             if (depth) // need a better way to do that
205             {
206                 std::string d = fil.cFileName;
207                 std::string subdir = pathstr + d;
208                 MakeSlashTerminated(d);
209                 StringList newdirs;
210                 GetDirList(subdir.c_str(), newdirs, depth - 1);
211                 for(std::deque<std::string>::iterator it = newdirs.begin(); it != newdirs.end(); ++it)
212                     dirs.push_back(d + *it);
213             }
214         }
215     }
216     while(FindNextFile(hFil, &fil));
217 
218     FindClose(hFil);
219     return true;
220 
221 #endif
222 }
223 
FileExists(const char * fn)224 bool FileExists(const char *fn)
225 {
226 #if _WIN32
227     return _access(fn, 0) == 0;
228 #else
229     return access(fn, F_OK) == 0;
230 #endif
231 }
232 
233 // must return true if creating the directory was successful, or already exists
CreateDir(const char * dir)234 bool CreateDir(const char *dir)
235 {
236     if(IsDirectory(dir)) // do not try to create if it already exists
237         return true;
238     bool result;
239 # if _WIN32
240     result = !!::CreateDirectory(dir, NULL);
241 # else
242     result = !mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
243 #endif
244     return result;
245 }
246 
CreateDirRec(const char * dir)247 bool CreateDirRec(const char *dir)
248 {
249     if(IsDirectory(dir))
250         return true;
251     bool result = true;
252     StringList li;
253     StrSplit(dir, "/\\", li, false);
254     std::string d;
255     d.reserve(strlen(dir) + 1);
256     if(*dir == '/')
257         d += '/';
258     bool last = false;
259     for(StringList::iterator it = li.begin(); it != li.end(); ++it)
260     {
261         d += *it;
262         last = CreateDir(d.c_str());
263         result = last && result;
264         d += '/';
265     }
266     return result || last;
267 }
268 
GetFileSize(const char * fn,vfspos & size)269 bool GetFileSize(const char* fn, vfspos& size)
270 {
271     vfspos sz = 0;
272 #if defined(VFS_LARGEFILE_SUPPORT) && defined(_MSC_VER)
273     struct _stat64 st;
274     if(_stat64(fn, &st))
275         return false;
276     sz = st.st_size;
277 #else
278     struct stat st;
279     if(stat(fn, &st))
280         return false;
281     sz = st.st_size;
282 #endif
283     size = sz;
284     return true;
285 }
286 
FixSlashes(std::string & s)287 void FixSlashes(std::string& s)
288 {
289     char last = 0, cur;
290     size_t wpos = 0;
291     for(size_t i = 0; i < s.length(); ++i)
292     {
293         cur = s[i];
294         if(cur == '\\')
295             cur = '/';
296         if(last == '/' && cur == '/')
297             continue;
298         s[wpos++] = cur;
299         last = cur;
300     }
301     s.resize(wpos);
302 }
303 
FixPath(std::string & s)304 void FixPath(std::string& s)
305 {
306     if(s.empty())
307         return;
308     const char *p = s.c_str();
309     while(p[0] == '.' && (p[1] == '/' || p[1] == '\\'))
310         p += 2;
311     if(!*p)
312     {
313         s.clear();
314         return;
315     }
316     if(s.c_str() != p)
317         s = p;
318     size_t len = s.length();
319     while(len > 1) // remove all trailing slashes unless the first char is a slash -- leave it there for absolute unix paths
320     {
321         char end = s[len - 1];
322         if(end == '/' || end == '\\') // strip trailing '/'
323             --len;
324         else
325             break;
326     }
327     s.resize(len);
328     FixSlashes(s);
329 }
330 
IsDirectory(const char * s)331 bool IsDirectory(const char *s)
332 {
333 #if _WIN32
334     DWORD dwFileAttr = GetFileAttributes(s);
335     if(dwFileAttr == INVALID_FILE_ATTRIBUTES)
336         return false;
337     return !!(dwFileAttr & FILE_ATTRIBUTE_DIRECTORY);
338 #else
339     if ( access( s, 0 ) == 0 )
340     {
341         struct stat status;
342         stat( s, &status );
343         return status.st_mode & S_IFDIR; // FIXME: what about symlinks here?
344     }
345     return false;
346 #endif
347 }
348 
MakeSlashTerminated(std::string & s)349 void MakeSlashTerminated(std::string& s)
350 {
351     if(s.length() && s[s.length() - 1] != '/')
352         s += '/';
353 }
354 
355 // extracts the file name (part after the last /) from a given path
356 // returns the string "/" as-is.
GetBaseNameFromPath(const char * str)357 const char *GetBaseNameFromPath(const char *str)
358 {
359     if(str[0] == '/' && !str[1])
360         return str;
361     const char *p = strrchr(str, '/');
362     return p ? p+1 : str;
363 }
364 
StripFileExtension(std::string & s)365 void StripFileExtension(std::string& s)
366 {
367     size_t pos = s.find_last_of('.');
368     size_t pos2 = s.find_last_of('/');
369     if(pos != std::string::npos && (pos2 == std::string::npos || pos2 < pos))
370         s.resize(pos+1);
371 }
372 
StripLastPath(std::string & s)373 void StripLastPath(std::string& s)
374 {
375     size_t len = s.length();
376     while(len)
377     {
378         char end = s[len - 1];
379         if(end == '/' || end == '\\') // strip trailing '/'
380             --len;
381         else
382             break;
383     }
384     s.resize(len);
385 
386     if(s.empty())
387         return;
388 
389     size_t pos = s.find_last_of('/');
390     if(pos == std::string::npos)
391     {
392         s.clear();
393         return; // nothing remains
394     }
395 
396     s.resize(pos+1);
397 }
398 
399 
400 // from http://board.byuu.org/viewtopic.php?f=10&t=1089&start=15
WildcardMatch(const char * str,const char * pattern)401 bool WildcardMatch(const char *str, const char *pattern)
402 {
403     const char *cp = 0, *mp = 0;
404     while(*str && *pattern != '*')
405     {
406         if(*pattern != *str && *pattern != '?')
407             return false;
408         ++pattern;
409         ++str;
410     }
411 
412     while(*str)
413     {
414         if(*pattern == '*')
415         {
416             if(!*++pattern)
417                 return true;
418             mp = pattern;
419             cp = str + 1;
420         }
421         else if(*pattern == *str || *pattern == '?')
422         {
423             ++pattern;
424             ++str;
425         }
426         else
427         {
428             pattern = mp;
429             str = cp++;
430         }
431     }
432 
433     while(*pattern == '*')
434         ++pattern;
435 
436     return !*pattern;
437 }
438 
439 // copy strings, mangling newlines to system standard
440 // windows has 13+10
441 // *nix has 10
442 // exotic systems may have 10+13
strnNLcpy(char * dst,const char * src,unsigned int n)443 size_t strnNLcpy(char *dst, const char *src, unsigned int n /* = -1 */)
444 {
445     char *olddst = dst;
446     bool had10 = false, had13 = false;
447 
448     --n; // reserve 1 for \0 at end
449 
450     while(*src && n)
451     {
452         if((had13 && *src == 10) || (had10 && *src == 13))
453         {
454             ++src; // last was already mangled
455             had13 = had10 = false; // processed one CRLF pair
456             continue;
457         }
458         had10 = *src == 10;
459         had13 = *src == 13;
460 
461         if(had10 || had13)
462         {
463             *dst++ = '\n';
464             ++src;
465         }
466         else
467             *dst++ = *src++;
468 
469         --n;
470     }
471 
472     *dst++ = 0;
473 
474     return dst - olddst;
475 }
476 
477 
478 
479 VFS_NAMESPACE_END
480