DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/dos/drive_cache.cpp
00001 /*
00002  *  Copyright (C) 2002-2020  The DOSBox Team
00003  *
00004  *  This program is free software; you can redistribute it and/or modify
00005  *  it under the terms of the GNU General Public License as published by
00006  *  the Free Software Foundation; either version 2 of the License, or
00007  *  (at your option) any later version.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License along
00015  *  with this program; if not, write to the Free Software Foundation, Inc.,
00016  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00017  */
00018 
00019 
00020 #include "drives.h"
00021 #include "dos_inc.h"
00022 #include "support.h"
00023 #include "cross.h"
00024 
00025 // STL stuff
00026 #include <vector>
00027 #include <iterator>
00028 #include <algorithm>
00029 
00030 #if defined (WIN32)   /* Win 32 */
00031 #define WIN32_LEAN_AND_MEAN        // Exclude rarely-used stuff from 
00032 #include <windows.h>
00033 #endif
00034 
00035 #if defined (OS2)
00036 #define INCL_DOSERRORS
00037 #define INCL_DOSFILEMGR
00038 #include <os2.h>
00039 #endif
00040 
00041 int fileInfoCounter = 0;
00042 
00043 bool SortByName(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) {
00044     return strcmp(a->shortname,b->shortname)<0;
00045 }
00046 
00047 bool SortByNameRev(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) {
00048     return strcmp(a->shortname,b->shortname)>0;
00049 }
00050 
00051 bool SortByDirName(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) {
00052     // Directories first...
00053     if (a->isDir!=b->isDir) return (a->isDir>b->isDir); 
00054     return strcmp(a->shortname,b->shortname)<0;
00055 }
00056 
00057 bool SortByDirNameRev(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) {
00058     // Directories first...
00059     if (a->isDir!=b->isDir) return (a->isDir>b->isDir); 
00060     return strcmp(a->shortname,b->shortname)>0;
00061 }
00062 
00063 DOS_Drive_Cache::DOS_Drive_Cache(void) {
00064     dirBase         = new CFileInfo;
00065     save_dir        = 0;
00066     srchNr          = 0;
00067     label[0]        = 0;
00068     nextFreeFindFirst   = 0;
00069     for (Bit32u i=0; i<MAX_OPENDIRS; i++) { dirSearch[i] = 0; dirFindFirst[i] = 0; }
00070     SetDirSort(DIRALPHABETICAL);
00071     updatelabel = true;
00072 }
00073 
00074 DOS_Drive_Cache::DOS_Drive_Cache(const char* path, DOS_Drive *drive) {
00075     dirBase         = new CFileInfo;
00076     save_dir        = 0;
00077     srchNr          = 0;
00078     label[0]        = 0;
00079     nextFreeFindFirst   = 0;
00080     for (Bit32u i=0; i<MAX_OPENDIRS; i++) { dirSearch[i] = 0; dirFindFirst[i] = 0; }
00081     SetDirSort(DIRALPHABETICAL);
00082     SetBaseDir(path,drive);
00083     updatelabel = true;
00084 }
00085 
00086 DOS_Drive_Cache::~DOS_Drive_Cache(void) {
00087     Clear();
00088     for (Bit32u i=0; i<MAX_OPENDIRS; i++) { DeleteFileInfo(dirFindFirst[i]); dirFindFirst[i]=0; }
00089 }
00090 
00091 void DOS_Drive_Cache::Clear(void) {
00092     DeleteFileInfo(dirBase); dirBase = 0;
00093     nextFreeFindFirst   = 0;
00094     for (Bit32u i=0; i<MAX_OPENDIRS; i++) dirSearch[i] = 0;
00095 }
00096 
00097 void DOS_Drive_Cache::EmptyCache(void) {
00098     // Empty Cache and reinit
00099     Clear();
00100     dirBase     = new CFileInfo;
00101     save_dir    = 0;
00102     srchNr      = 0;
00103     SetBaseDir(basePath,drive);
00104 }
00105 
00106 void DOS_Drive_Cache::SetLabel(const char* vname,bool cdrom,bool allowupdate) {
00107 /* allowupdate defaults to true. if mount sets a label then allowupdate is 
00108  * false and will this function return at once after the first call.
00109  * The label will be set at the first call. */
00110 
00111     if(!this->updatelabel) return;
00112     this->updatelabel = allowupdate;
00113     Set_Label(vname,label,cdrom);
00114     LOG(LOG_DOSMISC,LOG_NORMAL)("DIRCACHE: Set volume label to %s",label);
00115 }
00116 
00117 Bit16u DOS_Drive_Cache::GetFreeID(CFileInfo* dir) {
00118     if (dir->id != MAX_OPENDIRS)
00119         return dir->id;
00120     for (Bit16u i=0; i<MAX_OPENDIRS; i++) {
00121         if (!dirSearch[i]) {
00122             dir->id = i;
00123             return i;
00124         }
00125     }
00126     LOG(LOG_FILES,LOG_NORMAL)("DIRCACHE: Too many open directories!");
00127     dir->id=0;
00128     return 0;
00129 }
00130 
00131 void DOS_Drive_Cache::SetBaseDir(const char* baseDir, DOS_Drive *drive) {
00132     Bit16u id;
00133     strcpy(basePath,baseDir);
00134     this->drive = drive;
00135     if (OpenDir(baseDir,id)) {
00136         char* result = 0, *lresult = 0;
00137         ReadDir(id,result,lresult);
00138     }
00139     // Get Volume Label
00140 #if defined (WIN32) || defined (OS2)
00141     char labellocal[256]={ 0 };
00142     char drives[4] = "C:\\";
00143     drives[0] = basePath[0];
00144 #if defined (WIN32)
00145     if (GetVolumeInformation(drives,labellocal,256,NULL,NULL,NULL,NULL,0)) {
00146     bool cdrom = false;
00147     UINT test = GetDriveType(drives);
00148     if(test == DRIVE_CDROM) cdrom = true;
00149 #else // OS2
00150     //TODO determine wether cdrom or not!
00151     FSINFO fsinfo;
00152     ULONG drivenumber = drive[0];
00153     if (drivenumber > 26) { // drive letter was lowercase
00154         drivenumber = drive[0] - 'a' + 1;
00155     }
00156     APIRET rc = DosQueryFSInfo(drivenumber, FSIL_VOLSER, &fsinfo, sizeof(FSINFO));
00157     if (rc == NO_ERROR) {
00158         bool cdrom = false;
00159 #endif
00160         /* Set label and allow being updated */
00161         SetLabel(labellocal,cdrom,true);
00162     }
00163 #endif
00164 }
00165 
00166 void DOS_Drive_Cache::ExpandName(char* path) {
00167     strcpy(path,GetExpandName(path));
00168 }
00169 
00170 char* DOS_Drive_Cache::GetExpandName(const char* path) {
00171     static char work [CROSS_LEN] = { 0 };
00172     char dir [CROSS_LEN]; 
00173 
00174     work[0] = 0;
00175     strcpy (dir,path);
00176 
00177     const char* pos = strrchr(path,CROSS_FILESPLIT);
00178 
00179     if (pos) dir[pos-path+1] = 0;
00180     CFileInfo* dirInfo = FindDirInfo(dir, work);
00181         
00182     if (pos) {
00183         // Last Entry = File
00184         strcpy(dir,pos+1); 
00185         GetLongName(dirInfo, dir);
00186         strcat(work,dir);
00187     }
00188 
00189     if (*work) {
00190         size_t len = strlen(work);
00191 #if defined (WIN32)
00192 //What about OS/2
00193         if((work[len-1] == CROSS_FILESPLIT ) && (len >= 2) && (work[len-2] != ':')) {
00194 #else
00195         if((len > 1) && (work[len-1] == CROSS_FILESPLIT )) {
00196 #endif       
00197             work[len-1] = 0; // Remove trailing slashes except when in root
00198         }
00199     }
00200     return work;
00201 }
00202 
00203 void DOS_Drive_Cache::AddEntry(const char* path, bool checkExists) {
00204     // Get Last part...
00205     char expand [CROSS_LEN];
00206 
00207     CFileInfo* dir = FindDirInfo(path,expand);
00208     const char* pos = strrchr(path,CROSS_FILESPLIT);
00209 
00210     if (pos) {
00211         char file   [CROSS_LEN];
00212         strcpy(file,pos+1); 
00213         // Check if file already exists, then don't add new entry...
00214         if (checkExists) {
00215             if (GetLongName(dir,file)>=0) return;
00216         }
00217 
00218         char sfile[DOS_NAMELENGTH];
00219         sfile[0]=0;
00220         CreateEntry(dir,file,sfile,false);
00221 
00222         Bits index = GetLongName(dir,file);
00223         if (index>=0) {
00224             // Check if there are any open search dir that are affected by this...
00225             if (dir) for (Bit32u i=0; i<MAX_OPENDIRS; i++) {
00226                 if ((dirSearch[i]==dir) && ((Bit32u)index<=dirSearch[i]->nextEntry)) 
00227                     dirSearch[i]->nextEntry++;
00228             }
00229         }
00230         //      LOG_DEBUG("DIR: Added Entry %s",path);
00231     } else {
00232 //      LOG_DEBUG("DIR: Error: Failed to add %s",path); 
00233     }
00234 }
00235 
00236 bool filename_not_strict_8x3(const char *n);
00237 void DOS_Drive_Cache::AddEntryDirOverlay(const char* path, char *sfile, bool checkExists) {
00238   // Get Last part...
00239   char file   [CROSS_LEN];
00240   char expand [CROSS_LEN];
00241   char dironly[CROSS_LEN + 1];
00242 
00243   //When adding a directory, the directory we want to operate inside in is the above it. (which can go wrong if the directory already exists.)
00244   strcpy(dironly,path);
00245   char* post = strrchr(dironly,CROSS_FILESPLIT);
00246 
00247   if (post) {
00248 #if defined (WIN32) 
00249       //OS2 ?
00250       if (post > dironly && *(post - 1) == ':' && (post - dironly) == 2) 
00251           post++; //move away from X: as need to end up with "x:\"
00252 #else
00253   //Lets hope this is not really used.. (root folder specified as overlay)
00254       if (post == dironly)
00255           post++; //move away from / 
00256 #endif
00257       *post = 0; //TODO take care of AddEntryDIR D:\\piet) (so mount d d:\ as base)
00258       *(post + 1) = 0; //As FindDirInfo is skipping over the base directory
00259   }
00260   CFileInfo* dir = FindDirInfo(dironly,expand);
00261   const char* pos = strrchr(path,CROSS_FILESPLIT);
00262 
00263   char sname[CROSS_LEN], *p=strrchr(sfile, '\\');
00264   if (p!=NULL)
00265         strcpy(sname, p+1);
00266   else
00267         strcpy(sname, sfile);
00268   if (pos) {
00269       strcpy(file,pos + 1);   
00270       // Check if directory already exists, then don't add new entry...
00271       if (checkExists) {
00272           Bits index = GetLongName(dir,(char *)(!strlen(sname)||filename_not_strict_8x3(sname)?file:sname));
00273           if (index >= 0) {
00274               //directory already exists, but most likely empty. 
00275               dir = dir->fileList[index];
00276               if (dir->isOverlayDir && dir->fileList.empty()) {
00277                   //maybe care about searches ? but this function should only run on cache inits/refreshes.
00278                   //add dot entries
00279                   CreateEntry(dir,".",".",true);
00280                   CreateEntry(dir,"..","..",true);
00281               }
00282               return;
00283           }
00284       }
00285 
00286       if (filename_not_strict_8x3(sname)) sname[0]=0;
00287       char* genname=CreateEntry(dir,file,sname,true);
00288       Bits index = GetLongName(dir,(char *)(!strlen(sname)||filename_not_strict_8x3(sname)?file:sname));
00289           if (strlen(genname)) {
00290                   strcpy(sfile, sname);
00291                   p=strrchr(sfile, '\\');
00292                   if (p!=NULL) {
00293                           *(p+1)=0;
00294                           strcat(sfile, genname);
00295                   } else
00296                           strcpy(sfile, genname);
00297           }
00298       if (index>=0) {
00299           Bit32u i;
00300           // Check if there are any open search dir that are affected by this...
00301           if (dir) for (i=0; i<MAX_OPENDIRS; i++) {
00302               if ((dirSearch[i]==dir) && ((Bit32u)index<=dirSearch[i]->nextEntry)) 
00303                   dirSearch[i]->nextEntry++;
00304           }
00305 
00306           dir = dir->fileList[index];
00307           dir->isOverlayDir = true;
00308           CreateEntry(dir,".",".",true);
00309           CreateEntry(dir,"..","..",true);
00310       }
00311       //      LOG_DEBUG("DIR: Added Entry %s",path);
00312   } else {
00313       //      LOG_DEBUG("DIR: Error: Failed to add %s",path); 
00314     }
00315 }
00316 
00317 void DOS_Drive_Cache::DeleteEntry(const char* path, bool ignoreLastDir) {
00318     CacheOut(path,ignoreLastDir);
00319     if (dirSearch[srchNr] && (dirSearch[srchNr]->nextEntry>0)) dirSearch[srchNr]->nextEntry--;
00320 
00321     if (!ignoreLastDir) {
00322         // Check if there are any open search dir that are affected by this...
00323         char expand [CROSS_LEN];
00324         CFileInfo* dir = FindDirInfo(path,expand);
00325         if (dir) for (Bit32u i=0; i<MAX_OPENDIRS; i++) {
00326             if ((dirSearch[i]==dir) && (dirSearch[i]->nextEntry>0)) 
00327                 dirSearch[i]->nextEntry--;
00328         }   
00329     }
00330 }
00331 
00332 void DOS_Drive_Cache::CacheOut(const char* path, bool ignoreLastDir) {
00333     char expand[CROSS_LEN] = { 0 };
00334     CFileInfo* dir;
00335     
00336     if (ignoreLastDir) {
00337         char tmp[CROSS_LEN] = { 0 };
00338         Bit32s len=0;
00339         const char* pos = strrchr(path,CROSS_FILESPLIT);
00340         if (pos) len = (Bit32s)(pos - path);
00341         if (len>0) { 
00342             safe_strncpy(tmp,path,len+1); 
00343         } else  {
00344             strcpy(tmp,path);
00345         }
00346         dir = FindDirInfo(tmp,expand);
00347     } else {
00348         dir = FindDirInfo(path,expand); 
00349     }
00350 
00351 //  LOG_DEBUG("DIR: Caching out %s : dir %s",expand,dir->orgname);
00352 //  clear cache first?
00353     for (Bit32u i=0; i<MAX_OPENDIRS; i++) {
00354         dirSearch[i] = 0; //free[i] = true;    
00355     }
00356     // delete file objects...
00357     //Maybe check if it is a file and then only delete the file and possibly the long name. instead of all objects in the dir.
00358     for(Bit32u i=0; i<dir->fileList.size(); i++) {
00359         if (dirSearch[srchNr]==dir->fileList[i]) dirSearch[srchNr] = 0;
00360         DeleteFileInfo(dir->fileList[i]); dir->fileList[i] = 0;
00361     }
00362     // clear lists
00363     dir->fileList.clear();
00364     dir->longNameList.clear();
00365     save_dir = 0;
00366 }
00367 
00368 bool DOS_Drive_Cache::IsCachedIn(CFileInfo* curDir) {
00369     return (curDir->isOverlayDir || curDir->fileList.size()>0);
00370 }
00371 
00372 
00373 bool DOS_Drive_Cache::GetShortName(const char* fullname, char* shortname) {
00374     // Get Dir Info
00375     char expand[CROSS_LEN] = {0};
00376     CFileInfo* curDir = FindDirInfo(fullname,expand);
00377 
00378     const char* pos = strrchr(fullname,CROSS_FILESPLIT);
00379     if (pos) pos++; else return false;
00380 
00381     std::vector<CFileInfo*>::size_type filelist_size = curDir->longNameList.size();
00382     if (GCC_UNLIKELY(filelist_size<=0)) return false;
00383 
00384     // The orgname part of the list is not sorted (shortname is)! So we can only walk through it.
00385     for(Bitu i = 0; i < filelist_size; i++) {
00386 #if defined (WIN32) || defined (OS2)                        /* Win 32 & OS/2*/
00387         if (strcmp(pos,curDir->longNameList[i]->orgname) == 0) {
00388 #else
00389         if (strcmp(pos,curDir->longNameList[i]->orgname) == 0) {
00390 #endif
00391             strcpy(shortname,curDir->longNameList[i]->shortname);
00392             return true;
00393         }
00394     }
00395     return false;
00396 }
00397 
00398 int DOS_Drive_Cache::CompareShortname(const char* compareName, const char* shortName) {
00399     char const* cpos = strchr(shortName,'~');
00400     if (cpos) {
00401 /* the following code is replaced as it's not safe when char* is 64 bits */
00402 /*      Bits compareCount1  = (int)cpos - (int)shortName;
00403         char* endPos        = strchr(cpos,'.');
00404         Bitu numberSize     = endPos ? int(endPos)-int(cpos) : strlen(cpos);
00405         
00406         char* lpos          = strchr(compareName,'.');
00407         Bits compareCount2  = lpos ? int(lpos)-int(compareName) : strlen(compareName);
00408         if (compareCount2>8) compareCount2 = 8;
00409 
00410         compareCount2 -= numberSize;
00411         if (compareCount2>compareCount1) compareCount1 = compareCount2;
00412 */
00413         size_t compareCount1 = strcspn(shortName,"~");
00414         size_t numberSize    = strcspn(cpos,".");
00415         size_t compareCount2 = strcspn(compareName,".");
00416         if(compareCount2 > 8) compareCount2 = 8;
00417         /* We want 
00418          * compareCount2 -= numberSize;
00419          * if (compareCount2>compareCount1) compareCount1 = compareCount2;
00420          * but to prevent negative numbers: 
00421          */
00422         if(compareCount2 > compareCount1 + numberSize)
00423             compareCount1 = compareCount2 - numberSize;
00424         return strncmp(compareName,shortName,compareCount1);
00425     }
00426     return strcmp(compareName,shortName);
00427 }
00428 
00429 Bitu DOS_Drive_Cache::CreateShortNameID(CFileInfo* curDir, const char* name) {
00430     std::vector<CFileInfo*>::size_type filelist_size = curDir->longNameList.size();
00431     if (GCC_UNLIKELY(filelist_size<=0)) return 1;   // shortener IDs start with 1
00432 
00433     Bitu foundNr    = 0;    
00434     Bits low        = 0;
00435     Bits high       = (Bits)(filelist_size-1);
00436 
00437     while (low<=high) {
00438         Bits mid = (low+high)/2;
00439         Bits res = CompareShortname(name,curDir->longNameList[(size_t)mid]->shortname);
00440         
00441         if (res>0)  low  = mid+1; else
00442         if (res<0)  high = mid-1; 
00443         else {
00444             // any more same x chars in next entries ?  
00445             do {
00446                 foundNr = curDir->longNameList[(size_t)mid]->shortNr;
00447                 mid++;
00448             } while((Bitu)mid<curDir->longNameList.size() && (CompareShortname(name,curDir->longNameList[(size_t)mid]->shortname)==0));
00449             break;
00450         }
00451     }
00452     return foundNr+1;
00453 }
00454 
00455 bool DOS_Drive_Cache::RemoveTrailingDot(char* shortname) {
00456 // remove trailing '.' if no extension is available (Linux compatibility)
00457     size_t len = strlen(shortname);
00458     if (len && (shortname[len-1]=='.')) {
00459         if (len==1) return false;
00460         if ((len==2) && (shortname[0]=='.')) return false;
00461         shortname[len-1] = 0;   
00462         return true;
00463     }   
00464     return false;
00465 }
00466 
00467 #define WINE_DRIVE_SUPPORT 1
00468 #if WINE_DRIVE_SUPPORT
00469 //Changes to interact with WINE by supporting their namemangling.
00470 //The code is rather slow, because orglist is unordered, so it needs to be avoided if possible.
00471 //Hence the tests in GetLongFileName
00472 
00473 
00474 // From the Wine project
00475 static Bits wine_hash_short_file_name( char* name, char* buffer )
00476 {
00477     static const char hash_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
00478     static const char invalid_chars[] = { '*','?','<','>','|','"','+','=',',',';','[',']',' ','\345','~','.',0 };
00479     char* p;
00480     char* ext;
00481     char* end = name + strlen(name);
00482     char* dst;
00483     unsigned short hash;
00484     int i;
00485 
00486     // Compute the hash code of the file name
00487     for (p = name, hash = 0xbeef; p < end - 1; p++)
00488         hash = (hash<<3) ^ (hash>>5) ^ tolower(*p) ^ (tolower(p[1]) << 8);
00489     hash = (hash<<3) ^ (hash>>5) ^ tolower(*p); // Last character
00490 
00491 
00492     // Find last dot for start of the extension
00493     for (p = name + 1, ext = NULL; p < end - 1; p++) if (*p == '.') ext = p;
00494 
00495     // Copy first 4 chars, replacing invalid chars with '_'
00496     for (i = 4, p = name, dst = buffer; i > 0; i--, p++)
00497     {
00498         if (p == end || p == ext) break;
00499         *dst++ = (*p < 0 || strchr( invalid_chars, *p ) != NULL) ? '_' : toupper(*p);
00500     }
00501     // Pad to 5 chars with '~'
00502     while (i-- >= 0) *dst++ = '~';
00503 
00504     // Insert hash code converted to 3 ASCII chars
00505     *dst++ = hash_chars[(hash >> 10) & 0x1f];
00506     *dst++ = hash_chars[(hash >> 5) & 0x1f];
00507     *dst++ = hash_chars[hash & 0x1f];
00508 
00509     // Copy the first 3 chars of the extension (if any)
00510     if (ext)
00511     {
00512         *dst++ = '.';
00513         for (i = 3, ext++; (i > 0) && ext < end; i--, ext++)
00514             *dst++ = (*ext < 0 || strchr( invalid_chars, *ext ) != NULL) ? '_' : toupper(*ext);
00515     }
00516 
00517     return (Bits)(dst - buffer);
00518 }
00519 #endif
00520 
00521 Bits DOS_Drive_Cache::GetLongName(CFileInfo* curDir, char* shortName) {
00522     std::vector<CFileInfo*>::size_type filelist_size = curDir->fileList.size();
00523     if (GCC_UNLIKELY(filelist_size<=0)) return -1;
00524 
00525     // Remove dot, if no extension...
00526     RemoveTrailingDot(shortName);
00527     // Search long name and return array number of element
00528     Bits res;
00529         if (strlen(shortName))
00530                 for (Bitu i=0; i<filelist_size; i++) {
00531                         if (!strcasecmp(shortName,curDir->fileList[i]->orgname) || !strcasecmp(shortName,curDir->fileList[i]->shortname)) {
00532                                 strcpy(shortName,curDir->fileList[i]->orgname);
00533                                 return (Bits)i;
00534                         }
00535                 }
00536 
00537 #ifdef WINE_DRIVE_SUPPORT
00538     if (strlen(shortName) < 8 || shortName[4] != '~' || shortName[5] == '.' || shortName[6] == '.' || shortName[7] == '.') return -1; // not available
00539     // else it's most likely a Wine style short name ABCD~###, # = not dot  (length at least 8) 
00540     // The above test is rather strict as the following loop can be really slow if filelist_size is large.
00541     char buff[CROSS_LEN];
00542     for (Bitu i = 0; i < filelist_size; i++) {
00543         res = wine_hash_short_file_name(curDir->fileList[i]->orgname,buff);
00544         buff[res] = 0;
00545         if (!strcmp(shortName,buff)) {  
00546             // Found
00547             strcpy(shortName,curDir->fileList[i]->orgname);
00548             return (Bits)i;
00549         }
00550     }
00551 #endif
00552     // not available
00553     return -1;
00554 }
00555 
00556 bool DOS_Drive_Cache::RemoveSpaces(char* str) {
00557 // Removes all spaces
00558     char*   curpos  = str;
00559     char*   chkpos  = str;
00560     while (*chkpos!=0) { 
00561         if (*chkpos==' ') chkpos++; else *curpos++ = *chkpos++; 
00562     }
00563     *curpos = 0;
00564     return (curpos!=chkpos);
00565 }
00566 
00567 char * shiftjis_upcase(char * str);
00568 
00569 void DOS_Drive_Cache::CreateShortName(CFileInfo* curDir, CFileInfo* info) {
00570     Bits    len         = 0;
00571     bool    createShort = false;
00572 
00573     char tmpNameBuffer[CROSS_LEN];
00574 
00575     char* tmpName = tmpNameBuffer;
00576 
00577     // Remove Spaces
00578     strcpy(tmpName,info->orgname);
00579     if (IS_PC98_ARCH)
00580         shiftjis_upcase(tmpName);
00581     else
00582         upcase(tmpName);
00583 
00584     createShort = RemoveSpaces(tmpName);
00585 
00586     // Get Length of filename
00587     char* pos = strchr(tmpName,'.');
00588     if (pos) {
00589         // ignore preceding '.' if extension is longer than "3"
00590         if (strlen(pos)>4) {
00591             while (*tmpName=='.') tmpName++;
00592             createShort = true;
00593         }
00594         pos = strchr(tmpName,'.');
00595         if (pos)    len = (Bits)(pos - tmpName);
00596         else        len = (Bits)strlen(tmpName);
00597     } else {
00598         len = (Bits)strlen(tmpName);
00599     }
00600 
00601     // Should shortname version be created ?
00602     createShort = createShort || (len>8);
00603     if (!createShort) {
00604         char buffer[CROSS_LEN];
00605         strcpy(buffer,tmpName);
00606         createShort = (GetLongName(curDir,buffer)>=0);
00607     }
00608 
00609     if (createShort) {
00610         // Create number
00611         char buffer[8];
00612         info->shortNr = CreateShortNameID(curDir,tmpName);
00613         sprintf(buffer,"%d",(int)info->shortNr);
00614         // Copy first letters
00615         Bits tocopy = 0;
00616         size_t buflen = strlen(buffer);
00617         if ((size_t)len+buflen+1u>8u) tocopy = (Bits)(8u - buflen - 1u);
00618         else                          tocopy = len;
00619         safe_strncpy(info->shortname,tmpName,tocopy+1);
00620         // Copy number
00621         strcat(info->shortname,"~");
00622         strcat(info->shortname,buffer);
00623         // Add (and cut) Extension, if available
00624         if (pos) {
00625             // Step to last extension...
00626             pos = strrchr(tmpName, '.');
00627             // add extension
00628             strncat(info->shortname,pos,4);
00629             info->shortname[DOS_NAMELENGTH] = 0;
00630         }
00631 
00632         // keep list sorted for CreateShortNameID to work correctly
00633         if (curDir->longNameList.size()>0) {
00634             if (!(strcmp(info->shortname,curDir->longNameList.back()->shortname)<0)) {
00635                 // append at end of list
00636                 curDir->longNameList.push_back(info);
00637             } else {
00638                 // look for position where to insert this element
00639                 bool found=false;
00640                 std::vector<CFileInfo*>::iterator it;
00641                 for (it=curDir->longNameList.begin(); it!=curDir->longNameList.end(); ++it) {
00642                     if (strcmp(info->shortname,(*it)->shortname)<0) {
00643                         found = true;
00644                         break;
00645                     }
00646                 }
00647                 // Put it in longname list...
00648                 if (found) curDir->longNameList.insert(it,info);
00649                 else curDir->longNameList.push_back(info);
00650             }
00651         } else {
00652             // empty file list, append
00653             curDir->longNameList.push_back(info);
00654         }
00655     } else {
00656         strcpy(info->shortname,tmpName);
00657     }
00658     RemoveTrailingDot(info->shortname);
00659 }
00660 
00661 DOS_Drive_Cache::CFileInfo* DOS_Drive_Cache::FindDirInfo(const char* path, char* expandedPath) {
00662     // statics
00663     static char split[2] = { CROSS_FILESPLIT,0 };
00664     char        dir  [CROSS_LEN]; 
00665     const char* start = path;
00666     const char*     pos;
00667     CFileInfo*  curDir = dirBase;
00668     Bit16u      id;
00669 
00670     if (save_dir && (strcmp(path,save_path)==0)) {
00671         strcpy(expandedPath,save_expanded);
00672         return save_dir;
00673     }
00674 
00675 //  LOG_DEBUG("DIR: Find %s",path);
00676 
00677     // Remove base dir path
00678     start += strlen(basePath);
00679     strcpy(expandedPath,basePath);
00680 
00681     // hehe, baseDir should be cached in... 
00682     if (!IsCachedIn(curDir)) {
00683         char work [CROSS_LEN];
00684         strcpy(work,basePath);
00685         if (OpenDir(curDir,work,id)) {
00686             char buffer[CROSS_LEN];
00687             char *result = 0, *lresult = 0;
00688             strcpy(buffer,dirPath);
00689             ReadDir(id,result,lresult);
00690             strcpy(dirPath,buffer);
00691             if (dirSearch[id]) {
00692                 dirSearch[id]->id = MAX_OPENDIRS;
00693                 dirSearch[id] = 0;
00694             }
00695         }
00696     }
00697 
00698     do {
00699 // TODO: In PC-98 mode, use a Shift-JIS aware version of strchr() to find the path separator.
00700 //       It's possible for the host path separator to appear in the trailing end of a double-byte character.
00701 //      bool errorcheck = false;
00702         pos = strchr(start,CROSS_FILESPLIT);
00703         if (pos) { safe_strncpy(dir,start,pos-start+1); /*errorcheck = true;*/ }
00704         else     { strcpy(dir,start); }
00705  
00706         // Path found
00707         Bits nextDir = GetLongName(curDir,dir);
00708         strcat(expandedPath,dir);
00709 
00710         // Error check
00711 /*      if ((errorcheck) && (nextDir<0)) {
00712             LOG_DEBUG("DIR: Error: %s not found.",expandedPath);
00713         };
00714 */
00715         // Follow Directory
00716         if ((nextDir>=0) && curDir->fileList[(size_t)nextDir]->isDir) {
00717             curDir = curDir->fileList[(size_t)nextDir];
00718             strcpy (curDir->orgname,dir);
00719             if (!IsCachedIn(curDir)) {
00720                 if (OpenDir(curDir,expandedPath,id)) {
00721                     char buffer[CROSS_LEN];
00722                                         char *result = 0, *lresult = 0;
00723                     strcpy(buffer,dirPath);
00724                     ReadDir(id,result,lresult);
00725                     strcpy(dirPath,buffer);
00726                     if (dirSearch[id]) {
00727                         dirSearch[id]->id = MAX_OPENDIRS;
00728                         dirSearch[id] = 0;
00729                     }
00730                 }
00731             }
00732         }
00733         if (pos) {
00734             strcat(expandedPath,split);
00735             start = pos+1;
00736         }
00737     } while (pos);
00738 
00739     // Save last result for faster access next time
00740     strcpy(save_path,path);
00741     strcpy(save_expanded,expandedPath);
00742     save_dir = curDir;
00743 
00744     return curDir;
00745 }
00746 
00747 bool DOS_Drive_Cache::OpenDir(const char* path, Bit16u& id) {
00748     char expand[CROSS_LEN] = {0};
00749     CFileInfo* dir = FindDirInfo(path,expand);
00750     if (OpenDir(dir,expand,id)) {
00751         dirSearch[id]->nextEntry = 0;
00752         return true;
00753     }
00754     return false;
00755 }
00756 
00757 bool DOS_Drive_Cache::OpenDir(CFileInfo* dir, const char* expand, Bit16u& id) {
00758     id = GetFreeID(dir);
00759     dirSearch[id] = dir;
00760     char expandcopy [CROSS_LEN];
00761     strcpy(expandcopy,expand);   
00762     // Add "/"
00763     if (expandcopy[strlen(expandcopy)-1]!=CROSS_FILESPLIT) {
00764         char end[2]={CROSS_FILESPLIT,0};
00765         strcat(expandcopy,end);
00766     }
00767     // open dir
00768     if (dirSearch[id]) {
00769         // open dir
00770         void* dirp = drive->opendir(expandcopy);
00771         if (dirp || dir->isOverlayDir) { 
00772             // Reset it..
00773             if (dirp) drive->closedir(dirp);
00774             strcpy(dirPath,expandcopy);
00775             return true;
00776         }
00777         if (dirSearch[id]) {
00778             dirSearch[id]->id = MAX_OPENDIRS;
00779             dirSearch[id] = 0;
00780         }
00781     }
00782     return false;
00783 }
00784 
00785 char* DOS_Drive_Cache::CreateEntry(CFileInfo* dir, const char* name, const char* sname, bool is_directory) {
00786     CFileInfo* info = new CFileInfo;
00787     strcpy(info->shortname, sname);
00788         strcpy(info->orgname, name);
00789     info->shortNr = 0;
00790     info->isDir = is_directory;
00791 
00792     // Check for long filenames...
00793     if (sname[0]==0) CreateShortName(dir, info);
00794 
00795     // keep list sorted (so GetLongName works correctly, used by CreateShortName in this routine)
00796     if (dir->fileList.size()>0) {
00797         if (!(strcmp(info->shortname,dir->fileList.back()->shortname)<0)) {
00798             // append at end of list
00799             dir->fileList.push_back(info);
00800         } else {
00801             bool found = false;
00802             // look for position where to insert this element
00803             std::vector<CFileInfo*>::iterator it;
00804             for (it=dir->fileList.begin(); it!=dir->fileList.end(); ++it) {
00805                 if (strcmp(info->shortname,(*it)->shortname)<0) {
00806                     found = true;
00807                     break;
00808                 }
00809             }
00810             // Put file in lists
00811             if (found) dir->fileList.insert(it,info);
00812             else dir->fileList.push_back(info);
00813         }
00814     } else {
00815         // empty file list, append
00816         dir->fileList.push_back(info);
00817     }
00818         static char sgenname[DOS_NAMELENGTH+1];
00819         strcpy(sgenname, info->shortname);
00820         return sgenname;
00821 }
00822 
00823 void DOS_Drive_Cache::CopyEntry(CFileInfo* dir, CFileInfo* from) {
00824     CFileInfo* info = new CFileInfo;
00825     // just copy things into new fileinfo
00826     strcpy(info->orgname, from->orgname);           
00827     strcpy(info->shortname, from->shortname);
00828     info->shortNr = from->shortNr;
00829     info->isDir = from->isDir;
00830 
00831     dir->fileList.push_back(info);
00832 }
00833 
00834 bool DOS_Drive_Cache::ReadDir(Bit16u id, char* &result, char * &lresult) {
00835     // shouldnt happen...
00836     if (id>=MAX_OPENDIRS) return false;
00837 
00838     if (!IsCachedIn(dirSearch[id])) {
00839         // Try to open directory
00840         void* dirp = drive->opendir(dirPath);
00841         if (!dirp) {
00842             if (dirSearch[id]) {
00843                 dirSearch[id]->id = MAX_OPENDIRS;
00844                 dirSearch[id] = 0;
00845             }
00846             return false;
00847         }
00848         // Read complete directory
00849         char dir_name[CROSS_LEN], dir_sname[DOS_NAMELENGTH+1];
00850         bool is_directory;
00851         if (drive->read_directory_first(dirp, dir_name, dir_sname, is_directory)) {
00852             CreateEntry(dirSearch[id], dir_name, dir_sname, is_directory);
00853             while (drive->read_directory_next(dirp, dir_name, dir_sname, is_directory)) {
00854                 CreateEntry(dirSearch[id], dir_name, dir_sname, is_directory);
00855             }
00856         }
00857 
00858         // close dir
00859         drive->closedir(dirp);
00860 
00861         // Info
00862 /*      if (!dirp) {
00863             LOG_DEBUG("DIR: Error Caching in %s",dirPath);          
00864             return false;
00865         } else {    
00866             char buffer[128];
00867             sprintf(buffer,"DIR: Caching in %s (%d Files)",dirPath,dirSearch[srchNr]->fileList.size());
00868             LOG_DEBUG(buffer);
00869         };*/
00870     }
00871         if (SetResult(dirSearch[id], result, lresult, dirSearch[id]->nextEntry)) return true;
00872     if (dirSearch[id]) {
00873         dirSearch[id]->id = MAX_OPENDIRS;
00874         dirSearch[id] = 0;
00875     }
00876     return false;
00877 }
00878 
00879 bool DOS_Drive_Cache::SetResult(CFileInfo* dir, char* &result, char* &lresult, Bitu entryNr)
00880 {
00881     static char res[CROSS_LEN] = { 0 };
00882     static char lres[CROSS_LEN] = { 0 };
00883 
00884     result = res;
00885     lresult = lres;
00886 
00887     if (entryNr>=dir->fileList.size()) return false;
00888     CFileInfo* info = dir->fileList[entryNr];
00889     // copy filename, short version
00890     strcpy(res,info->shortname);
00891     strcpy(lres,info->orgname);
00892     // Set to next Entry
00893     dir->nextEntry = entryNr+1;
00894     return true;
00895 }
00896 
00897 // FindFirst / FindNext
00898 bool DOS_Drive_Cache::FindFirst(char* path, Bit16u& id) {
00899     Bit16u  dirID;
00900     // Cache directory in 
00901     if (!OpenDir(path,dirID)) return false;
00902 
00903     //Find a free slot.
00904     //If the next one isn't free, move on to the next, if none is free => reset and assume the worst
00905     Bit16u local_findcounter = 0;
00906     while ( local_findcounter < MAX_OPENDIRS ) {
00907         if (dirFindFirst[this->nextFreeFindFirst] == 0) break;
00908         if (++this->nextFreeFindFirst >= MAX_OPENDIRS) this->nextFreeFindFirst = 0; //Wrap around
00909         local_findcounter++;
00910     }
00911 
00912     Bit16u  dirFindFirstID = this->nextFreeFindFirst++;
00913     if (this->nextFreeFindFirst >= MAX_OPENDIRS) this->nextFreeFindFirst = 0; //Increase and wrap around for the next search.
00914 
00915     if (local_findcounter == MAX_OPENDIRS) { //Here is the reset from above.
00916         // no free slot found...
00917         LOG(LOG_MISC,LOG_ERROR)("DIRCACHE: FindFirst/Next: All slots full. Resetting");
00918         // Clear the internal list then.
00919         dirFindFirstID = 0;
00920         this->nextFreeFindFirst = 1; //the next free one after this search
00921         for(Bitu n=0; n<MAX_OPENDIRS;n++) { 
00922             // Clear and reuse slot
00923             DeleteFileInfo(dirFindFirst[n]);
00924             dirFindFirst[n]=0;
00925         }
00926        
00927     }       
00928     dirFindFirst[dirFindFirstID] = new CFileInfo();
00929     dirFindFirst[dirFindFirstID]-> nextEntry    = 0;
00930 
00931     // Copy entries to use with FindNext
00932     for (Bitu i=0; i<dirSearch[dirID]->fileList.size(); i++) {
00933         CopyEntry(dirFindFirst[dirFindFirstID],dirSearch[dirID]->fileList[i]);
00934     }
00935     // Now re-sort the fileList accordingly to output
00936     switch (sortDirType) {
00937         case ALPHABETICAL       : break;
00938 //      case ALPHABETICAL       : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByName);      break;
00939         case DIRALPHABETICAL    : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByDirName);       break;
00940         case ALPHABETICALREV    : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByNameRev);       break;
00941         case DIRALPHABETICALREV : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByDirNameRev);    break;
00942         case NOSORT             : break;
00943     }
00944 
00945 //  LOG(LOG_MISC,LOG_ERROR)("DIRCACHE: FindFirst : %s (ID:%02X)",path,dirFindFirstID);
00946     id = dirFindFirstID;
00947     return true;
00948 }
00949 
00950 bool DOS_Drive_Cache::FindNext(Bit16u id, char* &result, char* &lresult) {
00951     // out of range ?
00952     if ((id>=MAX_OPENDIRS) || !dirFindFirst[id]) {
00953         LOG(LOG_MISC,LOG_ERROR)("DIRCACHE: FindFirst/Next failure : ID out of range: %04X",id);
00954         return false;
00955     }
00956     if (!SetResult(dirFindFirst[id], result, lresult, dirFindFirst[id]->nextEntry)) {
00957         // free slot
00958         DeleteFileInfo(dirFindFirst[id]); dirFindFirst[id] = 0;
00959         return false;
00960     }
00961     return true;
00962 }
00963 
00964 void DOS_Drive_Cache::ClearFileInfo(CFileInfo *dir) {
00965     for(Bit32u i=0; i<dir->fileList.size(); i++) {
00966         if (CFileInfo *info = dir->fileList[i])
00967             ClearFileInfo(info);
00968     }
00969     if (dir->id != MAX_OPENDIRS) {
00970         dirSearch[dir->id] = 0;
00971         dir->id = MAX_OPENDIRS;
00972     }
00973 }
00974 
00975 void DOS_Drive_Cache::DeleteFileInfo(CFileInfo *dir) {
00976     if (dir)
00977         ClearFileInfo(dir);
00978     delete dir;
00979 }