1 /* bzflag
2  * Copyright (c) 1993-2021 Tim Riker
3  *
4  * This package is free software;  you can redistribute it and/or
5  * modify it under the terms of the license found in the file
6  * named COPYING that should have accompanied this file.
7  *
8  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11  */
12 
13 // interface header
14 #include "OSFile.h"
15 
16 // system headers
17 #include <vector>
18 #include <string> //std::string
19 #include <algorithm>
20 #include <string.h> //c-style string
21 #include <stdio.h>
22 #ifndef _MSC_VER
23 #include <stddef.h>
24 #endif
25 
26 // local implementation headers
27 #include "common.h"
28 #include "TextUtils.h"
29 
30 typedef std::vector<std::string> fileNameList;
31 
32 // These are the core file name conversions
33 // std pathnames are unix style
34 // If you are adding a platform
35 // this is one place you need to add conversions to/from your OS
36 
37 // utility functions
OSFileStdToOSDir(std::string & dir)38 void OSFileStdToOSDir(std::string &dir)
39 {
40     if (dir=="") return;
41 #ifndef _WIN32
42     return;
43 #else
44     std::replace(dir.begin(), dir.end(), '/', '\\');
45 #endif//WIN32
46 }
47 
OSFileOSToStdDir(std::string & dir)48 void OSFileOSToStdDir(std::string &dir)
49 {
50 //#ifndef _WIN32
51 //  return;
52 //#else
53     std::replace(dir.begin(), dir.end(), '\\', '/');
54 //#endif//WIN32
55 }
56 
57 // Windows specific make path
58 #ifdef _WIN32
windowsMakePath(const std::string & path)59 void windowsMakePath(const std::string &path)
60 {
61     std::string::size_type pos = 0;
62     std::string::size_type delimitpos;
63 
64     do
65     {
66         delimitpos = path.find('\\', pos);
67         std::string nextdir = path.substr(pos, delimitpos);
68         mkdir(nextdir.c_str());
69         pos = delimitpos + 1;
70     }
71     while (delimitpos != std::string::npos);
72 }
73 #endif // windows
74 
75 
76 /// global make path
osMakePath(const std::string & path)77 void osMakePath(const std::string &path)
78 {
79     if (path=="") return;
80 #ifdef _WIN32
81     windowsMakePath(path);
82 #endif
83 }
84 
85 
86 // global path stuff
87 std::string  stdBaseDir;
88 std::string  osBaseDir;
89 
setOSFileBaseDir(const std::string & dir)90 void setOSFileBaseDir(const std::string &dir)
91 {
92     stdBaseDir = dir;
93     osBaseDir = dir;
94     OSFileStdToOSDir(osBaseDir);
95 }
96 
97 // OSFileClass
98 class OSFile::OSFileInfo
99 {
100 public:
101     OSFileInfo();
102     OSFileInfo(const OSFile::OSFileInfo &r);
103     std::string    stdName;
104     std::string    osName;
105     std::string    title;
106     std::string    osPath;
107     bool      useGlobalPath;
108     FILE      *fp;
109 };
110 
OSFileInfo()111 OSFile::OSFileInfo::OSFileInfo()
112 {
113     fp = NULL;
114     useGlobalPath = true;
115 }
116 
OSFileInfo(const OSFile::OSFileInfo & r)117 OSFile::OSFileInfo::OSFileInfo(const OSFile::OSFileInfo &r)
118 {
119     // never copy the file
120     fp = NULL;
121     osName = r.osName;
122     stdName = r.stdName;
123     title =  r.title;
124     useGlobalPath = r.useGlobalPath;
125 }
126 
setUseGlobalPath(bool use)127 void OSFile::setUseGlobalPath(bool use)
128 {
129     info->useGlobalPath = use;
130 }
131 
OSFile()132 OSFile::OSFile()
133 {
134     info = new OSFileInfo;
135 }
136 
OSFile(const std::string & name)137 OSFile::OSFile(const std::string &name)
138 {
139     info = new OSFileInfo;
140     stdName(name);
141 }
142 
OSFile(const char * name)143 OSFile::OSFile(const char* name)
144 {
145     info = new OSFileInfo;
146     stdName(std::string(name));
147 }
148 
OSFile(const OSFile & r)149 OSFile::OSFile(const OSFile &r)
150 {
151     info = new OSFileInfo(*r.info);
152 }
153 
operator =(const OSFile & r)154 OSFile& OSFile::operator = (const OSFile &r)
155 {
156     if (this == &r)
157         return *this;
158 
159     if (info)
160         delete (info);
161 
162     info = new OSFileInfo(*r.info);
163     return *this;
164 }
165 
OSFile(const std::string & name,const char * mode)166 OSFile::OSFile(const std::string &name, const char *mode)
167 {
168     info = new OSFileInfo;
169     stdName(name);
170     open(name.c_str(), mode);
171 }
172 
~OSFile()173 OSFile::~OSFile()
174 {
175     close();
176     delete (info);
177 }
178 
open(const std::string & name,const char * mode)179 bool OSFile::open(const std::string &name, const char *mode)
180 {
181     close();
182     stdName(name);
183     return open(mode);
184 }
185 
open(const char * mode)186 bool OSFile::open(const char *mode)
187 {
188     close();
189 
190     char  modeToUse[32];
191 
192     if (!mode)
193         sprintf(modeToUse, "rb");
194     else
195     {
196         strncpy(modeToUse, mode, sizeof(modeToUse) - 1);
197         modeToUse[31] = '\0';
198     }
199 
200     std::string fileName;
201 
202     if (info->useGlobalPath)
203         fileName = osBaseDir;
204     fileName += info->osName;
205 
206     info->fp = fopen(fileName.c_str(),modeToUse);
207 
208     // we may need to make the file path to the file, if we are writing then lets get on with it.
209     if (!info->fp && strchr(modeToUse,'w'))
210     {
211         osMakePath(fileName);
212         info->fp = fopen(fileName.c_str(),modeToUse);
213     }
214 
215     return isOpen();
216 }
217 
close()218 bool OSFile::close()
219 {
220     if (info->fp)
221         fclose(info->fp);
222 
223     info->fp = NULL;
224 
225     return (!isOpen());
226 }
227 
read(void * data,int _size,int count)228 int OSFile::read(void* data, int _size, int count)
229 {
230     if (!isOpen())
231         return 0;
232 
233     return (int)fread(data, _size, count, info->fp);
234 }
235 
readChar()236 unsigned char OSFile::readChar()
237 {
238     if (!isOpen())
239         return 0;
240 
241     int c = getc(info->fp);
242     if (c!=EOF)
243         return c;
244     else
245         return 0;
246 }
247 
readLine()248 std::string OSFile::readLine()
249 {
250     std::string line;
251     // reserve initial space for about a typical line length to speed up things
252     line.reserve(20);
253 
254     char c = readChar();
255     while (c != 0 && c != '\n' && c != 10)
256     {
257         line += c;
258         c = readChar();
259     }
260     return line;
261 }
262 
scanChar(unsigned char * pChar)263 int OSFile::scanChar(unsigned char *pChar)
264 {
265     if (!pChar || !isOpen())
266         return 0;
267 
268     return fscanf(info->fp,"%c",pChar);
269 }
270 
scanStr()271 const char* OSFile::scanStr()
272 {
273     if (!isOpen())
274         return 0;
275 
276     static char temp[1024] = {0};
277     if (fscanf(info->fp,"%s",temp)!=1)
278         return NULL;
279     return temp;
280 }
281 
write(const void * data,int _size)282 int OSFile::write(const void* data, int _size)
283 {
284     if (!isOpen())
285         return 0;
286 
287     return (int)fwrite(data, _size, 1, info->fp);
288 }
289 
flush()290 void OSFile::flush()
291 {
292     fflush(info->fp);
293 }
294 
295 
seek(teFilePos ePos,int iOffset)296 int OSFile::seek(teFilePos ePos, int iOffset)
297 {
298     if (!isOpen())
299         return 0;
300 
301     long iMode;
302     switch (ePos)
303     {
304     case eFileStart:
305         iMode = SEEK_SET;
306         break;
307 
308     case eFileEnd:
309         iMode = SEEK_END ;
310         break;
311 
312     case eCurentPos:
313     default:
314         iMode = SEEK_CUR ;
315         break;
316     }
317 
318     return fseek(info->fp,iOffset,iMode);
319 }
320 
size()321 unsigned int OSFile::size()
322 {
323     if (!isOpen())
324         return 0;
325 
326     unsigned int pos = ftell(info->fp);
327     fseek(info->fp,0,SEEK_END);
328     unsigned int len = ftell(info->fp);
329     fseek(info->fp,pos, SEEK_SET);
330 
331     return len;
332 }
333 
tell()334 unsigned int OSFile::tell()
335 {
336     if (!isOpen())
337         return 0;
338     return ftell(info->fp);
339 }
340 
341 
stdName(const std::string & name)342 void OSFile::stdName(const std::string &name)
343 {
344     info->stdName = name;
345     info->osName = name;
346     OSFileStdToOSDir(info->osName);
347 }
348 
osName(const std::string & name)349 void OSFile::osName(const std::string &name)
350 {
351     info->stdName = name;
352     info->osName = name;
353     OSFileOSToStdDir(info->stdName);
354 }
355 
getFileName()356 std::string OSFile::getFileName()
357 {
358     std::string::size_type lastslash = info->stdName.rfind('/');
359     info->title = lastslash==std::string::npos ?
360                   info->stdName : info->stdName.substr(lastslash+1);
361 
362     // strip extension
363     info->title.erase(info->title.rfind('.'));
364 
365     return info->title;
366 }
367 
368 // this CAN return npos, cus the file may not have an extenstion, if it just happens to end in a '.' then well, your really weird Mr. File.
getExtension()369 std::string OSFile::getExtension()
370 {
371     std::string::size_type dot = info->stdName.rfind('.');
372     return dot==std::string::npos ? "" : info->stdName.substr(dot+1);
373 }
374 
getOSFileDir()375 std::string OSFile::getOSFileDir()
376 {
377     std::string::size_type lastslash = info->stdName.rfind('/');
378     info->osPath = lastslash==std::string::npos ?
379                    info->stdName : info->stdName.substr(0, lastslash);
380     OSFileStdToOSDir(info->osPath);
381 
382     return info->osPath;
383 }
384 
getFullOSPath()385 std::string OSFile::getFullOSPath()
386 {
387     std::string path = osBaseDir;
388     path += info->osName;
389     return path;
390 }
391 
getFile()392 FILE* OSFile::getFile()
393 {
394     return info->fp;
395 }
396 
getStdName()397 std::string OSFile::getStdName()
398 {
399     return info->stdName;
400 }
401 
getOSName()402 std::string OSFile::getOSName()
403 {
404     return info->osName;
405 }
406 
isOpen()407 bool OSFile::isOpen()
408 {
409     return info->fp != NULL;
410 }
411 
412 // OS Dir classes
413 class OSDir::OSDirInfo
414 {
415 public:
416     OSDirInfo();
417     OSDirInfo(const OSDir::OSDirInfo &r);
418 
419     std::string    baseStdDir;
420     std::string    baseOSDir;
421     fileNameList    nameList;
422     int      namePos;
423 };
424 
OSDirInfo()425 OSDir::OSDirInfo::OSDirInfo()
426 {
427     namePos = -1;
428 }
429 
OSDirInfo(const OSDir::OSDirInfo & r)430 OSDir::OSDirInfo::OSDirInfo(const OSDir::OSDirInfo &r)
431 {
432     baseStdDir = r.baseStdDir;
433     baseOSDir = r.baseOSDir;
434     nameList = r.nameList;
435     namePos = r.namePos;
436 }
437 
OSDir()438 OSDir::OSDir()
439 {
440     info = new OSDirInfo;
441 }
442 
OSDir(const OSDir & r)443 OSDir::OSDir(const OSDir& r)
444 {
445     info = new OSDirInfo(*r.info);
446 }
447 
operator =(const OSDir & r)448 OSDir& OSDir::operator = (const OSDir& r)
449 {
450     if (this == &r)
451         return *this;
452 
453     delete info;
454     info = new OSDirInfo(*r.info);
455 
456     return *this;
457 }
458 
OSDir(const std::string & DirName)459 OSDir::OSDir(const std::string &DirName)
460 {
461     info = new OSDirInfo;
462     setStdDir(DirName);
463 }
464 
~OSDir()465 OSDir::~OSDir()
466 {
467     if (info)
468     {
469         info->nameList.clear();
470         delete(info);
471     }
472 }
473 
setStdDir(const std::string & DirName)474 void OSDir::setStdDir(const std::string &DirName)
475 {
476     info->baseStdDir = DirName;
477     info->baseOSDir = DirName;
478     OSFileStdToOSDir(info->baseOSDir);
479 }
480 
setOSDir(const std::string & DirName)481 void OSDir::setOSDir(const std::string &DirName)
482 {
483     info->baseStdDir = DirName;
484     info->baseOSDir = DirName;
485     OSFileOSToStdDir(info->baseStdDir);
486 }
487 
makeStdDir(const std::string & DirName)488 void OSDir::makeStdDir(const std::string &DirName)
489 {
490     setStdDir(DirName);
491 #ifdef _WIN32
492     mkdir(info->baseOSDir.c_str());
493 #else
494     mkdir(info->baseOSDir.c_str(), 0777);
495 #endif
496 }
497 
makeOSDir(const std::string & DirName)498 void OSDir::makeOSDir(const std::string &DirName)
499 {
500     setOSDir(DirName);
501 #ifdef _WIN32
502     mkdir(info->baseOSDir.c_str());
503 #else
504     mkdir(info->baseOSDir.c_str(), 0777);
505 #endif
506 }
507 
getStdName()508 std::string OSDir::getStdName()
509 {
510     return info->baseStdDir;
511 }
512 
getOSName()513 std::string  OSDir::getOSName()
514 {
515     return info->baseOSDir.c_str();
516 }
517 
getFullOSPath()518 std::string OSDir::getFullOSPath()
519 {
520     std::string  FilePath = osBaseDir;
521     FilePath += info->baseOSDir;
522     return FilePath;
523 }
524 
getNextFile(OSFile & oFile,bool bRecursive)525 bool OSDir::getNextFile(OSFile &oFile, bool bRecursive)
526 {
527     return getNextFile(oFile, NULL, bRecursive);
528 }
529 
getFileScanCount()530 int OSDir::getFileScanCount()
531 {
532     if (info)
533         return (int)info->nameList.size();
534     else
535         return -1;
536 }
537 
getNextFile(OSFile & oFile,const char * fileMask,bool bRecursive)538 bool OSDir::getNextFile(OSFile &oFile, const char* fileMask, bool bRecursive)
539 {
540 #ifdef _WIN32
541     std::string realMask = "*.*";  //FIXME -- could this also be just '*' ?
542 #else
543     std::string realMask = "*";
544 #endif
545     if (fileMask)
546         realMask = fileMask;
547 
548     realMask = TextUtils::toupper(realMask);
549 
550     if (info->namePos == -1)
551     {
552         info->nameList.clear();
553         //FIXME -- just do the #ifdef'ing here?
554         windowsAddFileStack(getFullOSPath(), realMask, bRecursive);
555         linuxAddFileStack(getFullOSPath(), realMask, bRecursive);
556 
557         info->namePos = 0;
558     }
559 
560     int size = info->nameList.size();
561     if (info->namePos >= size)
562     {
563         info->namePos = -1;
564         return false;
565     }
566 
567     std::string fileName = info->nameList[info->namePos];
568 
569     if (osBaseDir.size()>1)
570     {
571         std::string temp = &(fileName.c_str()[osBaseDir.size()]);
572         fileName = temp;
573     }
574 
575     oFile.osName(fileName);
576     info->namePos++;
577 
578     return true;
579 }
580 
windowsAddFileStack(std::string pathName,std::string fileMask,bool bRecursive)581 bool OSDir::windowsAddFileStack(std::string pathName, std::string fileMask, bool bRecursive)
582 {
583 #ifdef _WIN32
584     struct _finddata_t fileInfo;
585 
586     long    hFile;
587     std::string searchstr;
588 
589     std::string FilePath;
590 
591     bool  bDone = false;
592 
593     searchstr = pathName;
594     searchstr += "\\";
595     if (fileMask.size() > 0)
596         searchstr += fileMask;
597     else
598         searchstr += "*.*";
599 
600     hFile = (long)_findfirst(searchstr.c_str(), &fileInfo);
601 
602     if (hFile != -1)
603     {
604         while (!bDone)
605         {
606             if ((strlen(fileInfo.name) >0) && (strcmp(fileInfo.name,".") != 0) &&
607                     (strcmp(fileInfo.name,"..") != 0))
608             {
609                 FilePath = pathName;
610                 FilePath += "\\";
611                 FilePath += fileInfo.name;
612 
613                 if ((fileInfo.attrib & _A_SUBDIR) && bRecursive)
614                     windowsAddFileStack(FilePath,fileMask,bRecursive);
615                 else if (!(fileInfo.attrib & _A_SUBDIR))
616                     info->nameList.push_back(FilePath);
617             }
618             if (_findnext(hFile,&fileInfo) == -1)
619                 bDone = true;
620         }
621     }
622     return true;
623 #else
624     // quell warnings
625     if (!bRecursive)
626     {
627         fileMask.size();
628         pathName.size();
629     }
630     return false;
631 #endif
632 }
633 
634 // linux mask filter functions
635 // we don't need these for windows as it can do it right in findNextFile
636 #ifndef _WIN32
match_multi(const char ** mask,const char ** string)637 static int match_multi(const char **mask, const char **string)
638 {
639     const char *msk;
640     const char *str;
641     const char *msktop;
642     const char *strtop;
643 
644     msk = *mask;
645     str = *string;
646 
647     while ((*msk != '\0') && (*msk == '*'))
648         msk++;    /* get rid of multiple '*'s */
649 
650     if (*msk == '\0') /* '*' was last, auto-match */
651         return +1;
652 
653     msktop = msk;
654     strtop = str;
655 
656     while (*msk != '\0')
657     {
658         if (*msk == '*')
659         {
660             *mask = msk;
661             *string = str;
662             return 0;     /* matched this segment */
663         }
664         else if (*str == '\0')
665             return -1;    /* can't match */
666         else
667         {
668             if ((*msk == '?') || (*msk == *str))
669             {
670                 msk++;
671                 str++;
672                 if ((*msk == '\0') && (*str != '\0'))  /* advanced check */
673                 {
674                     str++;
675                     strtop++;
676                     str = strtop;
677                     msk = msktop;
678                 }
679             }
680             else
681             {
682                 str++;
683                 strtop++;
684                 str = strtop;
685                 msk = msktop;
686             }
687         }
688     }
689 
690     *mask = msk;
691     *string = str;
692     return +1;               /* full match */
693 }
694 
match_mask(const char * mask,const char * string)695 static int match_mask (const char *mask, const char *string)
696 {
697     if (mask == NULL)
698         return 0;
699 
700     if (string == NULL)
701         return 0;
702 
703     if ((mask[0] == '*') && (mask[1] == '\0'))
704         return 1;         /* instant match */
705 
706     while (*mask != '\0')
707     {
708         if (*mask == '*')
709         {
710             mask++;
711             switch (match_multi (&mask, &string))
712             {
713             case +1:
714                 return 1;
715             case -1:
716                 return 0;
717             }
718         }
719         else if (*string == '\0')
720             return 0;
721         else if ((*mask == '?') || (*mask == *string))
722         {
723             mask++;
724             string++;
725         }
726         else
727             return 0;
728     }
729 
730     if (*string == '\0')
731         return 1;
732     else
733         return 0;
734 }
735 #endif
736 
linuxAddFileStack(std::string pathName,std::string fileMask,bool bRecursive)737 bool OSDir::linuxAddFileStack(std::string pathName, std::string fileMask, bool bRecursive)
738 {
739 #ifdef _WIN32
740     // quell warnings
741     if (!bRecursive)
742     {
743         fileMask.size();
744         pathName.size();
745     }
746     return false;
747 #else
748     DIR  *directory;
749     dirent  *fileInfo;
750     struct stat  statbuf;
751     char   searchstr[1024];
752     std::string  FilePath;
753 
754     strncpy(searchstr, pathName.c_str(), sizeof(searchstr) - 1);
755     searchstr[1023] = '\0';
756     size_t pathLen = strlen(searchstr);
757     if ((pathLen < 1022) && searchstr[pathLen - 1] != '/')
758         searchstr[pathLen] = '/';
759     directory = opendir(searchstr);
760     if (!directory)
761         return false;
762 
763     // TODO: make it use the filemask
764     while ((fileInfo = readdir(directory)))
765     {
766         if (!((strcmp(fileInfo->d_name, ".") == 0) || (strcmp(fileInfo->d_name, "..") == 0)))
767         {
768             FilePath = searchstr;
769             FilePath += fileInfo->d_name;
770             strcpy(fileInfo->d_name, TextUtils::toupper(fileInfo->d_name).c_str());
771 
772             stat(FilePath.c_str(), &statbuf);
773 
774             if (S_ISDIR(statbuf.st_mode) && bRecursive)
775                 linuxAddFileStack(FilePath,fileMask,bRecursive);
776             else if (match_mask(fileMask.c_str(), fileInfo->d_name))
777                 info->nameList.push_back(FilePath);
778         }
779     }
780     closedir(directory);
781     return true;
782 #endif// !Win32
783 }
784 
785 // Local Variables: ***
786 // mode: C++ ***
787 // tab-width: 4 ***
788 // c-basic-offset: 4 ***
789 // indent-tabs-mode: nil ***
790 // End: ***
791 // ex: shiftwidth=4 tabstop=4
792