diff --git a/include/dos_system.h b/include/dos_system.h index 8ab25d16..631c5cbf 100644 --- a/include/dos_system.h +++ b/include/dos_system.h @@ -128,8 +128,8 @@ public: bool UpdateDateTimeFromHost(void); void FlagReadOnlyMedium(void); void Flush(void); + FILE * fhandle; //todo handle this properly private: - FILE * fhandle; bool read_only_medium; enum { NONE,READ,WRITE } last_action; }; diff --git a/src/dos/Makefile.am b/src/dos/Makefile.am index 3bdfbf51..36a2d99b 100644 --- a/src/dos/Makefile.am +++ b/src/dos/Makefile.am @@ -7,4 +7,4 @@ libdos_a_SOURCES = dos.cpp dos_devices.cpp dos_execute.cpp dos_files.cpp dos_ioc drives.cpp drives.h drive_virtual.cpp drive_local.cpp drive_cache.cpp drive_fat.cpp \ drive_iso.cpp dev_con.h dos_mscdex.cpp dos_keyboard_layout.cpp \ cdrom.h cdrom.cpp cdrom_ioctl_win32.cpp cdrom_aspi_win32.cpp cdrom_ioctl_linux.cpp cdrom_image.cpp \ - cdrom_ioctl_os2.cpp + cdrom_ioctl_os2.cpp drive_overlay.cpp diff --git a/src/dos/dos_programs.cpp b/src/dos/dos_programs.cpp index 4ca3ada2..38cda889 100644 --- a/src/dos/dos_programs.cpp +++ b/src/dos/dos_programs.cpp @@ -195,14 +195,14 @@ public: std::string type="dir"; cmd->FindString("-t",type,true); bool iscdrom = (type =="cdrom"); //Used for mscdex bug cdrom label name emulation - if (type=="floppy" || type=="dir" || type=="cdrom") { + if (type=="floppy" || type=="dir" || type=="cdrom" || type =="overlay") { Bit16u sizes[4]; Bit8u mediaid; std::string str_size; if (type=="floppy") { str_size="512,1,2880,2880";/* All space free */ mediaid=0xF0; /* Floppy 1.44 media */ - } else if (type=="dir") { + } else if (type=="dir" || type == "overlay") { // 512*32*32765==~500MB total size // 512*32*16000==~250MB total free size str_size="512,32,32765,16000"; @@ -381,7 +381,39 @@ public: #else if(temp_line == "/") WriteOut(MSG_Get("PROGRAM_MOUNT_WARNING_OTHER")); #endif - newdrive=new localDrive(temp_line.c_str(),sizes[0],bit8size,sizes[2],sizes[3],mediaid); + if(type == "overlay") { + //Ensure that the base drive exists: + if (!Drives[drive-'A']) { + WriteOut("No basedrive mounted yet!"); + return; + } + localDrive* ldp = dynamic_cast(Drives[drive-'A']); + cdromDrive* cdp = dynamic_cast(Drives[drive-'A']); + if (!ldp || cdp) { + WriteOut("Basedrive not compatible"); + return; + } + std::string base = ldp->getBasedir(); + Bit8u o_error = 0; + newdrive = new Overlay_Drive(base.c_str(),temp_line.c_str(),sizes[0],bit8size,sizes[2],sizes[3],mediaid,o_error); + //Erase old drive on succes + if (newdrive) { + if (o_error) { + if (o_error == 1) WriteOut("No mixing of relative and absolute paths. Overlay failed."); + else if (o_error == 2) WriteOut("overlay directory can not be the same as underlying filesystem."); + else WriteOut("Something went wrong"); + delete newdrive; + return; + } + delete Drives[drive-'A']; + Drives[drive-'A'] = 0; + } else { + WriteOut("overlaydrive construction failed."); + return; + } + } else { + newdrive=new localDrive(temp_line.c_str(),sizes[0],bit8size,sizes[2],sizes[3],mediaid); + } } } else { WriteOut(MSG_Get("PROGRAM_MOUNT_ILL_TYPE"),type.c_str()); @@ -396,13 +428,14 @@ public: Drives[drive-'A']=newdrive; /* Set the correct media byte in the table */ mem_writeb(Real2Phys(dos.tables.mediaid)+(drive-'A')*9,newdrive->GetMediaByte()); - WriteOut(MSG_Get("PROGRAM_MOUNT_STATUS_2"),drive,newdrive->GetInfo()); + if (type != "overlay") WriteOut(MSG_Get("PROGRAM_MOUNT_STATUS_2"),drive,newdrive->GetInfo()); + else WriteOut("Overlay %s on drive %c mounted.\n",temp_line.c_str(),drive); /* check if volume label is given and don't allow it to updated in the future */ if (cmd->FindString("-label",label,true)) newdrive->dirCache.SetLabel(label.c_str(),iscdrom,false); /* For hard drives set the label to DRIVELETTER_Drive. * For floppy drives set the label to DRIVELETTER_Floppy. * This way every drive except cdroms should get a label.*/ - else if(type == "dir") { + else if(type == "dir" || type == "overlay") { label = drive; label += "_DRIVE"; newdrive->dirCache.SetLabel(label.c_str(),iscdrom,false); } else if(type == "floppy") { diff --git a/src/dos/drive_overlay.cpp b/src/dos/drive_overlay.cpp new file mode 100644 index 00000000..46f9ee93 --- /dev/null +++ b/src/dos/drive_overlay.cpp @@ -0,0 +1,1039 @@ +/* + * Copyright (C) 2002-2019 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. + */ + +#include "dosbox.h" +#include "dos_inc.h" +#include "drives.h" +#include "support.h" +#include "cross.h" +#include "inout.h" +#include "timer.h" + +#include +#include + +#include +#include +#include +#include +#include + +bool logoverlay = false; +using namespace std; + +#if defined (WIN32) || defined (OS2) /* Win 32 & OS/2*/ +#define CROSS_DOSFILENAME(blah) +#else +//Convert back to DOS PATH +#define CROSS_DOSFILENAME(blah) strreplace(blah,'/','\\') +#endif + + +/* + * design principles/limitations/requirements: + * 1) All directories that can be used for saving, must exist already in the base before mounting. (they will be created by DOSBox if missing in the overlay) + * 2) All filenames inside the overlay directories are UPPERCASE and conform to the 8.3 standard except for the special DBOVERLAY files. + * 3) To keep point 1 valid at all times, support for creating/renaming/removing directories has been disabled. + * + * Points 1 and 3 are still being worked on, so these limitations can be removed. + */ + + +//TODO recheck directories under linux with the filename_cache (as one adds the dos name (and runs cross_filename on the other)) + + +//TODO Check: Maybe handle file redirection in ccc (opening the new file), (call update datetime host there ?) + + +/* For rename/delete(unlink)/makedir/removedir we need to rebuild the cache. (shouldn't be needed, + * but cacheout/delete entry currently throw away the cached folder and rebuild it on read. + * so we have to ensure the rebuilding is controlled through the overlay. + * In order to not reread the overlay directory contents, the information in there is cached and updated when + * it changes (when deleting a file or adding one) + */ + + +//directories that exist only in overlay can not be added to the drive_cache currently. +//Either upgrade addentry to support directories. (without actually caching stuff in! (code in testing)) +//Or create an empty directory in local drive base. + +bool Overlay_Drive::RemoveDir(char * dir) { + //DOS_RemoveDir checks if directory exists. + E_Exit("Overlay: trying to remove directory: %s",dir); + /* Overlay: Check if folder is empty (findfirst/next ?), if so, then it is not too tricky. */ + char newdir[CROSS_LEN]; + strcpy(newdir,basedir); + strcat(newdir,dir); + CROSS_FILENAME(newdir); + int temp=rmdir(dirCache.GetExpandName(newdir)); + if (temp==0) dirCache.DeleteEntry(newdir,true); + return (temp==0); +} + +bool Overlay_Drive::MakeDir(char * dir) { + //DOS_MakeDir tries first, before checking if the directory already exists, so doing it here as well, so that case is handled. + if (TestDir(dir)) return false; + E_Exit("Overlay trying to make directory: %s",dir); + /* Overlay: Create in Overlay only and add it to drive_cache + some entries else the drive_cache will try to access it. Needs an AddEntry for directories. */ + + char newdir[CROSS_LEN]; + strcpy(newdir,basedir); + strcat(newdir,dir); + CROSS_FILENAME(newdir); +#if defined (WIN32) /* MS Visual C++ */ + int temp=mkdir(dirCache.GetExpandName(newdir)); +#else + int temp=mkdir(dirCache.GetExpandName(newdir),0700); +#endif + if (temp==0) dirCache.CacheOut(newdir,true); + + return (temp==0);// || ((temp!=0) && (errno==EEXIST)); +} + +bool Overlay_Drive::TestDir(char * dir) { + //Check if the directory is marked as deleted or one of its leading directories is. + + //Directories are stored without a trailing backslash + char tempdir[CROSS_LEN]; + strcpy(tempdir,dir); + size_t templen = strlen(dir); + if (templen && tempdir[templen-1] == '\\') tempdir[templen-1] = 0; + if (is_deleted_path(tempdir)) return false; + + // Rely on the localDrive directories. + // Directory is good according to the overlay, pass on to LocalDrive + return localDrive::TestDir(dir); +} + + +class OverlayFile: public localFile { +public: + OverlayFile(const char* name, FILE * handle):localFile(name,handle){ + if (logoverlay) LOG_MSG("constructing OverlayFile: %s",name); + } + bool Write(Bit8u * data,Bit16u * size) { + Bit32u f = real_flags&0xf; + if (!overlay_active && (f == OPEN_READWRITE || f == OPEN_WRITE)) { + if (logoverlay) LOG_MSG("write detected, switching file for %s",GetName()); + if (*data == 0) { + if (logoverlay) LOG_MSG("OPTIMISE: truncate on switch!!!!"); + } + Bitu a = GetTicks(); + bool r = create_copy(); + if (GetTicks()-a >2) { + if (logoverlay) LOG_MSG("OPTIMISE: switching took %d",GetTicks()-a); + } + if (!r) return false; + overlay_active = true; + flags = real_flags; + + } + return localFile::Write(data,size); + } + bool create_copy(); +//private: + Bit32u real_flags; + bool overlay_active; +}; + +//Create leading directories of a file being overlayed if they exist in the original (localDrive). +//This function is used to create copies of existing files, so all leading directories exist in the original. + +FILE* Overlay_Drive::create_file_in_overlay(char* dos_filename, char const* mode) { + + if (logoverlay) LOG_MSG("create_file_in_overlay called %s %s",dos_filename,mode); + char newname[CROSS_LEN]; + strcpy(newname,overlaydir); //TODO GOG make part of class and join in + strcat(newname,dos_filename); //HERE we need to convert it to Linux TODO + CROSS_FILENAME(newname); + + FILE* f = fopen(newname,mode); + //Check if a directories are part of the name: + char* dir = strrchr(dos_filename,'\\'); + if (!f && dir && *dir) { + if (logoverlay) LOG_MSG("Overlay: warning creating a file inside a directory %s",dos_filename); + //ensure they exist, else make them in the overlay if they exist in the original.... + Sync_leading_dirs(dos_filename); + //try again + f = fopen(newname,mode); + } + + return f; +} + +#ifndef BUFSIZ +#define BUFSIZ 2048 +#endif + +//bool OverlayFile::create_copy(DOS_File * file, char* newname) +bool OverlayFile::create_copy() { + //test if open/valid/etc + //ensure file position + if (logoverlay) LOG_MSG("create_copy called %s",GetName()); + + FILE* lhandle = this->fhandle; + fseek(lhandle,ftell(lhandle),SEEK_SET); + int location_in_old_file = ftell(lhandle); + fseek(lhandle,0L,SEEK_SET); + + FILE* newhandle = NULL; + Bit8u drive_set = GetDrive(); + if (drive_set != 0xff && drive_set < DOS_DRIVES && Drives[drive_set]){ + Overlay_Drive* od = dynamic_cast(Drives[drive_set]); + if (od) { + newhandle = od->create_file_in_overlay(GetName(),"wb+"); //todo check wb+ + } + } +// newhandle = create_file(newname,"wb+"); + if (!newhandle) return false; + char buffer[BUFSIZ]; + size_t s; + while ( (s = fread(buffer,1,BUFSIZ,lhandle)) ) fwrite(buffer, 1, s, newhandle); + fclose(lhandle); + //Set copied file handle to position of the old one + fseek(newhandle,location_in_old_file,SEEK_SET); + this->fhandle = newhandle; + //Flags ? + if (logoverlay) LOG_MSG("success"); + return true; +} + + + +static OverlayFile* ccc(DOS_File* file) { + localFile* l = dynamic_cast(file); + if (!l) E_Exit("overlay input file is not a localFile"); + //Create an overlayFile + OverlayFile* ret = new OverlayFile(l->GetName(),l->fhandle); + ret->flags = l->flags; + delete l; + return ret; +} + +Overlay_Drive::Overlay_Drive(const char * startdir,const char* overlay, Bit16u _bytes_sector,Bit8u _sectors_cluster,Bit16u _total_clusters,Bit16u _free_clusters,Bit8u _mediaid,Bit8u &error) +:localDrive(startdir,_bytes_sector,_sectors_cluster,_total_clusters,_free_clusters,_mediaid),special_prefix("DBOVERLAY") { + optimize_cache_v1 = true; //Try to not reread overlay files on deletes. Ideally drive_cache should be improved to handle deletes properly. + //Currently this flag does nothing, as the current behavior is to not reread due to caching everything. +#if defined (WIN32) + if (strcasecmp(startdir,overlay) == 0) { +#else + if (strcmp(startdir,overlay) == 0) { +#endif + //overlay directory can not be the base directory + error = 2; + return; + } + + std::string s(startdir); + std::string o(overlay); + bool s_absolute = Cross::IsPathAbsolute(s); + bool o_absolute = Cross::IsPathAbsolute(o); + error = 0; + if (s_absolute != o_absolute) { + error = 1; + return; + } + strcpy(overlaydir,overlay); + char dirname[CROSS_LEN] = { 0 }; + //Determine if overlaydir is part of the startdir. + convert_overlay_to_DOSname_in_base(dirname); + + + if(strlen(dirname) && dirname[strlen(dirname)-1] == '\\') dirname[strlen(dirname)-1] = 0; + + //add_deleted_path(dirname); //update_cache will add the overlap_folder + overlap_folder = dirname; + + update_cache(true); +} + +void Overlay_Drive::convert_overlay_to_DOSname_in_base(char* dirname ) +{ + dirname[0] = 0;//ensure good return string + if (strlen(overlaydir) >= strlen(basedir) ) { + //Needs to be longer at least. +#if defined (WIN32) +//OS2 ? + if (strncasecmp(overlaydir,basedir,strlen(basedir)) == 0) { +#else + if (strncmp(overlaydir,basedir,strlen(basedir)) == 0) { +#endif + //Beginning is the same. + char t[CROSS_LEN]; + strcpy(t,overlaydir+strlen(basedir)); + + char* p = t; + char* b = t; + + while ( (p =strchr(p,CROSS_FILESPLIT)) ) { + char directoryname[CROSS_LEN]={0}; + char dosboxdirname[CROSS_LEN]={0}; + strcpy(directoryname,dirname); + strncat(directoryname,b,p-b); + + char d[CROSS_LEN]; + strcpy(d,basedir); + strcat(d,directoryname); + CROSS_FILENAME(d); + //Try to find the corresponding directoryname in DOSBox. + if(!dirCache.GetShortName(d,dosboxdirname) ) { + //Not a long name, assume it is a short name instead + strncpy(dosboxdirname,b,p-b); + upcase(dosboxdirname); + } + + + strcat(dirname,dosboxdirname); + strcat(dirname,"\\"); + + if (logoverlay) LOG_MSG("HIDE directory: %s",dirname); + + + b=++p; + + } + } + } +} + +bool Overlay_Drive::FileOpen(DOS_File * * file,char * name,Bit32u flags) { + const char* type; + switch (flags&0xf) { + case OPEN_READ: type = "rb" ; break; + case OPEN_WRITE: type = "rb+"; break; + case OPEN_READWRITE: type = "rb+"; break; +// case OPEN_READ_NO_MOD: type = "rb" ; break; //No modification of dates. LORD4.07 uses this + default: + DOS_SetError(DOSERR_ACCESS_CODE_INVALID); + return false; + } + +#if 0 + //Flush the buffer of handles for the same file. (Betrayal in Antara) + Bit8u i,drive=DOS_DRIVES; + localFile *lfp; + for (i=0;iIsOpen() && Files[i]->GetDrive()==drive && Files[i]->IsName(name)) { + lfp=dynamic_cast(Files[i]); + if (lfp) lfp->Flush(); + } + } +#endif + + + //Todo check name first against local tree + //if name exists, use that one instead! + //overlay file. + char newname[CROSS_LEN]; + strcpy(newname,overlaydir); + strcat(newname,name); + CROSS_FILENAME(newname); + + FILE * hand = fopen(newname,type); + bool fileopened = false; + if (hand) { + if (logoverlay) LOG_MSG("overlay file opened %s",newname); + *file=new localFile(name,hand); + (*file)->flags=flags; + fileopened = true; + } else { + ; //TODO error handling!!!! (maybe check if it exists and read only (should not happen with overlays) + } + bool overlayed = fileopened; + + //File not present in overlay, try normal drive + //TODO take care of file being marked deleted. + + if (!fileopened && !is_deleted_file(name)) fileopened = localDrive::FileOpen(file,name, OPEN_READ); + + + if (fileopened) { + if (logoverlay) LOG_MSG("file opened %s",name); + //Convert file to OverlayFile + OverlayFile* f = ccc(*file); + //Store original flags, as with overlay the files are opened read only and they switch on write + f->real_flags = flags; + f->overlay_active = overlayed; //No need to switch if already in overlayed. + *file = f; + } + return fileopened; +} + + +bool Overlay_Drive::FileCreate(DOS_File * * file,char * name,Bit16u /*attributes*/) { + + //TODO Check if it exists in the dirCache ? // fix addentry ? or just double check (ld and overlay) + //AddEntry looks sound to me.. + + FILE* f = create_file_in_overlay(name,"wb+"); + if(!f) { + if (logoverlay) LOG_MSG("File creation in overlay system failed %s",name); + return false; + } + *file = new localFile(name,f); + (*file)->flags = OPEN_READWRITE; + OverlayFile* of = ccc(*file); + of->overlay_active = true; + of->real_flags = OPEN_READWRITE; + *file = of; + //create fake name for the drive cache + char fakename[CROSS_LEN]; + strcpy(fakename,basedir); + strcat(fakename,name); + CROSS_FILENAME(fakename); + dirCache.AddEntry(fakename,true); //add it. + add_DOSname_to_cache(name); + remove_deleted_file(name,true); + return true; +} +void Overlay_Drive::add_DOSname_to_cache(const char* name) { + for (std::vector::const_iterator itc = DOSnames_cache.begin(); itc != DOSnames_cache.end();itc++){ + if (name == (*itc)) return; + } + DOSnames_cache.push_back(name); +} +void Overlay_Drive::remove_DOSname_from_cache(const char* name) { + for (std::vector::iterator it = DOSnames_cache.begin(); it != DOSnames_cache.end();it++) { + if (name == (*it)) { DOSnames_cache.erase(it); return;} + } + +} + +bool Overlay_Drive::Sync_leading_dirs(const char* dos_filename){ + const char* lastdir = strrchr(dos_filename,'\\'); + //If there are no directories, return success. + if (!lastdir) return true; + + const char* leaddir = dos_filename; + while ( (leaddir=strchr(leaddir,'\\')) != 0) { + char dirname[CROSS_LEN] = {0}; + strncpy(dirname,dos_filename,leaddir-dos_filename); + + if (logoverlay) LOG_MSG("syncdir: %s",dirname); + //Test if directory exist in base. + char dirnamebase[CROSS_LEN] ={0}; + strcpy(dirnamebase,basedir); + strcat(dirnamebase,dirname); + CROSS_FILENAME(dirnamebase); + struct stat basetest; + if (stat(dirCache.GetExpandName(dirnamebase),&basetest) == 0 && basetest.st_mode & S_IFDIR) { + if (logoverlay) LOG_MSG("base exists: %s",dirnamebase); + //Directory exists in base folder. + //Ensure it exists in overlay as well + + struct stat overlaytest; + char dirnameoverlay[CROSS_LEN] ={0}; + strcpy(dirnameoverlay,overlaydir); + strcat(dirnameoverlay,dirname); + CROSS_FILENAME(dirnameoverlay); + if (stat(dirnameoverlay,&overlaytest) == 0 ) { + //item exist. Check if it is a folder, if not a folder =>fail! + if ((overlaytest.st_mode & S_IFDIR) ==0) return false; + } else { + //folder does not exist, make it + if (logoverlay) LOG_MSG("creating %s",dirnameoverlay); +#if defined (WIN32) /* MS Visual C++ */ + int temp = mkdir(dirnameoverlay); +#else + int temp = mkdir(dirnameoverlay,0700); +#endif + if (temp != 0) return false; + } + } + leaddir = leaddir + 1; //Move to next + } + + return true; +} +void Overlay_Drive::update_cache(bool read_directory_contents) { + Bitu a = GetTicks(); + std::vector specials; + std::vector dirnames; + std::vector filenames; + if (read_directory_contents) { + //Clear all lists + DOSnames_cache.clear(); + deleted_files_in_base.clear(); + deleted_paths_in_base.clear(); + //Ensure hiding of the folder that contains the overlay, if it is part of the base folder. + add_deleted_path(overlap_folder.c_str()); + } + + + //What about sequences were a base file gets copied to a working save game and then removed/renamed... + //copy should be safe as then the link with the original doesn't exist. + //however the working safe can be rather complicated after a rename and delete.. + + //Currently directories existing only in the overlay can not be added to drive cache: + //1. possible workaround create empty directory in base. Drawback would break the no-touching-of-base. + //2. double up Addentry to support directories, (and adding . and .. to the newly directory so it counts as cachedin.. and won't be recached, as otherwise + // cache will realize we are faking it. + //Working on solution 2. + + //Random TODO: Does the root drive under DOS have . and .. ? + + //This function needs to be called after any localDrive function calling cacheout/deleteentry, as those throw away directories. + //either do this with a parameter stating the part that needs to be rebuild,(directory) or clear the cache by default and do it all. + + std::vector::iterator i; + std::string::size_type const prefix_lengh = special_prefix.length(); + if (read_directory_contents) { + dir_information* dirp = open_directory(overlaydir); + if (dirp == NULL) return; + // Read complete directory + char dir_name[CROSS_LEN]; + bool is_directory; + if (read_directory_first(dirp, dir_name, is_directory)) { + if ((strlen(dir_name) > prefix_lengh+5) && strncmp(dir_name,special_prefix.c_str(),prefix_lengh) == 0) specials.push_back(dir_name); + else if (is_directory) dirnames.push_back(dir_name); + else filenames.push_back(dir_name); + while (read_directory_next(dirp, dir_name, is_directory)) { + if ((strlen(dir_name) > prefix_lengh+5) && strncmp(dir_name,special_prefix.c_str(),prefix_lengh) == 0) specials.push_back(dir_name); + else if (is_directory) dirnames.push_back(dir_name); + else filenames.push_back(dir_name); + } + } + close_directory(dirp); + //parse directories to add them. + + + + for (i = dirnames.begin(); i != dirnames.end();i++) { + if ((*i) == ".") continue; + if ((*i) == "..") continue; + std::string testi(*i); + std::string::size_type ll = testi.length(); + //TODO: Use the dirname\. and dirname\.. for creating fake directories in the driveCache. + if( ll >2 && testi[ll-1] == '.' && testi[ll-2] == CROSS_FILESPLIT) continue; + if( ll >3 && testi[ll-1] == '.' && testi[ll-2] == '.' && testi[ll-3] == CROSS_FILESPLIT) continue; + + char dir[CROSS_LEN]; + strcpy(dir,overlaydir); + strcat(dir,(*i).c_str()); + char dirpush[CROSS_LEN]; + strcpy(dirpush,(*i).c_str()); + static char end[2] = {CROSS_FILESPLIT,0}; + strcat(dirpush,end); //Linux ? + dir_information* dirp = open_directory(dir); + if (dirp == NULL) continue; + std::string backupi(*i); + // Read complete directory + char dir_name[CROSS_LEN]; + bool is_directory; + if (read_directory_first(dirp, dir_name, is_directory)) { + if ((strlen(dir_name) > prefix_lengh+5) && strncmp(dir_name,special_prefix.c_str(),prefix_lengh) == 0) specials.push_back(string(dirpush)+dir_name); + else if (is_directory) dirnames.push_back(string(dirpush)+dir_name); + else filenames.push_back(string(dirpush)+dir_name); + while (read_directory_next(dirp, dir_name, is_directory)) { + if ((strlen(dir_name) > prefix_lengh+5) && strncmp(dir_name,special_prefix.c_str(),prefix_lengh) == 0) specials.push_back(string(dirpush)+dir_name); + else if (is_directory) dirnames.push_back(string(dirpush)+dir_name); + else filenames.push_back(string(dirpush)+dir_name); + } + } + close_directory(dirp); + for(i = dirnames.begin(); i != dirnames.end();i++) { + if ( (*i) == backupi) break; //find current directory again, for the next round. + } + } + } + + + if (read_directory_contents) { + for( i = filenames.begin(); i != filenames.end(); i++) { + char dosname[CROSS_LEN]; + strcpy(dosname,(*i).c_str()); + upcase(dosname); //Should not be really needed, as uppercase in the overlay is a requirement... + CROSS_DOSFILENAME(dosname); + if (logoverlay) LOG_MSG("update cache add dosname %s",dosname); + DOSnames_cache.push_back(dosname); + } + } + + for (i = DOSnames_cache.begin(); i != DOSnames_cache.end(); i++) { + char fakename[CROSS_LEN]; + strcpy(fakename,basedir); + strcat(fakename,(*i).c_str()); + CROSS_FILENAME(fakename); + dirCache.AddEntry(fakename,true); + } + + if (read_directory_contents) { + for (i = specials.begin(); i != specials.end();i++) { + //Specials look like this DBOVERLAY_YYY_FILENAME.EXT or DIRNAME[\/]DBOVERLAY_YYY_FILENAME.EXT where + //YYY is the operation involved. Currently only DEL is supported. + //DEL = file marked as deleted, (but exists in localDrive!) + std::string name(*i); + std::string special_dir(""); + std::string special_file(""); + std::string special_operation(""); + std::string::size_type s = name.find(special_prefix); + if (s == std::string::npos) continue; + if (s) { + special_dir = name.substr(0,s); + name.erase(0,s); + } + name.erase(0,special_prefix.length()+1); //Erase DBOVERLAY_ + s = name.find("_"); + if (s == std::string::npos ||s == 0) continue; + special_operation = name.substr(0,s); + name.erase(0,s + 1); + special_file = name; + if (special_file.length() == 0) continue; + if (special_operation == "DEL") { + name = special_dir + special_file; + //CROSS_DOSFILENAME for strings: + while ( (s = name.find('/')) != std::string::npos) name.replace(s,1,"\\"); + + add_deleted_file(name.c_str(),false); + } else { + if (logoverlay) LOG_MSG("unsupported operation %s on %s",special_operation.c_str(),(*i).c_str()); + } + + } + } + if (logoverlay) LOG_MSG("OPTIMISE: update cache took %d",GetTicks()-a); +} + +bool Overlay_Drive::FindNext(DOS_DTA & dta) { + + char * dir_ent; + struct stat stat_block; + char full_name[CROSS_LEN]; + char dir_entcopy[CROSS_LEN]; + + Bit8u srch_attr;char srch_pattern[DOS_NAMELENGTH_ASCII]; + Bit8u find_attr; + + dta.GetSearchParams(srch_attr,srch_pattern); + Bit16u id = dta.GetDirID(); + +again: + if (!dirCache.FindNext(id,dir_ent)) { + DOS_SetError(DOSERR_NO_MORE_FILES); + return false; + } + if(!WildFileCmp(dir_ent,srch_pattern)) goto again; + + strcpy(full_name,srchInfo[id].srch_dir); + strcat(full_name,dir_ent); + + //GetExpandName might indirectly destroy dir_ent (by caching in a new directory + //and due to its design dir_ent might be lost.) + //Copying dir_ent first + strcpy(dir_entcopy,dir_ent); + + //First try overlay: + char ovname[CROSS_LEN]; + char relativename[CROSS_LEN]; + strcpy(relativename,srchInfo[id].srch_dir); + //strip off basedir: //TODO cleanup + char* prel = relativename+strlen(basedir); + strcpy(ovname,overlaydir); + prel =full_name+strlen(basedir); + + + +#if 0 + //Check hidden/deleted directories first. TODO is this really needed. If the directory exist in the overlay things are weird anyway. + //the deleted paths are added to the deleted_files list. + if (is_deleted_dir(prel)) { + LOG_MSG("skipping early out deleted dir %s",prel); + goto again; + } +#endif + + strcat(ovname,full_name+strlen(basedir)); + bool statok = ( stat(ovname,&stat_block)==0); + + if (logoverlay) LOG_MSG("listing %s",dir_entcopy); + if (statok) { + if (logoverlay) LOG_MSG("using overlay data for %s : %s",full_name, ovname); + } else { + char preldos[CROSS_LEN]; + strcpy(preldos,prel); + CROSS_DOSFILENAME(preldos); + if (is_deleted_file(preldos)) { + if (logoverlay) LOG_MSG("skipping deleted file %s %s %s",preldos,full_name,ovname); + goto again; + } + if (stat(dirCache.GetExpandName(full_name),&stat_block)!=0) { + if (logoverlay) LOG_MSG("stat failed for %s . This should not happen.",dirCache.GetExpandName(full_name)); + goto again;//No symlinks and such + } + } + + if(stat_block.st_mode & S_IFDIR) find_attr=DOS_ATTR_DIRECTORY; + else find_attr=DOS_ATTR_ARCHIVE; + if (~srch_attr & find_attr & (DOS_ATTR_DIRECTORY | DOS_ATTR_HIDDEN | DOS_ATTR_SYSTEM)) goto again; + + /*file is okay, setup everything to be copied in DTA Block */ + char find_name[DOS_NAMELENGTH_ASCII];Bit16u find_date,find_time;Bit32u find_size; + + if(strlen(dir_entcopy)tm_year+1900),(Bit16u)(time->tm_mon+1),(Bit16u)time->tm_mday); + find_time=DOS_PackTime((Bit16u)time->tm_hour,(Bit16u)time->tm_min,(Bit16u)time->tm_sec); + } else { + find_time=6; + find_date=4; + } + dta.SetResult(find_name,find_size,find_date,find_time,find_attr); + return true; +} + + + +bool Overlay_Drive::FileUnlink(char * name) { + Bitu a = GetTicks(); + if (logoverlay) LOG_MSG("calling unlink on %s",name); + char basename[CROSS_LEN]; + strcpy(basename,basedir); + strcat(basename,name); + CROSS_FILENAME(basename); + + + char overlayname[CROSS_LEN]; + strcpy(overlayname,overlaydir); + strcat(overlayname,name); + CROSS_FILENAME(overlayname); +// char *fullname = dirCache.GetExpandName(newname); + if (unlink(overlayname)) { + //Unlink failed for some reason try finding it. + struct stat buffer; + if(stat(overlayname,&buffer)) { + //file not found in overlay, check the basedrive + //Check if file not already deleted + if (is_deleted_file(name)) return false; + + + char *fullname = dirCache.GetExpandName(basename); + if (stat(fullname,&buffer)) return false; // File not found in either, return file false. + //File does exist in normal drive. + //Maybe do something with the drive_cache. + add_deleted_file(name,true); + return true; +// E_Exit("trying to remove existing non-overlay file %s",name); + } + FILE* file_writable = fopen(overlayname,"rb+"); + if(!file_writable) return false; //No access ? ERROR MESSAGE NOT SET. FIXME ? + fclose(file_writable); + + //File exists and can technically be deleted, nevertheless it failed. + //This means that the file is probably open by some process. + //See if We have it open. + bool found_file = false; + for(Bitu i = 0;i < DOS_FILES;i++){ + if(Files[i] && Files[i]->IsName(name)) { + Bitu max = DOS_FILES; + while(Files[i]->IsOpen() && max--) { + Files[i]->Close(); + if (Files[i]->RemoveRef()<=0) break; + } + found_file=true; + } + } + if(!found_file) return false; + if (unlink(overlayname) == 0) { //Overlay file removed + //Mark basefile as deleted if it exists: + if (localDrive::FileExists(name)) add_deleted_file(name,true); + remove_DOSname_from_cache(name); + //Handle this better + dirCache.DeleteEntry(basename); + update_cache(false); + //Check if it exists in the base dir as well + + return true; + } + return false; + } else { //Removed from overlay. + //TODO IF it exists in the basedir: and more locations above. + if (localDrive::FileExists(name)) add_deleted_file(name,true); + remove_DOSname_from_cache(name); + //TODODO remove from the update_cache cache as well + //Handle this better + //Check if it exists in the base dir as well + dirCache.DeleteEntry(basename); + + update_cache(false); + if (logoverlay) LOG_MSG("OPTIMISE: unlink took %d",GetTicks()-a); + return true; + } +} + + +bool Overlay_Drive::GetFileAttr(char * name,Bit16u * attr) { + char overlayname[CROSS_LEN]; + strcpy(overlayname,overlaydir); + strcat(overlayname,name); + CROSS_FILENAME(overlayname); + + struct stat status; + if (stat(overlayname,&status)==0) { + *attr=DOS_ATTR_ARCHIVE; + if(status.st_mode & S_IFDIR) *attr|=DOS_ATTR_DIRECTORY; + return true; + } + //Maybe check for deleted path as well + if (is_deleted_file(name)) { + *attr = 0; + return false; + } + return localDrive::GetFileAttr(name,attr); + +} + + +void Overlay_Drive::add_deleted_file(const char* name,bool create_on_disk) { + if (logoverlay) LOG_MSG("add del file %s",name); + if (!is_deleted_file(name)) { + deleted_files_in_base.push_back(name); + if (create_on_disk) add_deleted_file_to_disk(name); + + } +} + +void Overlay_Drive::add_deleted_file_to_disk(const char* dosname) { + std::string name = create_filename_of_special_operation(dosname,"DEL"); + char overlayname[CROSS_LEN]; + strcpy(overlayname,overlaydir); + strcat(overlayname,name.c_str()); + CROSS_FILENAME(overlayname); + FILE* f = fopen(overlayname,"wb+"); + if (!f) { + Sync_leading_dirs(dosname); + f = fopen(overlayname,"wb+"); + } + if (!f) E_Exit("Failed creation of %s",overlayname); + char buf[5] = {'e','m','p','t','y'}; + fwrite(buf,5,1,f); + fclose(f); + +} +void Overlay_Drive::remove_deleted_file_from_disk(const char* dosname) { + std::string name = create_filename_of_special_operation(dosname,"DEL"); + char overlayname[CROSS_LEN]; + strcpy(overlayname,overlaydir); + strcat(overlayname,name.c_str()); + CROSS_FILENAME(overlayname); + if(unlink(overlayname) != 0) E_Exit("Failed removal of %s",overlayname); +} + +std::string Overlay_Drive::create_filename_of_special_operation(const char* dosname, const char* operation) { + + std::string res(dosname); + std::string::size_type s = res.rfind("\\"); //CHECK DOS or host endings.... on update_cache + if (s == std::string::npos) s = 0; else s++; + std::string oper = special_prefix +"_" +operation +"_"; + res.insert(s,oper); + return res; + + +} + +bool Overlay_Drive::is_deleted_file(const char* name) { + if (!name || !*name) return false; + if (deleted_files_in_base.empty()) return false; + for(std::vector::iterator it = deleted_files_in_base.begin(); it != deleted_files_in_base.end(); it++) { + if (*it == name) return true; + } + return false; +} + +void Overlay_Drive::remove_deleted_file(const char* name,bool create_on_disk) { + for(std::vector::iterator it = deleted_files_in_base.begin(); it != deleted_files_in_base.end(); it++) { + if (*it == name) { + deleted_files_in_base.erase(it); + if (create_on_disk) remove_deleted_file_from_disk(name); + break; + } + } +} +void Overlay_Drive::add_deleted_path(const char* name) { + if (!name || !*name ) return; //Skip empty file. + if (logoverlay) LOG_MSG("add del path %s",name); + if (!is_deleted_path(name)) { + deleted_paths_in_base.push_back(name); + //Add it to deleted files as well, so it gets skipped in FindNext. + //Maybe revise that. + add_deleted_file(name,false); + } +} +bool Overlay_Drive::is_deleted_path(const char* name) { + if (!name || !*name) return false; + if (deleted_paths_in_base.empty()) return false; + std::string sname(name); + std::string::size_type namelen = sname.length();; + for(std::vector::iterator it = deleted_paths_in_base.begin(); it != deleted_paths_in_base.end(); it++) { + std::string::size_type blockedlen = (*it).length(); + if (namelen < blockedlen) continue; + //See if input starts with name. + std::string::size_type n = sname.find(*it); + if (n == 0 && (namelen == blockedlen) || *(name+blockedlen) =='\\' ) return true; + } + return false; +} + +void Overlay_Drive::remove_deleted_path(const char* name) { + for(std::vector::iterator it = deleted_paths_in_base.begin(); it != deleted_paths_in_base.end(); it++) { + if (*it == name) { + deleted_paths_in_base.erase(it); + break; + } + } +} + +bool Overlay_Drive::FileExists(const char* name) { + char overlayname[CROSS_LEN]; + strcpy(overlayname,overlaydir); + strcat(overlayname,name); + CROSS_FILENAME(overlayname); + struct stat temp_stat; + if(stat(overlayname,&temp_stat)==0 && (temp_stat.st_mode & S_IFDIR)==0) return true; + + if (is_deleted_file(name)) return false; + + return localDrive::FileExists(name); +} + +#if 1 +bool Overlay_Drive::Rename(char * oldname,char * newname) { + //TODO with cache function! +//Tricky function. +//Renaming directories is currently not supported, due the drive_cache not handling that smoothly. +//So oldname is directory => Exit! +//If oldname is on overlay => simple rename. +//if oldname is on base => copy file to overlay with new name and mark old file as deleted. +//More advanced version. keep track of the file being renamed in order to detect that the file is being renamed back. + Bit16u attr=0; + if (!GetFileAttr(oldname,&attr)) E_Exit("rename, but source doesn't exist, should not happen %s",oldname); + if (attr&DOS_ATTR_DIRECTORY) E_Exit("renaming directory %s to %s . Not yet supported in Overlay",oldname,newname); + Bitu a = GetTicks(); + //First generate overlay names. + char overlaynameold[CROSS_LEN]; + strcpy(overlaynameold,overlaydir); + strcat(overlaynameold,oldname); + CROSS_FILENAME(overlaynameold); + + char overlaynamenew[CROSS_LEN]; + strcpy(overlaynamenew,overlaydir); + strcat(overlaynamenew,newname); + CROSS_FILENAME(overlaynamenew); + + //No need to check if the original is marked as deleted, as GetFileAttr would fail if it did. + + //Check if overlay source file exists + struct stat tempstat; + int temp = -1; + if (stat(overlaynameold,&tempstat) ==0) { + //Simple rename + temp = rename(overlaynameold,overlaynamenew); + //TODO CHECK if base has a file with same oldname!!!!! if it does mark it as deleted!! + if (localDrive::FileExists(oldname)) add_deleted_file(oldname,true); + } else { + Bitu aa = GetTicks(); + //File exists in the basedrive. Make a copy and mark old one as deleted. + char newold[CROSS_LEN]; + strcpy(newold,basedir); + strcat(newold,oldname); + CROSS_FILENAME(newold); + dirCache.ExpandName(newold); + FILE* o = fopen(newold,"rb"); + if (!o) return false; + FILE* n = create_file_in_overlay(newname,"wb+"); + if (!n) {fclose(o); return false;} + char buffer[BUFSIZ]; + size_t s; + while ( (s = fread(buffer,1,BUFSIZ,o)) ) fwrite(buffer, 1, s, n); + fclose(o); fclose(n); + + //File copied. + //Mark old file as deleted + add_deleted_file(oldname,true); + temp =0; //success + if (logoverlay) LOG_MSG("OPTIMISE: update rename with copy took %d",GetTicks()-aa); + + } + if (temp ==0) { + //handle the drive_cache (a bit better) + //Ensure that the file is not marked as deleted anymore. + if (is_deleted_file(newname)) remove_deleted_file(newname,true); + dirCache.EmptyCache(); + update_cache(true); + if (logoverlay) LOG_MSG("OPTIMISE: rename took %d",GetTicks()-a); + + } + return (temp==0); + +} +#endif + +bool Overlay_Drive::FindFirst(char * _dir,DOS_DTA & dta,bool fcb_findfirst) { + if (logoverlay) LOG_MSG("FindFirst in %s",_dir); + + if (is_deleted_path(_dir)) { + //No accidental listing of files in there. + DOS_SetError(DOSERR_PATH_NOT_FOUND); + return false; + } + + return localDrive::FindFirst(_dir,dta,fcb_findfirst); +} + +bool Overlay_Drive::FileStat(const char* name, FileStat_Block * const stat_block) { + char overlayname[CROSS_LEN]; + strcpy(overlayname,overlaydir); + strcat(overlayname,name); + CROSS_FILENAME(overlayname); + struct stat temp_stat; + if(stat(overlayname,&temp_stat) != 0) { + if (is_deleted_file(name)) return false; + return localDrive::FileStat(name,stat_block); + } + /* Convert the stat to a FileStat */ + struct tm *time; + if((time=localtime(&temp_stat.st_mtime))!=0) { + stat_block->time=DOS_PackTime((Bit16u)time->tm_hour,(Bit16u)time->tm_min,(Bit16u)time->tm_sec); + stat_block->date=DOS_PackDate((Bit16u)(time->tm_year+1900),(Bit16u)(time->tm_mon+1),(Bit16u)time->tm_mday); + } else { + // ... But this function is not used at the moment. + } + stat_block->size=(Bit32u)temp_stat.st_size; + return true; +} + +Bits Overlay_Drive::UnMount(void) { + delete this; + return 0; +} +void Overlay_Drive::EmptyCache(void){ + localDrive::EmptyCache(); + update_cache(true);//lets rebuild it. +} + diff --git a/src/dos/drives.h b/src/dos/drives.h index 589b6da4..e53ef9d4 100644 --- a/src/dos/drives.h +++ b/src/dos/drives.h @@ -21,6 +21,7 @@ #define _DRIVES_H__ #include +#include #include #include "dos_system.h" #include "shell.h" /* for DOS_Shell */ @@ -70,13 +71,17 @@ public: virtual bool isRemote(void); virtual bool isRemovable(void); virtual Bits UnMount(void); -private: + const char* getBasedir() {return basedir;}; +protected: char basedir[CROSS_LEN]; - friend void DOS_Shell::CMD_SUBST(char* args); +private: + friend void DOS_Shell::CMD_SUBST(char* args); +protected: struct { char srch_dir[CROSS_LEN]; } srchInfo[MAX_OPENDIRS]; +private: struct { Bit16u bytes_sector; Bit8u sectors_cluster; @@ -406,6 +411,51 @@ private: VFILE_Block * search_file; }; +class Overlay_Drive: public localDrive { +public: + Overlay_Drive(const char * startdir,const char* overlay, Bit16u _bytes_sector,Bit8u _sectors_cluster,Bit16u _total_clusters,Bit16u _free_clusters,Bit8u _mediaid,Bit8u &error); + virtual bool FileOpen(DOS_File * * file,char * name,Bit32u flags); + virtual bool FileCreate(DOS_File * * file,char * name,Bit16u /*attributes*/); + virtual bool FindFirst(char * _dir,DOS_DTA & dta,bool fcb_findfirst); + virtual bool FindNext(DOS_DTA & dta); + virtual bool FileUnlink(char * name); + virtual bool GetFileAttr(char * name,Bit16u * attr); + virtual bool FileExists(const char* name); + virtual bool Rename(char * oldname,char * newname); + virtual bool FileStat(const char* name, FileStat_Block * const stat_block); + virtual void EmptyCache(void); + + bool Sync_leading_dirs(const char* dos_filename); + FILE* create_file_in_overlay(char* dos_filename, char const* mode); + virtual Bits UnMount(void); + virtual bool TestDir(char * dir); + virtual bool RemoveDir(char * dir); + virtual bool MakeDir(char * dir); +private: + char overlaydir[CROSS_LEN]; + bool optimize_cache_v1; + void add_DOSname_to_cache(const char* name); + void remove_DOSname_from_cache(const char* name); + void update_cache(bool read_directory_contents = false); + + std::vector deleted_files_in_base; //Set is probably better, or some other solution (involving the disk). + std::vector deleted_paths_in_base; //Currently used to hide the overlay folder. + std::string overlap_folder; + void add_deleted_file(const char* name, bool create_on_disk=true); + void remove_deleted_file(const char* name, bool create_on_disk=true); + bool is_deleted_file(const char* name); + void add_deleted_path(const char* name); + void remove_deleted_path(const char* name); + bool is_deleted_path(const char* name); + + void remove_deleted_file_from_disk(const char* dosname); + void add_deleted_file_to_disk(const char* dosname); + std::string create_filename_of_special_operation(const char* dosname, const char* operation); + void convert_overlay_to_DOSname_in_base(char* dirname ); + //For caching the update_cache routine. + std::vector DOSnames_cache; //Also set is probably better. + const std::string special_prefix; +}; #endif diff --git a/visualc_net/dosbox.vcproj b/visualc_net/dosbox.vcproj index 36c30833..5a1bac18 100644 --- a/visualc_net/dosbox.vcproj +++ b/visualc_net/dosbox.vcproj @@ -436,6 +436,9 @@ + +