/* * Copyright (C) 2002-2007 The DOSBox Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* $Id: drive_cache.cpp,v 1.50 2007/06/12 20:22:08 c2woody Exp $ */ #include "drives.h" #include "dos_inc.h" #include "dirent.h" #include "support.h" // STL stuff #include #include #include #if defined (WIN32) /* Win 32 */ #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from #include #endif #if defined (OS2) #define INCL_DOSERRORS #define INCL_DOSFILEMGR #include #endif int fileInfoCounter = 0; bool SortByName(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) { return strcmp(a->shortname,b->shortname)<0; }; bool SortByNameRev(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) { return strcmp(a->shortname,b->shortname)>0; }; bool SortByDirName(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) { // Directories first... if (a->isDir!=b->isDir) return (a->isDir>b->isDir); return strcmp(a->shortname,b->shortname)<0; }; bool SortByDirNameRev(DOS_Drive_Cache::CFileInfo* const &a, DOS_Drive_Cache::CFileInfo* const &b) { // Directories first... if (a->isDir!=b->isDir) return (a->isDir>b->isDir); return strcmp(a->shortname,b->shortname)>0; }; DOS_Drive_Cache::DOS_Drive_Cache(void) { dirBase = new CFileInfo; save_dir = 0; srchNr = 0; label[0] = 0; nextFreeFindFirst = 0; for (Bit32u i=0; iupdatelabel) return; this->updatelabel = allowupdate; Bitu togo = 8; Bitu vnamePos = 0; Bitu labelPos = 0; bool point = false; while (togo>0) { if (vname[vnamePos]==0) break; if (!point && (vname[vnamePos]=='.')) { togo=4; point=true; } label[labelPos] = toupper(vname[vnamePos]); labelPos++; vnamePos++; togo--; if ((togo==0) && !point) { if (vname[vnamePos]=='.') vnamePos++; label[labelPos]='.'; labelPos++; point=true; togo=3; } }; label[labelPos]=0; //Remove trailing dot. if((labelPos > 0) && (label[labelPos-1] == '.')) label[labelPos-1]=0; LOG(LOG_DOSMISC,LOG_NORMAL)("DIRCACHE: Set volume label to %s",label); }; Bit16u DOS_Drive_Cache::GetFreeID(CFileInfo* dir) { for (Bit16u i=0; i 26) { // drive letter was lowercase drivenumber = drive[0] - 'a' + 1; } APIRET rc = DosQueryFSInfo(drivenumber, FSIL_VOLSER, &fsinfo, sizeof(FSINFO)); if (rc == NO_ERROR) { #endif /* Set label and allow being updated */ SetLabel(labellocal,true); } #endif }; void DOS_Drive_Cache::ExpandName(char* path) { strcpy(path,GetExpandName(path)); }; char* DOS_Drive_Cache::GetExpandName(const char* path) { static char work [CROSS_LEN] = { 0 }; char dir [CROSS_LEN]; work[0] = 0; strcpy (dir,path); const char* pos = strrchr(path,CROSS_FILESPLIT); if (pos) dir[pos-path+1] = 0; CFileInfo* dirInfo = FindDirInfo(dir, work); if (pos) { // Last Entry = File strcpy(dir,pos+1); GetLongName(dirInfo, dir); strcat(work,dir); } if(work && *work) { size_t len = strlen(work); #if defined (WIN32) if((work[len-1] == CROSS_FILESPLIT ) && (len >= 2) && (work[len-2] != ':')) { #else if((len > 1) && (work[len-1] == CROSS_FILESPLIT )) { #endif work[len-1] = 0; // Remove trailing slashes except when in root } } return work; }; void DOS_Drive_Cache::AddEntry(const char* path, bool checkExists) { // Get Last part... char file [CROSS_LEN]; char expand [CROSS_LEN]; CFileInfo* dir = FindDirInfo(path,expand); const char* pos = strrchr(path,CROSS_FILESPLIT); if (pos) { strcpy(file,pos+1); // Check if file already exists, then dont add new entry... if (checkExists && (GetLongName(dir,file)>=0)) return; CreateEntry(dir,file); // Sort Lists - filelist has to be alphabetically sorted std::sort(dir->fileList.begin(), dir->fileList.end(), SortByName); Bits index = GetLongName(dir,file); if (index>=0) { Bit32u i; // Check if there are any open search dir that are affected by this... if (dir) for (i=0; inextEntry)) dirSearch[i]->nextEntry++; } }; // LOG_DEBUG("DIR: Added Entry %s",path); } else { // LOG_DEBUG("DIR: Error: Failed to add %s",path); }; }; void DOS_Drive_Cache::DeleteEntry(const char* path, bool ignoreLastDir) { CacheOut(path,ignoreLastDir); if (dirSearch[srchNr] && (dirSearch[srchNr]->nextEntry>0)) dirSearch[srchNr]->nextEntry--; if (!ignoreLastDir) { // Check if there are any open search dir that are affected by this... Bit32u i; char expand [CROSS_LEN]; CFileInfo* dir = FindDirInfo(path,expand); if (dir) for (i=0; inextEntry>0)) dirSearch[i]->nextEntry--; } } }; void DOS_Drive_Cache::CacheOut(const char* path, bool ignoreLastDir) { char expand[CROSS_LEN] = { 0 }; CFileInfo* dir; if (ignoreLastDir) { char tmp[CROSS_LEN] = { 0 }; Bit32s len = strrchr(path,CROSS_FILESPLIT) - path; if (len>0) { safe_strncpy(tmp,path,len+1); } else { strcpy(tmp,path); } dir = FindDirInfo(tmp,expand); } else { dir = FindDirInfo(path,expand); } // LOG_DEBUG("DIR: Caching out %s : dir %s",expand,dir->orgname); // delete file objects... for(Bit32u i=0; ifileList.size(); i++) { if (dirSearch[srchNr]==dir->fileList[i]) dirSearch[srchNr] = 0; delete dir->fileList[i]; dir->fileList[i] = 0; } // clear lists dir->fileList.clear(); dir->longNameList.clear(); save_dir = 0; }; bool DOS_Drive_Cache::IsCachedIn(CFileInfo* curDir) { return (curDir->fileList.size()>0); }; bool DOS_Drive_Cache::GetShortName(const char* fullname, char* shortname) { // Get Dir Info char expand[CROSS_LEN] = {0}; CFileInfo* curDir = FindDirInfo(fullname,expand); Bits low = 0; Bits high = curDir->longNameList.size()-1; Bits mid, res; while (low<=high) { mid = (low+high)/2; res = strcmp(fullname,curDir->longNameList[mid]->orgname); if (res>0) low = mid+1; else if (res<0) high = mid-1; else { strcpy(shortname,curDir->longNameList[mid]->shortname); return true; }; } return false; }; int DOS_Drive_Cache::CompareShortname(const char* compareName, const char* shortName) { char const* cpos = strchr(shortName,'~'); if (cpos) { /* the following code is replaced as it's not safe when char* is 64 bits */ /* Bits compareCount1 = (int)cpos - (int)shortName; char* endPos = strchr(cpos,'.'); Bitu numberSize = endPos ? int(endPos)-int(cpos) : strlen(cpos); char* lpos = strchr(compareName,'.'); Bits compareCount2 = lpos ? int(lpos)-int(compareName) : strlen(compareName); if (compareCount2>8) compareCount2 = 8; compareCount2 -= numberSize; if (compareCount2>compareCount1) compareCount1 = compareCount2; */ size_t compareCount1 = strcspn(shortName,"~"); size_t numberSize = strcspn(cpos,"."); size_t compareCount2 = strcspn(compareName,"."); if(compareCount2 > 8) compareCount2 = 8; /* We want * compareCount2 -= numberSize; * if (compareCount2>compareCount1) compareCount1 = compareCount2; * but to prevent negative numbers: */ if(compareCount2 > compareCount1 + numberSize) compareCount1 = compareCount2 - numberSize; return strncmp(compareName,shortName,compareCount1); } return strcmp(compareName,shortName); }; Bit16u DOS_Drive_Cache::CreateShortNameID(CFileInfo* curDir, const char* name) { Bits foundNr = 0; Bits low = 0; Bits high = curDir->longNameList.size()-1; Bits mid, res; while (low<=high) { mid = (low+high)/2; res = CompareShortname(name,curDir->longNameList[mid]->shortname); if (res>0) low = mid+1; else if (res<0) high = mid-1; else { // any more same x chars in next entries ? do { foundNr = curDir->longNameList[mid]->shortNr; mid++; } while((Bitu)midlongNameList.size() && (CompareShortname(name,curDir->longNameList[mid]->shortname)==0)); break; }; } return foundNr+1; }; bool DOS_Drive_Cache::RemoveTrailingDot(char* shortname) // remove trailing '.' if no extension is available (Linux compatibility) { Bitu len = strlen(shortname); if (len && (shortname[len-1]=='.')) { if (len==1) return false; if ((len==2) && (shortname[0]=='.')) return false; shortname[len-1] = 0; return true; } return false; }; Bits DOS_Drive_Cache::GetLongName(CFileInfo* curDir, char* shortName) { // Remove dot, if no extension... RemoveTrailingDot(shortName); // Search long name and return array number of element Bits low = 0; Bits high = curDir->fileList.size()-1; Bits mid,res; while (low<=high) { mid = (low+high)/2; res = strcmp(shortName,curDir->fileList[mid]->shortname); if (res>0) low = mid+1; else if (res<0) high = mid-1; else { // Found strcpy(shortName,curDir->fileList[mid]->orgname); return mid; }; } // not available return -1; }; bool DOS_Drive_Cache::RemoveSpaces(char* str) // Removes all spaces { char* curpos = str; char* chkpos = str; while (*chkpos!=0) { if (*chkpos==' ') chkpos++; else *curpos++ = *chkpos++; } *curpos = 0; return (curpos!=chkpos); }; void DOS_Drive_Cache::CreateShortName(CFileInfo* curDir, CFileInfo* info) { Bits len = 0; Bits lenExt = 0; bool createShort = false; char tmpNameBuffer[CROSS_LEN]; char* tmpName = tmpNameBuffer; // Remove Spaces strcpy(tmpName,info->orgname); upcase(tmpName); createShort = RemoveSpaces(tmpName); // Get Length of filename char* pos = strchr(tmpName,'.'); if (pos) { // Get Length of extension lenExt = strlen(pos)-1; // ignore preceding '.' if extension is longer than "3" if (lenExt>3) { while (*tmpName=='.') tmpName++; createShort = true; }; pos = strchr(tmpName,'.'); if (pos) len = (Bit16u)(pos - tmpName); else len = strlen(tmpName); } else len = strlen(tmpName); // Should shortname version be created ? createShort = createShort || (len>8); if (!createShort) { char buffer[CROSS_LEN]; strcpy(buffer,tmpName); createShort = (GetLongName(curDir,buffer)>=0); } if (createShort) { // Create number char buffer[8]; info->shortNr = CreateShortNameID(curDir,tmpName); sprintf(buffer,"%d",info->shortNr); // Copy first letters Bit16u tocopy = 0; if (len+strlen(buffer)+1>8) tocopy = 8 - strlen(buffer) - 1; else tocopy = len; safe_strncpy(info->shortname,tmpName,tocopy+1); // Copy number strcat(info->shortname,"~"); strcat(info->shortname,buffer); // Add (and cut) Extension, if available if (pos) { // Step to last extension... pos = strrchr(tmpName, '.'); // add extension strncat(info->shortname,pos,4); info->shortname[DOS_NAMELENGTH] = 0; }; // Put it in longname list... curDir->longNameList.push_back(info); std::sort(curDir->longNameList.begin(), curDir->longNameList.end(), SortByName); } else { strcpy(info->shortname,tmpName); } RemoveTrailingDot(info->shortname); }; DOS_Drive_Cache::CFileInfo* DOS_Drive_Cache::FindDirInfo(const char* path, char* expandedPath) { // statics static char split[2] = { CROSS_FILESPLIT,0 }; char dir [CROSS_LEN]; char work [CROSS_LEN]; const char* start = path; const char* pos; CFileInfo* curDir = dirBase; Bit16u id; if (save_dir && (strcmp(path,save_path)==0)) { strcpy(expandedPath,save_expanded); return save_dir; }; // LOG_DEBUG("DIR: Find %s",path); // Remove base dir path start += strlen(basePath); strcpy(expandedPath,basePath); // hehe, baseDir should be cached in... if (!IsCachedIn(curDir)) { strcpy(work,basePath); if (OpenDir(curDir,work,id)) { char buffer[CROSS_LEN]; char* result = 0; strcpy(buffer,dirPath); ReadDir(id,result); strcpy(dirPath,buffer); free[id] = true; }; }; do { // bool errorcheck = false; pos = strchr(start,CROSS_FILESPLIT); if (pos) { safe_strncpy(dir,start,pos-start+1); /*errorcheck = true;*/ } else { strcpy(dir,start); }; // Path found Bits nextDir = GetLongName(curDir,dir); strcat(expandedPath,dir); // Error check /* if ((errorcheck) && (nextDir<0)) { LOG_DEBUG("DIR: Error: %s not found.",expandedPath); }; */ // Follow Directory if ((nextDir>=0) && curDir->fileList[nextDir]->isDir) { curDir = curDir->fileList[nextDir]; strcpy (curDir->orgname,dir); if (!IsCachedIn(curDir)) { if (OpenDir(curDir,expandedPath,id)) { char buffer[CROSS_LEN]; char* result = 0; strcpy(buffer,dirPath); ReadDir(id,result); strcpy(dirPath,buffer); free[id] = true; }; } }; if (pos) { strcat(expandedPath,split); start = pos+1; } } while (pos); // Save last result for faster access next time strcpy(save_path,path); strcpy(save_expanded,expandedPath); save_dir = curDir; return curDir; }; bool DOS_Drive_Cache::OpenDir(const char* path, Bit16u& id) { char expand[CROSS_LEN] = {0}; CFileInfo* dir = FindDirInfo(path,expand); if (OpenDir(dir,expand,id)) { dirSearch[id]->nextEntry = 0; return true; } return false; }; bool DOS_Drive_Cache::OpenDir(CFileInfo* dir, const char* expand, Bit16u& id) { id = GetFreeID(dir); dirSearch[id] = dir; char expandcopy [CROSS_LEN]; strcpy(expandcopy,expand); // Add "/" char end[2]={CROSS_FILESPLIT,0}; if (expandcopy[strlen(expandcopy)-1]!=CROSS_FILESPLIT) strcat(expandcopy,end); // open dir if (dirSearch[id]) { // open dir DIR* dirp = opendir(expandcopy); if (dirp) { // Reset it.. closedir(dirp); strcpy(dirPath,expandcopy); free[id] = false; return true; } }; return false; }; void DOS_Drive_Cache::CreateEntry(CFileInfo* dir, const char* name) { struct stat status; CFileInfo* info = new CFileInfo; strcpy(info->orgname ,name); info->shortNr = 0; // Read and copy file stats char buffer[CROSS_LEN]; strcpy(buffer,dirPath); strcat(buffer,info->orgname); if (stat(buffer,&status)==0) info->isDir = (S_ISDIR(status.st_mode)>0); else info->isDir = false; // Check for long filenames... CreateShortName(dir, info); // Put file in lists dir->fileList.push_back(info); }; bool DOS_Drive_Cache::ReadDir(Bit16u id, char* &result) { // shouldnt happen... if (id>MAX_OPENDIRS) return false; if (!IsCachedIn(dirSearch[id])) { // Try to open directory DIR* dirp = opendir(dirPath); if (!dirp) { free[id] = true; return false; } // Read complete directory struct dirent* tmpres; while ((tmpres = readdir(dirp))!=NULL) { CreateEntry(dirSearch[id],tmpres->d_name); // Sort Lists - filelist has to be alphabetically sorted, even in between (for finding double file names) // hmpf.. bit slow probably... std::sort(dirSearch[id]->fileList.begin(), dirSearch[id]->fileList.end(), SortByName); } // close dir closedir(dirp); // Info /* if (!dirp) { LOG_DEBUG("DIR: Error Caching in %s",dirPath); return false; } else { char buffer[128]; sprintf(buffer,"DIR: Caching in %s (%d Files)",dirPath,dirSearch[srchNr]->fileList.size()); LOG_DEBUG(buffer); };*/ }; if (SetResult(dirSearch[id], result, dirSearch[id]->nextEntry)) return true; free[id] = true; return false; }; bool DOS_Drive_Cache::SetResult(CFileInfo* dir, char* &result, Bit16u entryNr) { static char res[CROSS_LEN] = { 0 }; result = res; if (entryNr>=dir->fileList.size()) return false; CFileInfo* info = dir->fileList[entryNr]; // copy filename, short version strcpy(res,info->shortname); // Set to next Entry dir->nextEntry = entryNr+1; return true; }; // FindFirst / FindNext bool DOS_Drive_Cache::FindFirst(char* path, Bitu& id) { Bit16u dirID; Bitu dirFindFirstID = this->nextFreeFindFirst; // Cache directory in if (!OpenDir(path,dirID)) return false; this->nextFreeFindFirst++; //increase it for the next search if (dirFindFirstID == MAX_OPENDIRS) { // no free slot found... LOG(LOG_MISC,LOG_ERROR)("DIRCACHE: FindFirst/Next: All slots full. Resetting"); // Clear the internal list then. dirFindFirstID = 0; this->nextFreeFindFirst = 1; //the next free one after this search for(Bitu n=0; n nextEntry = 0; // Copy entries to use with FindNext for (Bitu i=0; ifileList.size(); i++) { CreateEntry(dirFindFirst[dirFindFirstID],dirSearch[dirID]->fileList[i]->orgname); // Sort Lists - filelist has to be alphabetically sorted, even in between (for finding double file names) std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByName); }; // Now re-sort the fileList accordingly to output switch (sortDirType) { case ALPHABETICAL : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByName); break; case DIRALPHABETICAL : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByDirName); break; case ALPHABETICALREV : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByNameRev); break; case DIRALPHABETICALREV : std::sort(dirFindFirst[dirFindFirstID]->fileList.begin(), dirFindFirst[dirFindFirstID]->fileList.end(), SortByDirNameRev); break; case NOSORT : break; }; // LOG(LOG_MISC,LOG_ERROR)("DIRCACHE: FindFirst : %s (ID:%02X)",path,dirFindFirstID); id = dirFindFirstID; return true; }; bool DOS_Drive_Cache::FindNext(Bitu id, char* &result) { // out of range ? if ((id>=MAX_OPENDIRS) || !dirFindFirst[id]) { LOG(LOG_MISC,LOG_ERROR)("DIRCACHE: FindFirst/Next failure : ID out of range: %04X",id); return false; } if (!SetResult(dirFindFirst[id], result, dirFindFirst[id]->nextEntry)) { // free slot delete dirFindFirst[id]; dirFindFirst[id] = 0; return false; } return true; };