diff --git a/filesystem/VFileSystem009.cpp b/filesystem/VFileSystem009.cpp index 7bfee6ba..e9a3d54a 100644 --- a/filesystem/VFileSystem009.cpp +++ b/filesystem/VFileSystem009.cpp @@ -42,14 +42,14 @@ static inline qboolean IsIdGamedir( const char *id ) !Q_strcmp( id, "GAMEDOWNLOAD" ); } -static inline const char* IdToDir( const char *id ) +static inline const char *IdToDir( const char *id ) { if( !Q_strcmp( id, "GAME" )) return GI->gamefolder; else if( !Q_strcmp( id, "GAMEDOWNLOAD" )) return va( "%s/downloaded", GI->gamefolder ); else if( !Q_strcmp( id, "GAMECONFIG" )) - return fs_writedir; // full path here so it's totally our write allowed directory + return fs_writepath->filename; // full path here so it's totally our write allowed directory else if( !Q_strcmp( id, "PLATFORM" )) return "platform"; // stub else if( !Q_strcmp( id, "CONFIG" )) diff --git a/filesystem/dir.c b/filesystem/dir.c index 34b0fce7..4dd2fdf1 100644 --- a/filesystem/dir.c +++ b/filesystem/dir.c @@ -41,7 +41,7 @@ typedef struct dir_s struct dir_s *entries; // sorted } dir_t; -static int FS_SortDir( const void *_a, const void *_b ) +static int FS_SortDirEntries( const void *_a, const void *_b ) { const dir_t *a = _a; const dir_t *b = _b; @@ -65,25 +65,19 @@ static void FS_InitDirEntries( dir_t *dir, const stringlist_t *list ) { int i; - if( !list->numstrings ) - { - dir->numentries = DIRENTRY_EMPTY_DIRECTORY; - dir->entries = NULL; - return; - } - dir->numentries = list->numstrings; dir->entries = Mem_Malloc( fs_mempool, sizeof( dir_t ) * dir->numentries ); for( i = 0; i < list->numstrings; i++ ) { dir_t *entry = &dir->entries[i]; + Q_strncpy( entry->name, list->strings[i], sizeof( entry->name )); entry->numentries = DIRENTRY_NOT_SCANNED; entry->entries = NULL; } - qsort( dir->entries, dir->numentries, sizeof( dir->entries[0] ), FS_SortDir ); + qsort( dir->entries, dir->numentries, sizeof( dir->entries[0] ), FS_SortDirEntries ); } static void FS_PopulateDirEntries( dir_t *dir, const char *path ) @@ -102,8 +96,16 @@ static void FS_PopulateDirEntries( dir_t *dir, const char *path ) } stringlistinit( &list ); - listdirectory( &list, path, false ); - FS_InitDirEntries( dir, &list ); + listdirectory( &list, path ); + if( !list.numstrings ) + { + dir->numentries = DIRENTRY_EMPTY_DIRECTORY; + dir->entries = NULL; + } + else + { + FS_InitDirEntries( dir, &list ); + } stringlistfreecontents( &list ); #endif } @@ -112,12 +114,10 @@ static int FS_FindDirEntry( dir_t *dir, const char *name ) { int left, right; - if( dir->numentries < 0 ) - return -1; - // look for the file (binary search) left = 0; right = dir->numentries - 1; + while( left <= right ) { int middle = (left + right) / 2; @@ -142,30 +142,37 @@ static void FS_MergeDirEntries( dir_t *dir, const stringlist_t *list ) int i; dir_t temp; + // glorified realloc for sorted dir entries + // make new array and copy old entries with same name and subentries + // everything else get freed + FS_InitDirEntries( &temp, list ); - // copy all entries that has the same name and has subentries for( i = 0; i < dir->numentries; i++ ) { + dir_t *oldentry = &dir->entries[i]; + dir_t *newentry; int j; // don't care about directories without subentries - if( dir->entries == NULL ) + if( oldentry->entries == NULL ) continue; // try to find this directory in new tree - j = FS_FindDirEntry( &temp, dir->entries[i].name ); + j = FS_FindDirEntry( &temp, oldentry->name ); // not found, free memory if( j < 0 ) { - FS_FreeDirEntries( &dir->entries[i] ); + FS_FreeDirEntries( oldentry ); continue; } // found directory, move all entries - temp.entries[j].numentries = dir->entries[i].numentries; - temp.entries[j].entries = dir->entries[i].entries; + newentry = &temp.entries[j]; + + newentry->numentries = oldentry->numentries; + newentry->entries = oldentry->entries; } // now we can free old tree and replace it with temporary @@ -177,216 +184,141 @@ static void FS_MergeDirEntries( dir_t *dir, const stringlist_t *list ) static int FS_MaybeUpdateDirEntries( dir_t *dir, const char *path, const char *entryname ) { stringlist_t list; - qboolean update = false; - int idx; + int ret; stringlistinit( &list ); - listdirectory( &list, path, false ); + listdirectory( &list, path ); - // find the reason to update entries list - if( list.numstrings != dir->numentries ) + if( list.numstrings == 0 ) // empty directory { - // small optimization to not search string in the list - // and directly go updating entries - update = true; + FS_FreeDirEntries( dir ); + dir->numentries = DIRENTRY_EMPTY_DIRECTORY; + ret = -1; + } + else if( dir->numentries < 0 ) // not initialized or was empty + { + FS_InitDirEntries( dir, &list ); + ret = FS_FindDirEntry( dir, entryname ); + } + else if( list.numstrings != dir->numentries ) // quick update + { + FS_MergeDirEntries( dir, &list ); + ret = FS_FindDirEntry( dir, entryname ); } else { - for( idx = 0; idx < list.numstrings; idx++ ) + // do heavy compare if directory now have an entry we need + int i; + + for( i = 0; i < list.numstrings; i++ ) { - if( !Q_stricmp( list.strings[idx], entryname )) - { - update = true; + if( !Q_stricmp( list.strings[i], entryname )) break; - } } - } - if( !update ) - { - stringlistfreecontents( &list ); - return -1; + if( i != list.numstrings ) + { + FS_MergeDirEntries( dir, &list ); + ret = FS_FindDirEntry( dir, entryname ); + } + else ret = -1; } - FS_MergeDirEntries( dir, &list ); stringlistfreecontents( &list ); - return FS_FindDirEntry( dir, entryname ); + return ret; } -#if 1 -qboolean FS_FixFileCase( dir_t *dir, const char *path, char *dst, size_t len, qboolean createpath ) +static inline qboolean FS_AppendToPath( char *dst, size_t *pi, const size_t len, const char *src, const char *path, const char *err ) { - const char *prev = path; - const char *next = Q_strchrnul( prev, PATH_SEPARATOR ); - size_t i = Q_strlen( dst ); // dst is expected to have searchpath filename + size_t i = *pi; - while( true ) + i += Q_strncpy( &dst[i], src, len - i ); + *pi = i; + + if( i >= len ) { + Con_Printf( S_ERROR "FS_FixFileCase: overflow while searching %s (%s)\n", path, err ); + return false; + } + return true; +} + +qboolean FS_FixFileCase( dir_t *dir, const char *path, char *dst, const size_t len, qboolean createpath ) +{ + const char *prev, *next; + size_t i = 0; + + if( !FS_AppendToPath( dst, &i, len, dir->name, path, "init" )) + return false; + + for( prev = path, next = Q_strchrnul( prev, PATH_SEPARATOR ); + ; + prev = next + 1, next = Q_strchrnul( prev, PATH_SEPARATOR )) + { + qboolean uptodate = false; // do not run second scan if we're just updated our directory list + size_t temp; char entryname[MAX_SYSPATH]; int ret; // this subdirectory is case insensitive, just slam everything that's left if( dir->numentries == DIRENTRY_CASEINSENSITIVE ) { - i += Q_strncpy( &dst[i], prev, len - i ); - if( i >= len ) - { - Con_Printf( "%s: overflow while searching %s (caseinsensitive entry)\n", __FUNCTION__, path ); + if( !FS_AppendToPath( dst, &i, len, prev, path, "caseinsensitive entry" )) return false; - } break; } - // populate cache if needed if( dir->numentries == DIRENTRY_NOT_SCANNED ) + { + // read directory first time FS_PopulateDirEntries( dir, dst ); + uptodate = true; + } // get our entry name Q_strncpy( entryname, prev, next - prev + 1 ); - ret = FS_FindDirEntry( dir, entryname ); // didn't found, but does it exists in FS? - if( ret < 0 ) + if(( ret = FS_FindDirEntry( dir, entryname )) < 0 ) { - ret = FS_MaybeUpdateDirEntries( dir, dst, entryname ); + // if we're creating files or folders, we don't care if path doesn't exist + // so copy everything that's left and exit without an error + if( uptodate || ( ret = FS_MaybeUpdateDirEntries( dir, dst, entryname )) < 0 ) + return createpath ? FS_AppendToPath( dst, &i, len, prev, path, "create path" ) : false; - if( ret < 0 ) - { - // if we're creating files or folders, we don't care if path doesn't exist - // so copy everything that's left and exit without an error - if( createpath ) - { - i += Q_strncpy( &dst[i], prev, len - i ); - if( i >= len ) - { - Con_Printf( "%s: overflow while searching %s (create path)\n", __FUNCTION__, path ); - return false; - } - - return true; - } - return false; - } + uptodate = true; } dir = &dir->entries[ret]; - ret = Q_strncpy( &dst[i], dir->name, len - i ); - - // file not found, rescan... - if( !FS_SysFileOrFolderExists( dst )) - { - // strip failed part - dst[i] = 0; - - ret = FS_MaybeUpdateDirEntries( dir, dst, entryname ); - - // file not found, exit... =/ - if( ret < 0 ) - { - // if we're creating files or folders, we don't care if path doesn't exist - // so copy everything that's left and exit without an error - if( createpath ) - { - i += Q_strncpy( &dst[i], prev, len - i ); - if( i >= len ) - { - Con_Printf( "%s: overflow while searching %s (create path 2)\n", __FUNCTION__, path ); - return false; - } - - return true; - } - return false; - } - - dir = &dir->entries[ret]; - ret = Q_strncpy( &dst[i], dir->name, len - i ); - } - - i += ret; - if( i >= len ) // overflow! - { - Con_Printf( "%s: overflow while searching %s (appending fixed file name)\n", __FUNCTION__, path ); + temp = i; + if( !FS_AppendToPath( dst, &temp, len, dir->name, path, "case fix" )) return false; - } - - // end of string, found file, return - if( next[0] == '\0' ) - break; - // move pointer one character forward, find next path split character - prev = next + 1; - next = Q_strchrnul( prev, PATH_SEPARATOR ); - i += Q_strncpy( &dst[i], PATH_SEPARATOR_STR, len - i ); - if( i >= len ) // overflow! + if( !uptodate && !FS_SysFileOrFolderExists( dst )) // file not found, rescan... { - Con_Printf( "%s: overflow while searching %s (path separator)\n", __FUNCTION__, path ); - return false; - } - } - - return true; -} -#else -qboolean FS_FixFileCase( dir_t *dir, const char *path, char *dst, size_t len, qboolean createpath ) -{ - const char *prev = path; - const char *next = Q_strchrnul( prev, PATH_SEPARATOR ); - size_t i = Q_strlen( dst ); // dst is expected to have searchpath filename - - while( true ) - { - stringlist_t list; - char entryname[MAX_SYSPATH]; - int idx; - - // get our entry name - Q_strncpy( entryname, prev, next - prev + 1 ); + dst[i] = 0; // strip failed part - stringlistinit( &list ); - listdirectory( &list, dst, false ); + // if we're creating files or folders, we don't care if path doesn't exist + // so copy everything that's left and exit without an error + if(( ret = FS_MaybeUpdateDirEntries( dir, dst, entryname )) < 0 ) + return createpath ? FS_AppendToPath( dst, &i, len, prev, path, "create path rescan" ) : false; - for( idx = 0; idx < list.numstrings; idx++ ) - { - if( !Q_stricmp( list.strings[idx], entryname )) - break; - } - - if( idx != list.numstrings ) - { - i += Q_strncpy( &dst[i], list.strings[idx], len - i ); - if( i >= len ) // overflow! - { - Con_Printf( "%s: overflow while searching %s (appending fixed file name)\n", __FUNCTION__, path ); + dir = &dir->entries[ret]; + if( !FS_AppendToPath( dst, &temp, len, dir->name, path, "case fix rescan" )) return false; - } - } - else - { - stringlistfreecontents( &list ); - return false; } - - stringlistfreecontents( &list ); + i = temp; // end of string, found file, return - if( next[0] == '\0' ) + if( next[0] == '\0' || ( next[0] == PATH_SEPARATOR && next[1] == '\0' )) break; - // move pointer one character forward, find next path split character - prev = next + 1; - next = Q_strchrnul( prev, PATH_SEPARATOR ); - i += Q_strncpy( &dst[i], PATH_SEPARATOR_STR, len - i ); - if( i >= len ) // overflow! - { - Con_Printf( "%s: overflow while searching %s (path separator)\n", __FUNCTION__, path ); + if( !FS_AppendToPath( dst, &i, len, PATH_SEPARATOR_STR, path, "path separator" )) return false; - } } return true; } -#endif static void FS_Close_DIR( searchpath_t *search ) { @@ -403,7 +335,6 @@ static int FS_FindFile_DIR( searchpath_t *search, const char *path, char *fixedn { char netpath[MAX_SYSPATH]; - Q_strncpy( netpath, search->filename, sizeof( netpath )); if( !FS_FixFileCase( search->dir, path, netpath, sizeof( netpath ), false )) return -1; @@ -438,12 +369,16 @@ static void FS_Search_DIR( searchpath_t *search, stringlist_t *list, const char if( basepathlength ) memcpy( basepath, pattern, basepathlength ); basepath[basepathlength] = '\0'; - Q_snprintf( netpath, sizeof( netpath ), "%s%s", search->filename, basepath ); + if( !FS_FixFileCase( search->dir, basepath, netpath, sizeof( netpath ), false )) + { + Mem_Free( basepath ); + return; + } stringlistinit( &dirlist ); - listdirectory( &dirlist, netpath, caseinsensitive ); + listdirectory( &dirlist, netpath ); - Q_strncpy( temp, basepath, sizeof( temp ) ); + Q_strncpy( temp, basepath, sizeof( temp )); for( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ ) { @@ -500,7 +435,7 @@ void FS_InitDirectorySearchpath( searchpath_t *search, const char *path, int fla // create cache root search->dir = Mem_Malloc( fs_mempool, sizeof( dir_t )); - search->dir->name[0] = 0; // root has no filename, unused + Q_strncpy( search->dir->name, search->filename, sizeof( search->dir->name )); FS_PopulateDirEntries( search->dir, path ); } diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index c3795909..a7b22415 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -50,7 +50,6 @@ poolhandle_t fs_mempool; searchpath_t *fs_searchpaths = NULL; // chain char fs_rodir[MAX_SYSPATH]; char fs_rootdir[MAX_SYSPATH]; -char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) searchpath_t *fs_writepath; static char fs_basedir[MAX_SYSPATH]; // base game directory @@ -193,10 +192,8 @@ static void listlowercase( stringlist_t *list ) } } -void listdirectory( stringlist_t *list, const char *path, qboolean lowercase ) +void listdirectory( stringlist_t *list, const char *path ) { - int i; - signed char *c; #if XASH_WIN32 char pattern[4096]; struct _finddata_t n_file; @@ -307,7 +304,7 @@ static qboolean FS_AddArchive_Fullpath( const char *file, qboolean *already_load ================ FS_AddGameDirectory -Sets fs_writedir, adds the directory to the head of the path, +Sets fs_writepath, adds the directory to the head of the path, then loads and adds pak1.pak pak2.pak ... ================ */ @@ -319,7 +316,7 @@ void FS_AddGameDirectory( const char *dir, uint flags ) int i; stringlistinit( &list ); - listdirectory( &list, dir, false ); + listdirectory( &list, dir ); stringlistsort( &list ); // add any PAK package in the directory @@ -351,10 +348,7 @@ void FS_AddGameDirectory( const char *dir, uint flags ) // (unpacked files have the priority over packed files) search = FS_AddDir_Fullpath( dir, NULL, flags ); if( !FBitSet( flags, FS_NOWRITE_PATH )) - { - Q_strncpy( fs_writedir, dir, sizeof( fs_writedir )); fs_writepath = search; - } } /* @@ -1318,7 +1312,7 @@ qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char } stringlistinit( &dirs ); - listdirectory( &dirs, fs_rodir, false ); + listdirectory( &dirs, fs_rodir ); stringlistsort( &dirs ); for( i = 0; i < dirs.numstrings; i++ ) @@ -1342,7 +1336,7 @@ qboolean FS_InitStdio( qboolean caseinsensitive, const char *rootdir, const char // validate directories stringlistinit( &dirs ); - listdirectory( &dirs, "./", false ); + listdirectory( &dirs, "./" ); stringlistsort( &dirs ); for( i = 0; i < dirs.numstrings; i++ ) @@ -1794,8 +1788,11 @@ file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ) char real_path[MAX_SYSPATH]; // open the file on disk directly - Q_sprintf( real_path, "%s/%s", fs_writedir, filepath ); + if( !FS_FixFileCase( fs_writepath->dir, filepath, real_path, sizeof( real_path ), true )) + return NULL; + FS_CreatePath( real_path ); // Create directories up to the file + return FS_SysOpen( real_path, mode ); } @@ -2455,25 +2452,40 @@ rename specified file from gamefolder */ qboolean FS_Rename( const char *oldname, const char *newname ) { - char oldpath[MAX_SYSPATH], newpath[MAX_SYSPATH]; - qboolean iRet; + char oldname2[MAX_SYSPATH], newname2[MAX_SYSPATH], oldpath[MAX_SYSPATH], newpath[MAX_SYSPATH]; + int ret; - if( !oldname || !newname || !*oldname || !*newname ) + if( !COM_CheckString( oldname ) || !COM_CheckString( newname )) return false; // no work done if( !Q_stricmp( oldname, newname )) return true; - Q_snprintf( oldpath, sizeof( oldpath ), "%s%s", fs_writedir, oldname ); - Q_snprintf( newpath, sizeof( newpath ), "%s%s", fs_writedir, newname ); + // fix up slashes + Q_strncpy( oldname2, oldname, sizeof( oldname2 )); + Q_strncpy( newname2, newname, sizeof( newname2 )); - COM_FixSlashes( oldpath ); - COM_FixSlashes( newpath ); + COM_FixSlashes( oldname2 ); + COM_FixSlashes( newname2 ); - iRet = rename( oldpath, newpath ); + // file does not exist + if( !FS_FixFileCase( fs_writepath->dir, oldname2, oldpath, sizeof( oldpath ), false )) + return false; + + // exit if overflowed + if( !FS_FixFileCase( fs_writepath->dir, newname2, newpath, sizeof( newpath ), true )) + return false; + + ret = rename( oldpath, newpath ); + if( ret < 0 ) + { + Con_Printf( "%s: failed to rename file %s (%s) to %s (%s): %s\n", + __FUNCTION__, oldpath, oldname2, newpath, newname2, strerror( errno )); + return false; + } - return (iRet == 0); + return true; } /* @@ -2485,17 +2497,26 @@ delete specified file from gamefolder */ qboolean GAME_EXPORT FS_Delete( const char *path ) { - char real_path[MAX_SYSPATH]; - qboolean iRet; + char path2[MAX_SYSPATH], real_path[MAX_SYSPATH]; + int ret; - if( !path || !*path ) + if( !COM_CheckString( path )) return false; - Q_snprintf( real_path, sizeof( real_path ), "%s%s", fs_writedir, path ); - COM_FixSlashes( real_path ); - iRet = remove( real_path ); + Q_strncpy( path2, path, sizeof( path2 )); + COM_FixSlashes( path2 ); - return (iRet == 0); + if( !FS_FixFileCase( fs_writepath->dir, path2, real_path, sizeof( real_path ), true )) + return true; + + ret = remove( real_path ); + if( ret < 0 ) + { + Con_Printf( "%s: failed to delete file %s (%s): %s\n", __FUNCTION__, real_path, path, strerror( errno )); + return false; + } + + return true; } /* @@ -2556,7 +2577,7 @@ search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) { if( gamedironly && !FBitSet( searchpath->flags, FS_GAMEDIRONLY_SEARCH_FLAGS )) continue; - + searchpath->pfnSearch( searchpath, &resultlist, pattern, caseinsensitive ); } diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h index 66e0886d..fb586ebb 100644 --- a/filesystem/filesystem_internal.h +++ b/filesystem/filesystem_internal.h @@ -70,7 +70,7 @@ typedef struct searchpath_s string filename; int type; int flags; - + union { dir_t *dir; @@ -91,12 +91,12 @@ typedef struct searchpath_s extern fs_globals_t FI; extern searchpath_t *fs_searchpaths; +extern searchpath_t *fs_writepath; extern poolhandle_t fs_mempool; extern fs_interface_t g_engfuncs; extern qboolean fs_ext_path; extern char fs_rodir[MAX_SYSPATH]; extern char fs_rootdir[MAX_SYSPATH]; -extern char fs_writedir[MAX_SYSPATH]; extern fs_api_t g_api; #define GI FI.GameInfo @@ -164,7 +164,7 @@ void stringlistinit( stringlist_t *list ); void stringlistfreecontents( stringlist_t *list ); void stringlistappend( stringlist_t *list, char *text ); void stringlistsort( stringlist_t *list ); -void listdirectory( stringlist_t *list, const char *path, qboolean lowercase ); +void listdirectory( stringlist_t *list, const char *path ); // filesystem ops int FS_FileExists( const char *filename, int gamedironly ); @@ -212,6 +212,7 @@ qboolean FS_AddZip_Fullpath( const char *zipfile, qboolean *already_loaded, int // dir.c // searchpath_t *FS_AddDir_Fullpath( const char *path, qboolean *already_loaded, int flags ); +qboolean FS_FixFileCase( dir_t *dir, const char *path, char *dst, const size_t len, qboolean createpath ); void FS_InitDirectorySearchpath( searchpath_t *search, const char *path, int flags ); #ifdef __cplusplus