From 202bfa1155885fff4a5134423ca8b6b0b30769f5 Mon Sep 17 00:00:00 2001 From: Peter Veenstra Date: Mon, 25 Mar 2019 13:49:25 +0000 Subject: [PATCH] New Drive type: overlay. When active, this drive redirects new and changed files to a different location. The files in the overlay and normal drive are merged on startup and kept up to date when the game changes something. Files in the overlay are priotizedi, if they exists, above the normal files. The drive will switch to an overlayed version of the file on the first write! (Not when opening the file in write-mode). The overlay is capable of creating missing directories. With the help of DBOVERLAY files, the drive keeps track of files that are present in the normal directory but deleted by the game. All changes are preserved between sessions. Current 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. Thanks for the help GOG. Imported-from: https://svn.code.sf.net/p/dosbox/code-0/dosbox/trunk@4196 --- include/dos_system.h | 2 +- src/dos/Makefile.am | 2 +- src/dos/dos_programs.cpp | 43 +- src/dos/drive_overlay.cpp | 1039 +++++++++++++++++++++++++++++++++++++ src/dos/drives.h | 54 +- visualc_net/dosbox.vcproj | 3 + 6 files changed, 1134 insertions(+), 9 deletions(-) create mode 100644 src/dos/drive_overlay.cpp 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 @@ + +