From 420b91db29485df39fd6e724e782c449158811cb Mon Sep 17 00:00:00 2001 From: James Cook Date: Tue, 2 Jan 2007 08:33:20 +0000 Subject: Print done when done. --- indra/llvfs/lldir.cpp | 461 ++++++++++ indra/llvfs/lldir.h | 102 +++ indra/llvfs/lldir_linux.cpp | 329 +++++++ indra/llvfs/lldir_linux.h | 41 + indra/llvfs/lldir_mac.cpp | 362 ++++++++ indra/llvfs/lldir_mac.h | 40 + indra/llvfs/lldir_win32.cpp | 382 ++++++++ indra/llvfs/lldir_win32.h | 37 + indra/llvfs/lllfsthread.cpp | 308 +++++++ indra/llvfs/lllfsthread.h | 119 +++ indra/llvfs/llvfile.cpp | 415 +++++++++ indra/llvfs/llvfile.h | 75 ++ indra/llvfs/llvfs.cpp | 2048 +++++++++++++++++++++++++++++++++++++++++++ indra/llvfs/llvfs.h | 151 ++++ indra/llvfs/llvfsthread.cpp | 294 +++++++ indra/llvfs/llvfsthread.h | 125 +++ 16 files changed, 5289 insertions(+) create mode 100644 indra/llvfs/lldir.cpp create mode 100644 indra/llvfs/lldir.h create mode 100644 indra/llvfs/lldir_linux.cpp create mode 100644 indra/llvfs/lldir_linux.h create mode 100644 indra/llvfs/lldir_mac.cpp create mode 100644 indra/llvfs/lldir_mac.h create mode 100644 indra/llvfs/lldir_win32.cpp create mode 100644 indra/llvfs/lldir_win32.h create mode 100644 indra/llvfs/lllfsthread.cpp create mode 100644 indra/llvfs/lllfsthread.h create mode 100644 indra/llvfs/llvfile.cpp create mode 100644 indra/llvfs/llvfile.h create mode 100644 indra/llvfs/llvfs.cpp create mode 100644 indra/llvfs/llvfs.h create mode 100644 indra/llvfs/llvfsthread.cpp create mode 100644 indra/llvfs/llvfsthread.h (limited to 'indra/llvfs') diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp new file mode 100644 index 0000000000..3c82b28c74 --- /dev/null +++ b/indra/llvfs/lldir.cpp @@ -0,0 +1,461 @@ +/** + * @file lldir.cpp + * @brief implementation of directory utilities base class + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#if !LL_WINDOWS +#include +#include +#else +#include +#endif + +#include "lldir.h" +#include "llerror.h" +#include "lluuid.h" + +#if LL_WINDOWS +#include "lldir_win32.h" +LLDir_Win32 gDirUtil; +#elif LL_DARWIN +#include "lldir_mac.h" +LLDir_Mac gDirUtil; +#else +#include "lldir_linux.h" +LLDir_Linux gDirUtil; +#endif + +LLDir *gDirUtilp = (LLDir *)&gDirUtil; + +LLDir::LLDir() +: mAppName(""), + mExecutablePathAndName(""), + mExecutableFilename(""), + mExecutableDir(""), + mAppRODataDir(""), + mOSUserDir(""), + mOSUserAppDir(""), + mLindenUserDir(""), + mCAFile(""), + mTempDir(""), + mDirDelimiter("") +{ +} + +LLDir::~LLDir() +{ +} + + +S32 LLDir::deleteFilesInDir(const std::string &dirname, const std::string &mask) +{ + S32 count = 0; + std::string filename; + std::string fullpath; + S32 result; + while (getNextFileInDir(dirname, mask, filename, FALSE)) + { + if ((filename == ".") || (filename == "..")) + { + // skipping directory traversal filenames + count++; + continue; + } + fullpath = dirname; + fullpath += getDirDelimiter(); + fullpath += filename; + + S32 retry_count = 0; + while (retry_count < 5) + { + if (0 != LLFile::remove(fullpath.c_str())) + { + result = errno; + llwarns << "Problem removing " << fullpath << " - errorcode: " + << result << " attempt " << retry_count << llendl; + ms_sleep(1000); + } + else + { + if (retry_count) + { + llwarns << "Successfully removed " << fullpath << llendl; + } + break; + } + retry_count++; + } + count++; + } + return count; +} + +const std::string LLDir::findFile(const std::string &filename, + const std::string searchPath1, + const std::string searchPath2, + const std::string searchPath3) +{ + std::vector search_paths; + search_paths.push_back(searchPath1); + search_paths.push_back(searchPath2); + search_paths.push_back(searchPath3); + + std::vector::iterator search_path_iter; + for (search_path_iter = search_paths.begin(); + search_path_iter != search_paths.end(); + ++search_path_iter) + { + if (!search_path_iter->empty()) + { + std::string filename_and_path = (*search_path_iter) + getDirDelimiter() + filename; + if (fileExists(filename_and_path)) + { + return filename_and_path; + } + } + } + return ""; +} + + +const std::string &LLDir::getExecutablePathAndName() const +{ + return mExecutablePathAndName; +} + +const std::string &LLDir::getExecutableFilename() const +{ + return mExecutableFilename; +} + +const std::string &LLDir::getExecutableDir() const +{ + return mExecutableDir; +} + +const std::string &LLDir::getWorkingDir() const +{ + return mWorkingDir; +} + +const std::string &LLDir::getAppName() const +{ + return mAppName; +} + +const std::string &LLDir::getAppRODataDir() const +{ + return mAppRODataDir; +} + +const std::string &LLDir::getOSUserDir() const +{ + return mOSUserDir; +} + +const std::string &LLDir::getOSUserAppDir() const +{ + return mOSUserAppDir; +} + +const std::string &LLDir::getLindenUserDir() const +{ + return mLindenUserDir; +} + +const std::string &LLDir::getChatLogsDir() const +{ + return mChatLogsDir; +} + +const std::string &LLDir::getPerAccountChatLogsDir() const +{ + return mPerAccountChatLogsDir; +} + +const std::string &LLDir::getTempDir() const +{ + return mTempDir; +} + +const std::string &LLDir::getCAFile() const +{ + return mCAFile; +} + +const std::string &LLDir::getDirDelimiter() const +{ + return mDirDelimiter; +} + +const std::string &LLDir::getSkinDir() const +{ + return mSkinDir; +} + +std::string LLDir::getExpandedFilename(ELLPath location, const std::string &filename) const +{ + std::string prefix; + switch (location) + { + case LL_PATH_NONE: + // Do nothing + break; + + case LL_PATH_APP_SETTINGS: + prefix = getAppRODataDir(); + prefix += mDirDelimiter; + prefix += "app_settings"; + break; + + case LL_PATH_CHARACTER: + prefix = getAppRODataDir(); + prefix += mDirDelimiter; + prefix += "character"; + break; + + case LL_PATH_MOTIONS: + prefix = getAppRODataDir(); + prefix += mDirDelimiter; + prefix += "motions"; + break; + + case LL_PATH_HELP: + prefix = "help"; + break; + + case LL_PATH_CACHE: + if (getOSUserAppDir().empty()) + { + prefix = "data"; + } + else + { + prefix = getOSUserAppDir(); + prefix += mDirDelimiter; + prefix += "cache"; + } + break; + + case LL_PATH_USER_SETTINGS: + prefix = getOSUserAppDir(); + prefix += mDirDelimiter; + prefix += "user_settings"; + break; + + case LL_PATH_PER_SL_ACCOUNT: + prefix = getLindenUserDir(); + break; + + case LL_PATH_CHAT_LOGS: + prefix = getChatLogsDir(); + break; + + case LL_PATH_PER_ACCOUNT_CHAT_LOGS: + prefix = getPerAccountChatLogsDir(); + break; + + case LL_PATH_LOGS: + prefix = getOSUserAppDir(); + prefix += mDirDelimiter; + prefix += "logs"; + break; + + case LL_PATH_TEMP: + prefix = getTempDir(); + break; + + case LL_PATH_TOP_SKIN: + prefix = getSkinDir(); + break; + + case LL_PATH_SKINS: + prefix = getAppRODataDir(); + prefix += mDirDelimiter; + prefix += "skins"; + break; + + case LL_PATH_MOZILLA_PROFILE: + prefix = getOSUserAppDir(); + prefix += mDirDelimiter; + prefix += "browser_profile"; + break; + + default: + llassert(0); + } + + std::string expanded_filename; + if (!filename.empty()) + { + if (!prefix.empty()) + { + expanded_filename += prefix; + expanded_filename += mDirDelimiter; + expanded_filename += filename; + } + else + { + expanded_filename = filename; + } + } + else + if (!prefix.empty()) + { + // Directory only, no file name. + expanded_filename = prefix; + } + else + { + expanded_filename.assign(""); + } + + //llinfos << "*** EXPANDED FILENAME: <" << mExpandedFilename << ">" << llendl; + + return expanded_filename; +} + +std::string LLDir::getTempFilename() const +{ + LLUUID random_uuid; + char uuid_str[64]; + + random_uuid.generate(); + random_uuid.toString(uuid_str); + + std::string temp_filename = getTempDir(); + temp_filename += mDirDelimiter; + temp_filename += uuid_str; + temp_filename += ".tmp"; + + return temp_filename; +} + +void LLDir::setLindenUserDir(const std::string &first, const std::string &last) +{ + // if both first and last aren't set, assume we're grabbing the cached dir + if (!first.empty() && !last.empty()) + { + // some platforms have case-sensitive filesystems, so be + // utterly consistent with our firstname/lastname case. + LLString firstlower(first); + LLString::toLower(firstlower); + LLString lastlower(last); + LLString::toLower(lastlower); + mLindenUserDir = getOSUserAppDir(); + mLindenUserDir += mDirDelimiter; + mLindenUserDir += firstlower.c_str(); + mLindenUserDir += "_"; + mLindenUserDir += lastlower.c_str(); + } + else + { + llerrs << "Invalid name for LLDir::setLindenUserDir" << llendl; + } + + dumpCurrentDirectories(); +} + +void LLDir::setChatLogsDir(const std::string &path) +{ + if (!path.empty() ) + { + mChatLogsDir = path; + } + else + { + llwarns << "Invalid name for LLDir::setChatLogsDir" << llendl; + } +} + +void LLDir::setPerAccountChatLogsDir(const std::string &first, const std::string &last) +{ + // if both first and last aren't set, assume we're grabbing the cached dir + if (!first.empty() && !last.empty()) + { + // some platforms have case-sensitive filesystems, so be + // utterly consistent with our firstname/lastname case. + LLString firstlower(first); + LLString::toLower(firstlower); + LLString lastlower(last); + LLString::toLower(lastlower); + mPerAccountChatLogsDir = getChatLogsDir(); + mPerAccountChatLogsDir += mDirDelimiter; + mPerAccountChatLogsDir += firstlower.c_str(); + mPerAccountChatLogsDir += "_"; + mPerAccountChatLogsDir += lastlower.c_str(); + } + else + { + llwarns << "Invalid name for LLDir::setPerAccountChatLogsDir" << llendl; + } +} + +void LLDir::setSkinFolder(const std::string &skin_folder) +{ + mSkinDir = getAppRODataDir(); + mSkinDir += mDirDelimiter; + mSkinDir += "skins"; + mSkinDir += mDirDelimiter; + mSkinDir += skin_folder; +} + +void LLDir::dumpCurrentDirectories() +{ + llinfos << "Current Directories:" << llendl; + + llinfos << " CurPath: " << getCurPath() << llendl; + llinfos << " AppName: " << getAppName() << llendl; + llinfos << " ExecutableFilename: " << getExecutableFilename() << llendl; + llinfos << " ExecutableDir: " << getExecutableDir() << llendl; + llinfos << " ExecutablePathAndName: " << getExecutablePathAndName() << llendl; + llinfos << " WorkingDir: " << getWorkingDir() << llendl; + llinfos << " AppRODataDir: " << getAppRODataDir() << llendl; + llinfos << " OSUserDir: " << getOSUserDir() << llendl; + llinfos << " OSUserAppDir: " << getOSUserAppDir() << llendl; + llinfos << " LindenUserDir: " << getLindenUserDir() << llendl; + llinfos << " TempDir: " << getTempDir() << llendl; + llinfos << " CAFile: " << getCAFile() << llendl; + llinfos << " SkinDir: " << getSkinDir() << llendl; +} + + +void dir_exists_or_crash(const std::string &dir_name) +{ +#if LL_WINDOWS + // *FIX: lame - it doesn't do the same thing on windows. not so + // important since we don't deploy simulator to windows boxes. + LLFile::mkdir(dir_name.c_str(), 0700); +#else + struct stat dir_stat; + if(0 != LLFile::stat(dir_name.c_str(), &dir_stat)) + { + S32 stat_rv = errno; + if(ENOENT == stat_rv) + { + if(0 != LLFile::mkdir(dir_name.c_str(), 0700)) // octal + { + llerrs << "Unable to create directory: " << dir_name << llendl; + } + } + else + { + llerrs << "Unable to stat: " << dir_name << " errno = " << stat_rv + << llendl; + } + } + else + { + // data_dir exists, make sure it's a directory. + if(!S_ISDIR(dir_stat.st_mode)) + { + llerrs << "Data directory collision: " << dir_name << llendl; + } + } +#endif +} diff --git a/indra/llvfs/lldir.h b/indra/llvfs/lldir.h new file mode 100644 index 0000000000..710dcd1ae3 --- /dev/null +++ b/indra/llvfs/lldir.h @@ -0,0 +1,102 @@ +/** + * @file lldir.h + * @brief Definition of directory utilities class + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDIR_H +#define LL_LLDIR_H + +typedef enum ELLPath +{ + LL_PATH_NONE = 0, + LL_PATH_USER_SETTINGS = 1, + LL_PATH_APP_SETTINGS = 2, + LL_PATH_PER_SL_ACCOUNT = 3, + LL_PATH_CACHE = 4, + LL_PATH_CHARACTER = 5, + LL_PATH_MOTIONS = 6, + LL_PATH_HELP = 7, + LL_PATH_LOGS = 8, + LL_PATH_TEMP = 9, + LL_PATH_SKINS = 10, + LL_PATH_TOP_SKIN = 11, + LL_PATH_CHAT_LOGS = 12, + LL_PATH_PER_ACCOUNT_CHAT_LOGS = 13, + LL_PATH_MOZILLA_PROFILE = 14, + LL_PATH_COUNT = 15 +} ELLPath; + + +class LLDir +{ + public: + LLDir(); + virtual ~LLDir(); + + virtual void initAppDirs(const std::string &app_name) = 0; + public: + virtual S32 deleteFilesInDir(const std::string &dirname, const std::string &mask); + +// pure virtual functions + virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask) = 0; + virtual BOOL getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap) = 0; + virtual void getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname) = 0; + virtual std::string getCurPath() = 0; + virtual BOOL fileExists(const std::string &filename) = 0; + + const std::string findFile(const std::string &filename, const std::string searchPath1 = "", const std::string searchPath2 = "", const std::string searchPath3 = ""); + const std::string &getExecutablePathAndName() const; // Full pathname of the executable + const std::string &getAppName() const; // install directory under progams/ ie "SecondLife" + const std::string &getExecutableDir() const; // Directory where the executable is located + const std::string &getExecutableFilename() const;// Filename of .exe + const std::string &getWorkingDir() const; // Current working directory + const std::string &getAppRODataDir() const; // Location of read-only data files + const std::string &getOSUserDir() const; // Location of the os-specific user dir + const std::string &getOSUserAppDir() const; // Location of the os-specific user app dir + const std::string &getLindenUserDir() const; // Location of the Linden user dir. + const std::string &getChatLogsDir() const; // Location of the chat logs dir. + const std::string &getPerAccountChatLogsDir() const; // Location of the per account chat logs dir. + const std::string &getTempDir() const; // Common temporary directory + const std::string &getCAFile() const; // File containing TLS certificate authorities + const std::string &getDirDelimiter() const; // directory separator for platform (ie. '\' or '/' or ':') + const std::string &getSkinDir() const; // User-specified skin folder. + + // Expanded filename + std::string getExpandedFilename(ELLPath location, const std::string &filename) const; + + // random filename in common temporary directory + std::string getTempFilename() const; + + virtual void setChatLogsDir(const std::string &path); // Set the chat logs dir to this user's dir + virtual void setPerAccountChatLogsDir(const std::string &first, const std::string &last); // Set the per user chat log directory. + virtual void setLindenUserDir(const std::string &first, const std::string &last); // Set the linden user dir to this user's dir + virtual void setSkinFolder(const std::string &skin_folder); + + virtual void dumpCurrentDirectories(); + +protected: + std::string mAppName; // install directory under progams/ ie "SecondLife" + std::string mExecutablePathAndName; // full path + Filename of .exe + std::string mExecutableFilename; // Filename of .exe + std::string mExecutableDir; // Location of executable + std::string mWorkingDir; // Current working directory + std::string mAppRODataDir; // Location for static app data + std::string mOSUserDir; // OS Specific user directory + std::string mOSUserAppDir; // OS Specific user app directory + std::string mLindenUserDir; // Location for Linden user-specific data + std::string mPerAccountChatLogsDir; // Location for chat logs. + std::string mChatLogsDir; // Location for chat logs. + std::string mCAFile; // Location of the TLS certificate authority PEM file. + std::string mTempDir; + std::string mDirDelimiter; + std::string mSkinDir; // Location for u ser-specified skin info. +}; + +void dir_exists_or_crash(const std::string &dir_name); + +extern LLDir *gDirUtilp; + +#endif // LL_LLDIR_H diff --git a/indra/llvfs/lldir_linux.cpp b/indra/llvfs/lldir_linux.cpp new file mode 100644 index 0000000000..6e50f9f239 --- /dev/null +++ b/indra/llvfs/lldir_linux.cpp @@ -0,0 +1,329 @@ +/** + * @file lldir_linux.cpp + * @brief Implementation of directory utilities for linux + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "lldir_linux.h" +#include "llerror.h" +#include "llrand.h" // for gLindenLabRandomNumber +#include +#include +#include +#include +#include + + +static std::string getCurrentUserHome(char* fallback) +{ + const uid_t uid = getuid(); + struct passwd *pw; + char *result_cstr = fallback; + + pw = getpwuid(uid); + if ((pw != NULL) && (pw->pw_dir != NULL)) + { + result_cstr = (char*) pw->pw_dir; + } + else + { + llinfos << "Couldn't detect home directory from passwd - trying $HOME" << llendl; + const char *const home_env = getenv("HOME"); + if (home_env) + { + result_cstr = (char*) home_env; + } + else + { + llwarns << "Couldn't detect home directory! Falling back to " << fallback << llendl; + } + } + + return std::string(result_cstr); +} + + +LLDir_Linux::LLDir_Linux() +{ + mDirDelimiter = "/"; + mCurrentDirIndex = -1; + mCurrentDirCount = -1; + mDirp = NULL; + + char tmp_str[LL_MAX_PATH]; + getcwd(tmp_str, LL_MAX_PATH); + + mExecutableFilename = ""; + mExecutablePathAndName = ""; + mExecutableDir = tmp_str; + mWorkingDir = tmp_str; + mAppRODataDir = tmp_str; + mOSUserDir = getCurrentUserHome(tmp_str); + mOSUserAppDir = ""; + mLindenUserDir = tmp_str; + + char path [32]; + + // !!! FIXME: /proc/%d/exe doesn't work on FreeBSD. + sprintf (path, "/proc/%d/exe", (int) getpid ()); + int rc = readlink (path, tmp_str, sizeof (tmp_str)-1); + if ( (rc != -1) && (rc <= ((int) sizeof (tmp_str)-1)) ) + { + tmp_str[rc] = '\0'; //readlink() doesn't 0-terminate the buffer + mExecutablePathAndName = tmp_str; + char *path_end; + if ((path_end = strrchr(tmp_str,'/'))) + { + *path_end = '\0'; + mExecutableDir = tmp_str; + mWorkingDir = tmp_str; + mExecutableFilename = path_end+1; + } + else + { + mExecutableFilename = tmp_str; + } + } + + // !!! FIXME: don't use /tmp, use $HOME/.secondlife/tmp or something. + mTempDir = "/tmp"; +} + +LLDir_Linux::~LLDir_Linux() +{ +} + +// Implementation + + +void LLDir_Linux::initAppDirs(const std::string &app_name) +{ + mAppName = app_name; + + LLString upper_app_name(app_name); + LLString::toUpper(upper_app_name); + + char* app_home_env = getenv((upper_app_name + "_USER_DIR").c_str()); + if (app_home_env) + { + // user has specified own userappdir i.e. $SECONDLIFE_USER_DIR + mOSUserAppDir = app_home_env; + } + else + { + // traditionally on unixoids, MyApp gets ~/.myapp dir for data + mOSUserAppDir = mOSUserDir; + mOSUserAppDir += "/"; + mOSUserAppDir += "."; + LLString lower_app_name(app_name); + LLString::toLower(lower_app_name); + mOSUserAppDir += lower_app_name; + } + + // create any directories we expect to write to. + + int res = LLFile::mkdir(mOSUserAppDir.c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create app user dir " << mOSUserAppDir << llendl; + llwarns << "Default to base dir" << mOSUserDir << llendl; + mOSUserAppDir = mOSUserDir; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_LOGS,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_LOGS dir " << getExpandedFilename(LL_PATH_LOGS,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_USER_SETTINGS,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_USER_SETTINGS dir " << getExpandedFilename(LL_PATH_USER_SETTINGS,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_CACHE,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_CACHE dir " << getExpandedFilename(LL_PATH_CACHE,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_MOZILLA_PROFILE,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_MOZILLA_PROFILE dir " << getExpandedFilename(LL_PATH_MOZILLA_PROFILE,"") << llendl; + } + } + + mCAFile = getExpandedFilename(LL_PATH_APP_SETTINGS, "CA.pem"); +} + +U32 LLDir_Linux::countFilesInDir(const std::string &dirname, const std::string &mask) +{ + U32 file_count = 0; + glob_t g; + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + file_count = g.gl_pathc; + + globfree(&g); + } + + return (file_count); +} + +// get the next file in the directory +// automatically wrap if we've hit the end +BOOL LLDir_Linux::getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap) +{ + glob_t g; + BOOL result = FALSE; + fname = ""; + + if(!(dirname == mCurrentDir)) + { + // different dir specified, close old search + mCurrentDirIndex = -1; + mCurrentDirCount = -1; + mCurrentDir = dirname; + } + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + if(g.gl_pathc > 0) + { + if((int)g.gl_pathc != mCurrentDirCount) + { + // Number of matches has changed since the last search, meaning a file has been added or deleted. + // Reset the index. + mCurrentDirIndex = -1; + mCurrentDirCount = g.gl_pathc; + } + + mCurrentDirIndex++; + + if((mCurrentDirIndex >= (int)g.gl_pathc) && wrap) + { + mCurrentDirIndex = 0; + } + + if(mCurrentDirIndex < (int)g.gl_pathc) + { +// llinfos << "getNextFileInDir: returning number " << mCurrentDirIndex << ", path is " << g.gl_pathv[mCurrentDirIndex] << llendl; + + // The API wants just the filename, not the full path. + //fname = g.gl_pathv[mCurrentDirIndex]; + + char *s = strrchr(g.gl_pathv[mCurrentDirIndex], '/'); + + if(s == NULL) + s = g.gl_pathv[mCurrentDirIndex]; + else if(s[0] == '/') + s++; + + fname = s; + + result = TRUE; + } + } + + globfree(&g); + } + + return(result); +} + + +// get a random file in the directory +// automatically wrap if we've hit the end +void LLDir_Linux::getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname) +{ + U32 num_files; + U32 which_file; + DIR *dirp; + dirent *entryp = NULL; + + fname = ""; + + num_files = countFilesInDir(dirname,mask); + if (!num_files) + { + return; + } + + which_file = gLindenLabRandomNumber.llrand() % num_files; + +// llinfos << "Random select file #" << which_file << llendl; + + // which_file now indicates the (zero-based) index to which file to play + + if (!((dirp = opendir(dirname.c_str())))) + { + while (which_file--) + { + if (!((entryp = readdir(dirp)))) + { + return; + } + } + + if ((!which_file) && entryp) + { + fname = entryp->d_name; + } + + closedir(dirp); + } +} + +std::string LLDir_Linux::getCurPath() +{ + char tmp_str[LL_MAX_PATH]; + getcwd(tmp_str, LL_MAX_PATH); + return tmp_str; +} + + +BOOL LLDir_Linux::fileExists(const std::string &filename) +{ + struct stat stat_data; + // Check the age of the file + // Now, we see if the files we've gathered are recent... + int res = stat(filename.c_str(), &stat_data); + if (!res) + { + return TRUE; + } + else + { + return FALSE; + } +} + diff --git a/indra/llvfs/lldir_linux.h b/indra/llvfs/lldir_linux.h new file mode 100644 index 0000000000..3e63e72303 --- /dev/null +++ b/indra/llvfs/lldir_linux.h @@ -0,0 +1,41 @@ +/** + * @file lldir_linux.h + * @brief Definition of directory utilities class for linux + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDIR_LINUX_H +#define LL_LLDIR_LINUX_H + +#include "lldir.h" + +#include +#include +#include + +class LLDir_Linux : public LLDir +{ +public: + LLDir_Linux(); + virtual ~LLDir_Linux(); + + virtual void initAppDirs(const std::string &app_name); +public: + virtual std::string getCurPath(); + virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask); + virtual BOOL getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap); + virtual void getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname); + /*virtual*/ BOOL fileExists(const std::string &filename); + +private: + DIR *mDirp; + int mCurrentDirIndex; + int mCurrentDirCount; + std::string mCurrentDir; +}; + +#endif // LL_LLDIR_LINUX_H + + diff --git a/indra/llvfs/lldir_mac.cpp b/indra/llvfs/lldir_mac.cpp new file mode 100644 index 0000000000..591241478d --- /dev/null +++ b/indra/llvfs/lldir_mac.cpp @@ -0,0 +1,362 @@ +/** + * @file lldir_mac.cpp + * @brief Implementation of directory utilities for linux + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_DARWIN + +#include "linden_common.h" + +#include "lldir_mac.h" +#include "llerror.h" +#include "llrand.h" +#include +#include +#include +#include + +#include + +// -------------------------------------------------------------------------------- + +static OSStatus CFCreateDirectory(FSRef *parentRef, CFStringRef name, FSRef *newRef) +{ + OSStatus result = noErr; + HFSUniStr255 uniStr; + + uniStr.length = CFStringGetLength(name); + CFStringGetCharacters(name, CFRangeMake(0, uniStr.length), uniStr.unicode); + result = FSMakeFSRefUnicode(parentRef, uniStr.length, uniStr.unicode, kTextEncodingMacRoman, newRef); + if (result != noErr) + { + result = FSCreateDirectoryUnicode(parentRef, uniStr.length, uniStr.unicode, 0, NULL, newRef, NULL, NULL); + } + + return result; +} + +// -------------------------------------------------------------------------------- + +static void CFStringRefToLLString(CFStringRef stringRef, std::string &llString, bool releaseWhenDone) +{ + if (stringRef) + { + long bufferSize = CFStringGetLength(stringRef) + 1; + char* buffer = new char[bufferSize]; + memset(buffer, 0, bufferSize); + if (CFStringGetCString(stringRef, buffer, bufferSize, kCFStringEncodingUTF8)) + llString = buffer; + delete[] buffer; + if (releaseWhenDone) + CFRelease(stringRef); + } +} + +// -------------------------------------------------------------------------------- + +static void CFURLRefToLLString(CFURLRef urlRef, std::string &llString, bool releaseWhenDone) +{ + if (urlRef) + { + CFURLRef absoluteURLRef = CFURLCopyAbsoluteURL(urlRef); + if (absoluteURLRef) + { + CFStringRef stringRef = CFURLCopyFileSystemPath(absoluteURLRef, kCFURLPOSIXPathStyle); + CFStringRefToLLString(stringRef, llString, true); + CFRelease(absoluteURLRef); + } + if (releaseWhenDone) + CFRelease(urlRef); + } +} + +// -------------------------------------------------------------------------------- + +static void FSRefToLLString(FSRef *fsRef, std::string &llString) +{ + OSStatus error = noErr; + char path[MAX_PATH]; + + error = FSRefMakePath(fsRef, (UInt8*) path, sizeof(path)); + if (error == noErr) + llString = path; +} + +// -------------------------------------------------------------------------------- + +LLDir_Mac::LLDir_Mac() +{ + mDirDelimiter = "/"; + mCurrentDirIndex = -1; + mCurrentDirCount = -1; + + CFBundleRef mainBundleRef = NULL; + CFURLRef executableURLRef = NULL; + CFStringRef stringRef = NULL; + OSStatus error = noErr; + FSRef fileRef; + CFStringRef secondLifeString = CFSTR("SecondLife"); + + mainBundleRef = CFBundleGetMainBundle(); + + executableURLRef = CFBundleCopyExecutableURL(mainBundleRef); + + if (executableURLRef != NULL) + { + // mExecutablePathAndName + CFURLRefToLLString(executableURLRef, mExecutablePathAndName, false); + + // mExecutableFilename + stringRef = CFURLCopyLastPathComponent(executableURLRef); + CFStringRefToLLString(stringRef, mExecutableFilename, true); + + // mExecutableDir + CFURLRef executableParentURLRef = CFURLCreateCopyDeletingLastPathComponent(NULL, executableURLRef); + CFURLRefToLLString(executableParentURLRef, mExecutableDir, true); + + // mAppRODataDir + CFURLRef resourcesURLRef = CFBundleCopyResourcesDirectoryURL(mainBundleRef); + CFURLRefToLLString(resourcesURLRef, mAppRODataDir, true); + + // mOSUserDir + error = FSFindFolder(kUserDomain, kApplicationSupportFolderType, true, &fileRef); + if (error == noErr) + { + FSRef newFileRef; + + // Create the directory + error = CFCreateDirectory(&fileRef, secondLifeString, &newFileRef); + if (error == noErr) + { + // Save the full path to the folder + FSRefToLLString(&newFileRef, mOSUserDir); + + // Create our sub-dirs + (void) CFCreateDirectory(&newFileRef, CFSTR("data"), NULL); + (void) CFCreateDirectory(&newFileRef, CFSTR("cache"), NULL); + (void) CFCreateDirectory(&newFileRef, CFSTR("logs"), NULL); + (void) CFCreateDirectory(&newFileRef, CFSTR("user_settings"), NULL); + (void) CFCreateDirectory(&newFileRef, CFSTR("browser_profile"), NULL); + } + } + + // mOSUserAppDir + mOSUserAppDir = mOSUserDir; + + // mTempDir + error = FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType, true, &fileRef); + if (error == noErr) + { + FSRef tempRef; + error = CFCreateDirectory(&fileRef, secondLifeString, &tempRef); + if (error == noErr) + FSRefToLLString(&tempRef, mTempDir); + } + + // Set the working dir to /Contents/Resources + (void) chdir(mAppRODataDir.c_str()); + + // Canonically, since we set it here... + mWorkingDir = mAppRODataDir; + + CFRelease(executableURLRef); + executableURLRef = NULL; + } +} + +LLDir_Mac::~LLDir_Mac() +{ +} + +// Implementation + + +void LLDir_Mac::initAppDirs(const std::string &app_name) +{ + mCAFile = getExpandedFilename(LL_PATH_APP_SETTINGS, "CA.pem"); + + //dumpCurrentDirectories(); +} + +U32 LLDir_Mac::countFilesInDir(const std::string &dirname, const std::string &mask) +{ + U32 file_count = 0; + glob_t g; + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + file_count = g.gl_pathc; + + globfree(&g); + } + + return (file_count); +} + +// get the next file in the directory +// automatically wrap if we've hit the end +BOOL LLDir_Mac::getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap) +{ + glob_t g; + BOOL result = FALSE; + fname = ""; + + if(!(dirname == mCurrentDir)) + { + // different dir specified, close old search + mCurrentDirIndex = -1; + mCurrentDirCount = -1; + mCurrentDir = dirname; + } + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + if(g.gl_pathc > 0) + { + if(g.gl_pathc != mCurrentDirCount) + { + // Number of matches has changed since the last search, meaning a file has been added or deleted. + // Reset the index. + mCurrentDirIndex = -1; + mCurrentDirCount = g.gl_pathc; + } + + mCurrentDirIndex++; + + if((mCurrentDirIndex >= g.gl_pathc) && wrap) + { + mCurrentDirIndex = 0; + } + + if(mCurrentDirIndex < g.gl_pathc) + { +// llinfos << "getNextFileInDir: returning number " << mCurrentDirIndex << ", path is " << g.gl_pathv[mCurrentDirIndex] << llendl; + + // The API wants just the filename, not the full path. + //fname = g.gl_pathv[mCurrentDirIndex]; + + char *s = strrchr(g.gl_pathv[mCurrentDirIndex], '/'); + + if(s == NULL) + s = g.gl_pathv[mCurrentDirIndex]; + else if(s[0] == '/') + s++; + + fname = s; + + result = TRUE; + } + } + + globfree(&g); + } + + return(result); +} + +// get a random file in the directory +void LLDir_Mac::getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname) +{ + U32 which_file; + glob_t g; + fname = ""; + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + if(g.gl_pathc > 0) + { + + which_file = gLindenLabRandomNumber.llrand() % g.gl_pathc; + +// llinfos << "getRandomFileInDir: returning number " << which_file << ", path is " << g.gl_pathv[which_file] << llendl; + // The API wants just the filename, not the full path. + //fname = g.gl_pathv[which_file]; + + char *s = strrchr(g.gl_pathv[which_file], '/'); + + if(s == NULL) + s = g.gl_pathv[which_file]; + else if(s[0] == '/') + s++; + + fname = s; + } + + globfree(&g); + } +} + +S32 LLDir_Mac::deleteFilesInDir(const std::string &dirname, const std::string &mask) +{ + glob_t g; + S32 result = 0; + + std::string tmp_str; + tmp_str = dirname; + tmp_str += mask; + + if(glob(tmp_str.c_str(), GLOB_NOSORT, NULL, &g) == 0) + { + int i; + + for(i = 0; i < g.gl_pathc; i++) + { +// llinfos << "deleteFilesInDir: deleting number " << i << ", path is " << g.gl_pathv[i] << llendl; + + if(unlink(g.gl_pathv[i]) != 0) + { + result = errno; + + llwarns << "Problem removing " << g.gl_pathv[i] << " - errorcode: " + << result << llendl; + } + } + + globfree(&g); + } + + return(result); +} + +std::string LLDir_Mac::getCurPath() +{ + char tmp_str[LL_MAX_PATH]; + getcwd(tmp_str, LL_MAX_PATH); + return tmp_str; +} + + + +BOOL LLDir_Mac::fileExists(const std::string &filename) +{ + struct stat stat_data; + // Check the age of the file + // Now, we see if the files we've gathered are recent... + int res = stat(filename.c_str(), &stat_data); + if (!res) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +#endif // LL_DARWIN diff --git a/indra/llvfs/lldir_mac.h b/indra/llvfs/lldir_mac.h new file mode 100644 index 0000000000..24fb1ac583 --- /dev/null +++ b/indra/llvfs/lldir_mac.h @@ -0,0 +1,40 @@ +/** + * @file lldir_mac.h + * @brief Definition of directory utilities class for Mac OS X + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDIR_MAC_H +#define LL_LLDIR_MAC_H + +#include "lldir.h" + +#include +#include + +class LLDir_Mac : public LLDir +{ +public: + LLDir_Mac(); + virtual ~LLDir_Mac(); + + virtual void initAppDirs(const std::string &app_name); +public: + virtual S32 deleteFilesInDir(const std::string &dirname, const std::string &mask); + virtual std::string getCurPath(); + virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask); + virtual BOOL getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap); + virtual void getRandomFileInDir(const std::string &dirname, const std::string &ask, std::string &fname); + virtual BOOL fileExists(const std::string &filename); + +private: + int mCurrentDirIndex; + int mCurrentDirCount; + std::string mCurrentDir; +}; + +#endif // LL_LLDIR_MAC_H + + diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp new file mode 100644 index 0000000000..0c5b0ecf19 --- /dev/null +++ b/indra/llvfs/lldir_win32.cpp @@ -0,0 +1,382 @@ +/** + * @file lldir_win32.cpp + * @brief Implementation of directory utilities for windows + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_WINDOWS + +#include "linden_common.h" + +#include "lldir_win32.h" +#include "llerror.h" +#include "llrand.h" // for gLindenLabRandomNumber +#include "shlobj.h" + +#include +#include +#include + +// Utility stuff to get versions of the sh +#define PACKVERSION(major,minor) MAKELONG(minor,major) +DWORD GetDllVersion(LPCTSTR lpszDllName); + +LLDir_Win32::LLDir_Win32() +{ + mDirDelimiter = "\\"; + + WCHAR w_str[MAX_PATH]; + + // Application Data is where user settings go + SHGetSpecialFolderPath(NULL, w_str, CSIDL_APPDATA, TRUE); + + mOSUserDir = utf16str_to_utf8str(llutf16string(w_str)); + + // Local Settings\Application Data is where cache files should + // go, they don't get copied to the server if the user moves his + // profile around on the network. JC + // + // TODO: patch the installer to remove old cache files on update, then + // enable this code. + //SHGetSpecialFolderPath(NULL, w_str, CSIDL_LOCAL_APPDATA, TRUE); + //mOSUserCacheDir = utf16str_to_utf8str(llutf16string(w_str)); + + if (GetTempPath(MAX_PATH, w_str)) + { + if (wcslen(w_str)) + { + w_str[wcslen(w_str)-1] = '\0'; // remove trailing slash + } + mTempDir = utf16str_to_utf8str(llutf16string(w_str)); + } + else + { + mTempDir = mOSUserDir; + } + +// fprintf(stderr, "mTempDir = <%s>",mTempDir); + +#if 1 + // Don't use the real app path for now, as we'll have to add parsing to detect if + // we're in a developer tree, which has a different structure from the installed product. + + S32 size = GetModuleFileName(NULL, w_str, MAX_PATH); + if (size) + { + w_str[size] = '\0'; + mExecutablePathAndName = utf16str_to_utf8str(llutf16string(w_str)); + S32 path_end = mExecutablePathAndName.find_last_of('\\'); + if (path_end != std::string::npos) + { + mExecutableDir = mExecutablePathAndName.substr(0, path_end); + mExecutableFilename = mExecutablePathAndName.substr(path_end+1, std::string::npos); + } + else + { + mExecutableFilename = mExecutablePathAndName; + } + GetCurrentDirectory(MAX_PATH, w_str); + mWorkingDir = utf16str_to_utf8str(llutf16string(w_str)); + + } + else + { + fprintf(stderr, "Couldn't get APP path, assuming current directory!"); + GetCurrentDirectory(MAX_PATH, w_str); + mExecutableDir = utf16str_to_utf8str(llutf16string(w_str)); + // Assume it's the current directory + } +#else + GetCurrentDirectory(MAX_PATH, w_str); + mExecutableDir = utf16str_to_utf8str(llutf16string(w_str)); +#endif + if (strstr(mExecutableDir.c_str(), "indra\\newview")) + mAppRODataDir = getCurPath(); + else + mAppRODataDir = mExecutableDir; +} + +LLDir_Win32::~LLDir_Win32() +{ +} + +// Implementation + +void LLDir_Win32::initAppDirs(const std::string &app_name) +{ + mAppName = app_name; + mOSUserAppDir = mOSUserDir; + mOSUserAppDir += "\\"; + mOSUserAppDir += app_name; + + int res = LLFile::mkdir(mOSUserAppDir.c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create app user dir " << mOSUserAppDir << llendl; + llwarns << "Default to base dir" << mOSUserDir << llendl; + mOSUserAppDir = mOSUserDir; + } + } + //dumpCurrentDirectories(); + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_LOGS,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_LOGS dir " << getExpandedFilename(LL_PATH_LOGS,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_USER_SETTINGS,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_USER_SETTINGS dir " << getExpandedFilename(LL_PATH_USER_SETTINGS,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_CACHE,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_CACHE dir " << getExpandedFilename(LL_PATH_CACHE,"") << llendl; + } + } + + res = LLFile::mkdir(getExpandedFilename(LL_PATH_MOZILLA_PROFILE,"").c_str()); + if (res == -1) + { + if (errno != EEXIST) + { + llwarns << "Couldn't create LL_PATH_MOZILLA_PROFILE dir " << getExpandedFilename(LL_PATH_MOZILLA_PROFILE,"") << llendl; + } + } + + mCAFile = getExpandedFilename(LL_PATH_APP_SETTINGS, "CA.pem"); +} + +U32 LLDir_Win32::countFilesInDir(const std::string &dirname, const std::string &mask) +{ + HANDLE count_search_h; + U32 file_count; + + file_count = 0; + + WIN32_FIND_DATA FileData; + + llutf16string pathname = utf8str_to_utf16str(dirname); + pathname += utf8str_to_utf16str(mask); + + if ((count_search_h = FindFirstFile(pathname.c_str(), &FileData)) != INVALID_HANDLE_VALUE) + { + file_count++; + + while (FindNextFile(count_search_h, &FileData)) + { + file_count++; + } + + FindClose(count_search_h); + } + + return (file_count); +} + + +// get the next file in the directory +// automatically wrap if we've hit the end +BOOL LLDir_Win32::getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap) +{ + llutf16string dirnamew = utf8str_to_utf16str(dirname); + return getNextFileInDir(dirnamew, mask, fname, wrap); + +} + +BOOL LLDir_Win32::getNextFileInDir(const llutf16string &dirname, const std::string &mask, std::string &fname, BOOL wrap) +{ + WIN32_FIND_DATAW FileData; + + fname = ""; + llutf16string pathname = dirname; + pathname += utf8str_to_utf16str(mask); + + if (pathname != mCurrentDir) + { + // different dir specified, close old search + if (mCurrentDir[0]) + { + FindClose(mDirSearch_h); + } + mCurrentDir = pathname; + + // and open new one + // Check error opening Directory structure + if ((mDirSearch_h = FindFirstFile(pathname.c_str(), &FileData)) == INVALID_HANDLE_VALUE) + { +// llinfos << "Unable to locate first file" << llendl; + return(FALSE); + } + } + else // get next file in list + { + // Find next entry + if (!FindNextFile(mDirSearch_h, &FileData)) + { + if (GetLastError() == ERROR_NO_MORE_FILES) + { + // No more files, so reset to beginning of directory + FindClose(mDirSearch_h); + mCurrentDir[0] = NULL; + + if (wrap) + { + return(getNextFileInDir(pathname,"",fname,TRUE)); + } + else + { + fname[0] = 0; + return(FALSE); + } + } + else + { + // Error +// llinfos << "Unable to locate next file" << llendl; + return(FALSE); + } + } + } + + // convert from TCHAR to char + fname = utf16str_to_utf8str(FileData.cFileName); + + // fname now first name in list + return(TRUE); +} + + +// get a random file in the directory +// automatically wrap if we've hit the end +void LLDir_Win32::getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname) +{ + U32 num_files; + U32 which_file; + HANDLE random_search_h; + + fname = ""; + + llutf16string pathname = utf8str_to_utf16str(dirname); + pathname += utf8str_to_utf16str(mask); + + WIN32_FIND_DATA FileData; + fname[0] = NULL; + + num_files = countFilesInDir(dirname,mask); + if (!num_files) + { + return; + } + + which_file = gLindenLabRandomNumber.llrand() % num_files; + +// llinfos << "Random select mp3 #" << which_file << llendl; + + // which_file now indicates the (zero-based) index to which file to play + + if ((random_search_h = FindFirstFile(pathname.c_str(), &FileData)) != INVALID_HANDLE_VALUE) + { + while (which_file--) + { + if (!FindNextFile(random_search_h, &FileData)) + { + return; + } + } + FindClose(random_search_h); + + fname = utf16str_to_utf8str(llutf16string(FileData.cFileName)); + } +} + +std::string LLDir_Win32::getCurPath() +{ + WCHAR w_str[MAX_PATH]; + GetCurrentDirectory(MAX_PATH, w_str); + + return utf16str_to_utf8str(llutf16string(w_str)); +} + + +BOOL LLDir_Win32::fileExists(const std::string &filename) +{ + llstat stat_data; + // Check the age of the file + // Now, we see if the files we've gathered are recent... + int res = LLFile::stat(filename.c_str(), &stat_data); + if (!res) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +#if 0 +// Utility function to get version number of a DLL + +#define PACKVERSION(major,minor) MAKELONG(minor,major) + +DWORD GetDllVersion(LPCTSTR lpszDllName) +{ + + HINSTANCE hinstDll; + DWORD dwVersion = 0; + + hinstDll = LoadLibrary(lpszDllName); + + if(hinstDll) + { + DLLGETVERSIONPROC pDllGetVersion; + + pDllGetVersion = (DLLGETVERSIONPROC) GetProcAddress(hinstDll, "DllGetVersion"); + +/*Because some DLLs might not implement this function, you + must test for it explicitly. Depending on the particular + DLL, the lack of a DllGetVersion function can be a useful + indicator of the version. +*/ + if(pDllGetVersion) + { + DLLVERSIONINFO dvi; + HRESULT hr; + + ZeroMemory(&dvi, sizeof(dvi)); + dvi.cbSize = sizeof(dvi); + + hr = (*pDllGetVersion)(&dvi); + + if(SUCCEEDED(hr)) + { + dwVersion = PACKVERSION(dvi.dwMajorVersion, dvi.dwMinorVersion); + } + } + + FreeLibrary(hinstDll); + } + return dwVersion; +} +#endif + +#endif + + diff --git a/indra/llvfs/lldir_win32.h b/indra/llvfs/lldir_win32.h new file mode 100644 index 0000000000..fbeeef2732 --- /dev/null +++ b/indra/llvfs/lldir_win32.h @@ -0,0 +1,37 @@ +/** + * @file lldir_win32.h + * @brief Definition of directory utilities class for windows + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLDIR_WIN32_H +#define LL_LLDIR_WIN32_H + +#include "lldir.h" + +class LLDir_Win32 : public LLDir +{ +public: + LLDir_Win32(); + virtual ~LLDir_Win32(); + + virtual void initAppDirs(const std::string &app_name); +public: + virtual std::string getCurPath(); + virtual U32 countFilesInDir(const std::string &dirname, const std::string &mask); + virtual BOOL getNextFileInDir(const std::string &dirname, const std::string &mask, std::string &fname, BOOL wrap); + virtual void getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname); + /*virtual*/ BOOL fileExists(const std::string &filename); + +private: + BOOL LLDir_Win32::getNextFileInDir(const llutf16string &dirname, const std::string &mask, std::string &fname, BOOL wrap); + + HANDLE mDirSearch_h; + llutf16string mCurrentDir; +}; + +#endif // LL_LLDIR_WIN32_H + + diff --git a/indra/llvfs/lllfsthread.cpp b/indra/llvfs/lllfsthread.cpp new file mode 100644 index 0000000000..57b4bc6d47 --- /dev/null +++ b/indra/llvfs/lllfsthread.cpp @@ -0,0 +1,308 @@ +/** + * @file lllfsthread.cpp + * @brief LLLFSThread base class + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llmath.h" +#include "lllfsthread.h" +#include "llstl.h" +#include "llapr.h" + +//============================================================================ + +/*static*/ LLLFSThread* LLLFSThread::sLocal = NULL; + +//============================================================================ +// Run on MAIN thread +//static +void LLLFSThread::initClass(bool local_is_threaded, bool local_run_always) +{ + llassert(sLocal == NULL); + sLocal = new LLLFSThread(local_is_threaded, local_run_always); +} + +//static +S32 LLLFSThread::updateClass(U32 ms_elapsed) +{ + sLocal->update(ms_elapsed); + return sLocal->getPending(); +} + +//static +void LLLFSThread::cleanupClass() +{ + sLocal->setQuitting(); + while (sLocal->getPending()) + { + sLocal->update(0); + } + delete sLocal; + sLocal = 0; +} + +//---------------------------------------------------------------------------- + +LLLFSThread::LLLFSThread(bool threaded, bool runalways) : + LLQueuedThread("LFS", threaded, runalways) +{ +} + +LLLFSThread::~LLLFSThread() +{ + // ~LLQueuedThread() will be called here +} + +//---------------------------------------------------------------------------- + +LLLFSThread::handle_t LLLFSThread::read(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes, U32 priority, U32 flags) +{ + handle_t handle = generateHandle(); + + priority = llmax(priority, (U32)PRIORITY_LOW); // All reads are at least PRIORITY_LOW + Request* req = new Request(handle, priority, flags, + FILE_READ, filename, + buffer, offset, numbytes); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +S32 LLLFSThread::readImmediate(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, + FILE_READ, filename, + buffer, offset, numbytes); + + S32 res = addRequest(req) ? 1 : 0; + if (res == 0) + { + llerrs << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + } + else + { + llverify(waitForResult(handle, false) == true); + res = req->getBytesRead(); + completeRequest(handle); + } + return res; +} + +LLLFSThread::handle_t LLLFSThread::write(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes, U32 flags) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, 0, flags, + FILE_WRITE, filename, + buffer, offset, numbytes); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLLFSThread::read called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +S32 LLLFSThread::writeImmediate(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, + FILE_WRITE, filename, + buffer, offset, numbytes); + + S32 res = addRequest(req) ? 1 : 0; + if (res == 0) + { + llerrs << "LLLFSThread::write called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + } + else + { + llverify(waitForResult(handle, false) == true); + res = req->getBytesRead(); + completeRequest(handle); + } + return res; +} + + +LLLFSThread::handle_t LLLFSThread::rename(const LLString& filename, const LLString& newname, U32 flags) +{ + handle_t handle = generateHandle(); + + LLString* new_name_str = new LLString(newname); // deleted with Request + Request* req = new Request(handle, 0, flags, + FILE_RENAME, filename, + (U8*)new_name_str, 0, 0); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLLFSThread::rename called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +LLLFSThread::handle_t LLLFSThread::remove(const LLString& filename, U32 flags) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, 0, flags, + FILE_RENAME, filename, + NULL, 0, 0); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLLFSThread::remove called after LLLFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +//============================================================================ +// Runs on its OWN thread + +bool LLLFSThread::processRequest(QueuedRequest* qreq) +{ + Request *req = (Request*)qreq; + + bool complete = req->processIO(); + + return complete; +} + +//============================================================================ + +LLLFSThread::Request::Request(handle_t handle, U32 priority, U32 flags, + operation_t op, const LLString& filename, + U8* buffer, S32 offset, S32 numbytes) : + QueuedRequest(handle, priority, flags), + mOperation(op), + mFileName(filename), + mBuffer(buffer), + mOffset(offset), + mBytes(numbytes), + mBytesRead(0) +{ + llassert(mBuffer); + + if (numbytes <= 0 && mOperation != FILE_RENAME && mOperation != FILE_REMOVE) + { + llwarns << "LLLFSThread: Request with numbytes = " << numbytes << llendl; + } +} + +void LLLFSThread::Request::finishRequest() +{ +} + +void LLLFSThread::Request::deleteRequest() +{ + if (getStatus() == STATUS_QUEUED || getStatus() == STATUS_ABORT) + { + llerrs << "Attempt to delete a queued LLLFSThread::Request!" << llendl; + } + if (mOperation == FILE_WRITE) + { + if (mFlags & AUTO_DELETE) + { + delete mBuffer; + } + } + else if (mOperation == FILE_RENAME) + { + LLString* new_name = (LLString*)mBuffer; + delete new_name; + } + LLQueuedThread::QueuedRequest::deleteRequest(); +} + +bool LLLFSThread::Request::processIO() +{ + bool complete = false; + if (mOperation == FILE_READ) + { + llassert(mOffset >= 0); + apr_file_t* filep = ll_apr_file_open(mFileName, LL_APR_RB); + if (!filep) + { + llwarns << "LLLFS: Unable to read file: " << mFileName << llendl; + mBytesRead = 0; // fail + return true; + } + if (mOffset < 0) + ll_apr_file_seek(filep, APR_END, 0); + else + ll_apr_file_seek(filep, APR_SET, mOffset); + mBytesRead = ll_apr_file_read(filep, mBuffer, mBytes ); + apr_file_close(filep); + complete = true; + //llinfos << llformat("LLLFSThread::READ '%s': %d bytes",mFileName.c_str(),mBytesRead) << llendl; + } + else if (mOperation == FILE_WRITE) + { + apr_file_t* filep = ll_apr_file_open(mFileName, LL_APR_WB); + if (!filep) + { + llwarns << "LLLFS: Unable to write file: " << mFileName << llendl; + mBytesRead = 0; // fail + return true; + } + if (mOffset < 0) + ll_apr_file_seek(filep, APR_END, 0); + else + ll_apr_file_seek(filep, APR_SET, mOffset); + mBytesRead = ll_apr_file_write(filep, mBuffer, mBytes ); + complete = true; + apr_file_close(filep); + //llinfos << llformat("LLLFSThread::WRITE '%s': %d bytes",mFileName.c_str(),mBytesRead) << llendl; + } + else if (mOperation == FILE_RENAME) + { + LLString* new_name = (LLString*)mBuffer; + ll_apr_file_rename(mFileName, *new_name); + complete = true; + //llinfos << llformat("LLLFSThread::RENAME '%s': '%s'",mFileName.c_str(),new_name->c_str()) << llendl; + } + else if (mOperation == FILE_REMOVE) + { + ll_apr_file_remove(mFileName); + complete = true; + //llinfos << llformat("LLLFSThread::REMOVE '%s'",mFileName.c_str()) << llendl; + } + else + { + llerrs << llformat("LLLFSThread::unknown operation: %d", mOperation) << llendl; + } + return complete; +} + +//============================================================================ diff --git a/indra/llvfs/lllfsthread.h b/indra/llvfs/lllfsthread.h new file mode 100644 index 0000000000..a55a2668b3 --- /dev/null +++ b/indra/llvfs/lllfsthread.h @@ -0,0 +1,119 @@ +/** + * @file lllfsthread.h + * @brief LLLFSThread base class + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLLFSTHREAD_H +#define LL_LLLFSTHREAD_H + +#include +#include +#include +#include + +#include "llapr.h" + +#include "llqueuedthread.h" + +//============================================================================ +// Threaded Local File System +//============================================================================ + +class LLLFSThread : public LLQueuedThread +{ + //------------------------------------------------------------------------ +public: + enum operation_t { + FILE_READ, + FILE_WRITE, + FILE_RENAME, + FILE_REMOVE + }; + + //------------------------------------------------------------------------ +public: + + class Request : public QueuedRequest + { + protected: + ~Request() {}; // use deleteRequest() + + public: + Request(handle_t handle, U32 priority, U32 flags, + operation_t op, const LLString& filename, + U8* buffer, S32 offset, S32 numbytes); + + S32 getBytes() + { + return mBytes; + } + S32 getBytesRead() + { + return mBytesRead; + } + S32 getOperation() + { + return mOperation; + } + U8* getBuffer() + { + return mBuffer; + } + const LLString& getFilename() + { + return mFileName; + } + + /*virtual*/ void finishRequest(); + /*virtual*/ void deleteRequest(); + + bool processIO(); + + private: + operation_t mOperation; + + LLString mFileName; + + U8* mBuffer; // dest for reads, source for writes, new UUID for rename + S32 mOffset; // offset into file, -1 = append (WRITE only) + S32 mBytes; // bytes to read from file, -1 = all + S32 mBytesRead; // bytes read from file + }; + + //------------------------------------------------------------------------ +public: + LLLFSThread(bool threaded = TRUE, bool runalways = TRUE); + ~LLLFSThread(); + + // Return a Request handle + handle_t read(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes, U32 pri=PRIORITY_NORMAL, U32 flags = 0); + handle_t write(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes, U32 flags = 0); + handle_t rename(const LLString& filename, const LLString& newname, U32 flags = 0); + handle_t remove(const LLString& filename, U32 flags = 0); + + // Return number of bytes read + S32 readImmediate(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes); + S32 writeImmediate(const LLString& filename, + U8* buffer, S32 offset, S32 numbytes); + + static void initClass(bool local_is_threaded = TRUE, bool run_always = TRUE); // Setup sLocal + static S32 updateClass(U32 ms_elapsed); + static void cleanupClass(); // Delete sLocal + +protected: + /*virtual*/ bool processRequest(QueuedRequest* req); + +public: + static LLLFSThread* sLocal; // Default local file thread +}; + +//============================================================================ + + +#endif // LL_LLLFSTHREAD_H diff --git a/indra/llvfs/llvfile.cpp b/indra/llvfs/llvfile.cpp new file mode 100644 index 0000000000..36ac569d02 --- /dev/null +++ b/indra/llvfs/llvfile.cpp @@ -0,0 +1,415 @@ +/** + * @file llvfile.cpp + * @brief Implementation of virtual file + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llvfile.h" + +#include "llerror.h" +#include "llthread.h" +#include "llvfs.h" + +const S32 LLVFile::READ = 0x00000001; +const S32 LLVFile::WRITE = 0x00000002; +const S32 LLVFile::READ_WRITE = 0x00000003; // LLVFile::READ & LLVFile::WRITE +const S32 LLVFile::APPEND = 0x00000006; // 0x00000004 & LLVFile::WRITE + +//---------------------------------------------------------------------------- +LLVFSThread* LLVFile::sVFSThread = NULL; +BOOL LLVFile::sAllocdVFSThread = FALSE; +BOOL LLVFile::ALLOW_ASYNC = TRUE; +//---------------------------------------------------------------------------- + +//============================================================================ + +LLVFile::LLVFile(LLVFS *vfs, const LLUUID &file_id, const LLAssetType::EType file_type, S32 mode) +{ + mFileType = file_type; + + mFileID = file_id; + mPosition = 0; + mMode = mode; + mVFS = vfs; + + mBytesRead = 0; + mHandle = LLVFSThread::nullHandle(); + mPriority = 128.f; + + mVFS->incLock(mFileID, mFileType, VFSLOCK_OPEN); +} + +LLVFile::~LLVFile() +{ + if (!isReadComplete()) + { + if (mHandle != LLVFSThread::nullHandle()) + { + if (!(mMode & LLVFile::WRITE)) + { + // llwarns << "Destroying LLVFile with pending async read/write, aborting..." << llendl; + sVFSThread->abortRequest(mHandle, LLVFSThread::AUTO_COMPLETE); + } + else // WRITE + { + sVFSThread->setFlags(mHandle, LLVFSThread::AUTO_COMPLETE); + } + } + } + mVFS->decLock(mFileID, mFileType, VFSLOCK_OPEN); +} + +BOOL LLVFile::read(U8 *buffer, S32 bytes, BOOL async, F32 priority) +{ + if (! (mMode & READ)) + { + llwarns << "Attempt to read from file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << llendl; + return FALSE; + } + + if (mHandle != LLVFSThread::nullHandle()) + { + llwarns << "Attempt to read from vfile object " << mFileID << " with pending async operation" << llendl; + return FALSE; + } + mPriority = priority; + + BOOL success = TRUE; + + // We can't do a read while there are pending async writes + waitForLock(VFSLOCK_APPEND); + + // FIXME + if (async) + { + mHandle = sVFSThread->read(mVFS, mFileID, mFileType, buffer, mPosition, bytes, threadPri()); + } + else + { + // We can't do a read while there are pending async writes on this file + mBytesRead = sVFSThread->readImmediate(mVFS, mFileID, mFileType, buffer, mPosition, bytes); + mPosition += mBytesRead; + if (! mBytesRead) + { + success = FALSE; + } + } + + return success; +} + +//static +U8* LLVFile::readFile(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, S32* bytes_read) +{ + U8 *data; + LLVFile file(vfs, uuid, type, LLVFile::READ); + S32 file_size = file.getSize(); + if (file_size == 0) + { + // File is empty. + data = NULL; + } + else + { + data = new U8[file_size]; + file.read(data, file_size); + + if (file.getLastBytesRead() != (S32)file_size) + { + delete[] data; + data = NULL; + file_size = 0; + } + } + if (bytes_read) + { + *bytes_read = file_size; + } + return data; +} + +void LLVFile::setReadPriority(const F32 priority) +{ + mPriority = priority; + if (mHandle != LLVFSThread::nullHandle()) + { + sVFSThread->setPriority(mHandle, threadPri()); + } +} + +BOOL LLVFile::isReadComplete() +{ + BOOL res = TRUE; + if (mHandle != LLVFSThread::nullHandle()) + { + LLVFSThread::Request* req = (LLVFSThread::Request*)sVFSThread->getRequest(mHandle); + LLVFSThread::status_t status = req->getStatus(); + if (status == LLVFSThread::STATUS_COMPLETE) + { + mBytesRead = req->getBytesRead(); + mPosition += mBytesRead; + sVFSThread->completeRequest(mHandle); + mHandle = LLVFSThread::nullHandle(); + } + else + { + res = FALSE; + } + } + return res; +} + +S32 LLVFile::getLastBytesRead() +{ + return mBytesRead; +} + +BOOL LLVFile::eof() +{ + return mPosition >= getSize(); +} + +BOOL LLVFile::write(const U8 *buffer, S32 bytes) +{ + if (! (mMode & WRITE)) + { + llwarns << "Attempt to write to file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << llendl; + } + if (mHandle != LLVFSThread::nullHandle()) + { + llerrs << "Attempt to write to vfile object " << mFileID << " with pending async operation" << llendl; + return FALSE; + } + BOOL success = TRUE; + + // FIXME: allow async writes? potential problem wit mPosition... + if (mMode == APPEND) // all appends are async (but WRITEs are not) + { + U8* writebuf = new U8[bytes]; + memcpy(writebuf, buffer, bytes); + S32 offset = -1; + mHandle = sVFSThread->write(mVFS, mFileID, mFileType, + writebuf, offset, bytes, + LLVFSThread::AUTO_COMPLETE | LLVFSThread::AUTO_DELETE); + mHandle = LLVFSThread::nullHandle(); // AUTO_COMPLETE means we don't track this + } + else + { + // We can't do a write while there are pending reads or writes on this file + waitForLock(VFSLOCK_READ); + waitForLock(VFSLOCK_APPEND); + + S32 pos = (mMode & APPEND) == APPEND ? -1 : mPosition; + + S32 wrote = sVFSThread->writeImmediate(mVFS, mFileID, mFileType, (U8*)buffer, pos, bytes); + + mPosition += wrote; + + if (wrote < bytes) + { + llwarns << "Tried to write " << bytes << " bytes, actually wrote " << wrote << llendl; + + success = FALSE; + } + } + return success; +} + +//static +BOOL LLVFile::writeFile(const U8 *buffer, S32 bytes, LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type) +{ + LLVFile file(vfs, uuid, type, LLVFile::WRITE); + file.setMaxSize(bytes); + return file.write(buffer, bytes); +} + +BOOL LLVFile::seek(S32 offset, S32 origin) +{ + if (mMode == APPEND) + { + llwarns << "Attempt to seek on append-only file" << llendl; + return FALSE; + } + + if (-1 == origin) + { + origin = mPosition; + } + + S32 new_pos = origin + offset; + + S32 size = getSize(); // Calls waitForLock(VFSLOCK_APPEND) + + if (new_pos > size) + { + llwarns << "Attempt to seek past end of file" << llendl; + + mPosition = size; + return FALSE; + } + else if (new_pos < 0) + { + llwarns << "Attempt to seek past beginning of file" << llendl; + + mPosition = 0; + return FALSE; + } + + mPosition = new_pos; + return TRUE; +} + +S32 LLVFile::tell() const +{ + return mPosition; +} + +S32 LLVFile::getSize() +{ + waitForLock(VFSLOCK_APPEND); + S32 size = mVFS->getSize(mFileID, mFileType); + + return size; +} + +S32 LLVFile::getMaxSize() +{ + S32 size = mVFS->getMaxSize(mFileID, mFileType); + + return size; +} + +BOOL LLVFile::setMaxSize(S32 size) +{ + if (! (mMode & WRITE)) + { + llwarns << "Attempt to change size of file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << llendl; + + return FALSE; + } + + if (!mVFS->checkAvailable(size)) + { + LLFastTimer t(LLFastTimer::FTM_VFILE_WAIT); + S32 count = 0; + while (sVFSThread->getPending() > 1000) + { + if (count % 100 == 0) + { + llinfos << "VFS catching up... Pending: " << sVFSThread->getPending() << llendl; + } + if (sVFSThread->isPaused()) + { + sVFSThread->updateQueue(0); + } + ms_sleep(10); + } + } + return mVFS->setMaxSize(mFileID, mFileType, size); +} + +BOOL LLVFile::rename(const LLUUID &new_id, const LLAssetType::EType new_type) +{ + if (! (mMode & WRITE)) + { + llwarns << "Attempt to rename file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << llendl; + + return FALSE; + } + + if (mHandle != LLVFSThread::nullHandle()) + { + llwarns << "Renaming file with pending async read" << llendl; + } + + waitForLock(VFSLOCK_READ); + waitForLock(VFSLOCK_APPEND); + + // we need to release / replace our own lock + // since the renamed file will inherit locks from the new name + mVFS->decLock(mFileID, mFileType, VFSLOCK_OPEN); + mVFS->renameFile(mFileID, mFileType, new_id, new_type); + mVFS->incLock(new_id, new_type, VFSLOCK_OPEN); + + mFileID = new_id; + mFileType = new_type; + + return TRUE; +} + +BOOL LLVFile::remove() +{ +// llinfos << "Removing file " << mFileID << llendl; + + if (! (mMode & WRITE)) + { + // Leaving paranoia warning just because this should be a very infrequent + // operation. + llwarns << "Remove file " << mFileID << " opened with mode " << std::hex << mMode << std::dec << llendl; + } + + if (mHandle != LLVFSThread::nullHandle()) + { + llwarns << "Removing file with pending async read" << llendl; + } + + // why not seek back to the beginning of the file too? + mPosition = 0; + + waitForLock(VFSLOCK_READ); + waitForLock(VFSLOCK_APPEND); + mVFS->removeFile(mFileID, mFileType); + + return TRUE; +} + +// static +void LLVFile::initClass(LLVFSThread* vfsthread) +{ + if (!vfsthread) + { + if (LLVFSThread::sLocal != NULL) + { + vfsthread = LLVFSThread::sLocal; + } + else + { + vfsthread = new LLVFSThread(); + sAllocdVFSThread = TRUE; + } + } + sVFSThread = vfsthread; +} + +// static +void LLVFile::cleanupClass() +{ + if (sAllocdVFSThread) + { + delete sVFSThread; + } + sVFSThread = NULL; +} + +bool LLVFile::isLocked(EVFSLock lock) +{ + return mVFS->isLocked(mFileID, mFileType, lock) ? true : false; +} + +void LLVFile::waitForLock(EVFSLock lock) +{ + LLFastTimer t(LLFastTimer::FTM_VFILE_WAIT); + // spin until the lock clears + while (isLocked(lock)) + { + if (sVFSThread->isPaused()) + { + sVFSThread->updateQueue(0); + } + ms_sleep(1); + } +} diff --git a/indra/llvfs/llvfile.h b/indra/llvfs/llvfile.h new file mode 100644 index 0000000000..c00e843cad --- /dev/null +++ b/indra/llvfs/llvfile.h @@ -0,0 +1,75 @@ +/** + * @file llvfile.h + * @brief Definition of virtual file + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVFILE_H +#define LL_LLVFILE_H + +#include "lluuid.h" +#include "llassettype.h" +#include "llvfs.h" +#include "llvfsthread.h" + +class LLVFile +{ +public: + LLVFile(LLVFS *vfs, const LLUUID &file_id, const LLAssetType::EType file_type, S32 mode = LLVFile::READ); + ~LLVFile(); + + BOOL read(U8 *buffer, S32 bytes, BOOL async = FALSE, F32 priority = 128.f); + static U8* readFile(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, S32* bytes_read = 0); + void setReadPriority(const F32 priority); + BOOL isReadComplete(); + S32 getLastBytesRead(); + BOOL eof(); + + BOOL write(const U8 *buffer, S32 bytes); + static BOOL writeFile(const U8 *buffer, S32 bytes, LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type); + BOOL seek(S32 offset, S32 origin = -1); + S32 tell() const; + + S32 getSize(); + S32 getMaxSize(); + BOOL setMaxSize(S32 size); + BOOL rename(const LLUUID &new_id, const LLAssetType::EType new_type); + BOOL remove(); + + bool isLocked(EVFSLock lock); + void waitForLock(EVFSLock lock); + + static void initClass(LLVFSThread* vfsthread = NULL); + static void cleanupClass(); + static LLVFSThread* getVFSThread() { return sVFSThread; } + +protected: + static LLVFSThread* sVFSThread; + static BOOL sAllocdVFSThread; + U32 threadPri() { return LLVFSThread::PRIORITY_NORMAL + llmin((U32)mPriority,(U32)0xfff); } + +public: + static const S32 READ; + static const S32 WRITE; + static const S32 READ_WRITE; + static const S32 APPEND; + + static BOOL ALLOW_ASYNC; + +protected: + LLAssetType::EType mFileType; + + LLUUID mFileID; + S32 mPosition; + S32 mMode; + LLVFS *mVFS; + F32 mPriority; + BOOL mOnReadQueue; + + S32 mBytesRead; + LLVFSThread::handle_t mHandle; +}; + +#endif diff --git a/indra/llvfs/llvfs.cpp b/indra/llvfs/llvfs.cpp new file mode 100644 index 0000000000..39b12035c9 --- /dev/null +++ b/indra/llvfs/llvfs.cpp @@ -0,0 +1,2048 @@ +/** + * @file llvfs.cpp + * @brief Implementation of virtual file system + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include +#include +#include +#include +#if LL_WINDOWS +#include +#else +#include +#endif + +#include "llvfs.h" +#include "llstl.h" + +const S32 FILE_BLOCK_MASK = 0x000003FF; // 1024-byte blocks +const S32 VFS_CLEANUP_SIZE = 5242880; // how much space we free up in a single stroke +const S32 BLOCK_LENGTH_INVALID = -1; // mLength for invalid LLVFSFileBlocks + +LLVFS *gVFS = NULL; + +// internal class definitions +class LLVFSBlock +{ +public: + LLVFSBlock() + { + mLocation = 0; + mLength = 0; + } + + LLVFSBlock(U32 loc, S32 size) + { + mLocation = loc; + mLength = size; + } + + static BOOL insertFirstLL(LLVFSBlock *first, LLVFSBlock *second) + { + return first->mLocation != second->mLocation + ? first->mLocation < second->mLocation + : first->mLength < second->mLength; + + } + +public: + U32 mLocation; + S32 mLength; // allocated block size +}; + +LLVFSFileSpecifier::LLVFSFileSpecifier() +: mFileID(), + mFileType( LLAssetType::AT_NONE ) +{ +} + +LLVFSFileSpecifier::LLVFSFileSpecifier(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + mFileID = file_id; + mFileType = file_type; +} + +bool LLVFSFileSpecifier::operator<(const LLVFSFileSpecifier &rhs) const +{ + return (mFileID == rhs.mFileID) + ? mFileType < rhs.mFileType + : mFileID < rhs.mFileID; +} + +bool LLVFSFileSpecifier::operator==(const LLVFSFileSpecifier &rhs) const +{ + return (mFileID == rhs.mFileID && + mFileType == rhs.mFileType); +} + + +class LLVFSFileBlock : public LLVFSBlock, public LLVFSFileSpecifier +{ +public: + LLVFSFileBlock() : LLVFSBlock(), LLVFSFileSpecifier() + { + init(); + } + + LLVFSFileBlock(const LLUUID &file_id, LLAssetType::EType file_type, U32 loc = 0, S32 size = 0) + : LLVFSBlock(loc, size), LLVFSFileSpecifier( file_id, file_type ) + { + init(); + } + + void init() + { + mSize = 0; + mIndexLocation = -1; + mAccessTime = (U32)time(NULL); + + for (S32 i = 0; i < (S32)VFSLOCK_COUNT; i++) + { + mLocks[(EVFSLock)i] = 0; + } + } + + #ifdef LL_LITTLE_ENDIAN + inline void swizzleCopy(void *dst, void *src, int size) { memcpy(dst, src, size); } + + #else + + inline U32 swizzle32(U32 x) + { + return(((x >> 24) & 0x000000FF) | ((x >> 8) & 0x0000FF00) | ((x << 8) & 0x00FF0000) |((x << 24) & 0xFF000000)); + } + + inline U16 swizzle16(U16 x) + { + return( ((x >> 8) & 0x000000FF) | ((x << 8) & 0x0000FF00) ); + } + + inline void swizzleCopy(void *dst, void *src, int size) + { + if(size == 4) + { + ((U32*)dst)[0] = swizzle32(((U32*)src)[0]); + } + else if(size == 2) + { + ((U16*)dst)[0] = swizzle16(((U16*)src)[0]); + } + else + { + // Perhaps this should assert... + memcpy(dst, src, size); + } + } + + #endif + + void serialize(U8 *buffer) + { + swizzleCopy(buffer, &mLocation, 4); + buffer += 4; + swizzleCopy(buffer, &mLength, 4); + buffer +=4; + swizzleCopy(buffer, &mAccessTime, 4); + buffer +=4; + memcpy(buffer, &mFileID.mData, 16); + buffer += 16; + S16 temp_type = mFileType; + swizzleCopy(buffer, &temp_type, 2); + buffer += 2; + swizzleCopy(buffer, &mSize, 4); + } + + void deserialize(U8 *buffer, const S32 index_loc) + { + mIndexLocation = index_loc; + + swizzleCopy(&mLocation, buffer, 4); + buffer += 4; + swizzleCopy(&mLength, buffer, 4); + buffer += 4; + swizzleCopy(&mAccessTime, buffer, 4); + buffer += 4; + memcpy(&mFileID.mData, buffer, 16); + buffer += 16; + S16 temp_type; + swizzleCopy(&temp_type, buffer, 2); + mFileType = (LLAssetType::EType)temp_type; + buffer += 2; + swizzleCopy(&mSize, buffer, 4); + } + + static BOOL insertLRU(LLVFSFileBlock* const& first, + LLVFSFileBlock* const& second) + { + return (first->mAccessTime == second->mAccessTime) + ? *first < *second + : first->mAccessTime < second->mAccessTime; + } + +public: + S32 mSize; + S32 mIndexLocation; // location of index entry + U32 mAccessTime; + BOOL mLocks[VFSLOCK_COUNT]; // number of outstanding locks of each type + + static const S32 SERIAL_SIZE; +}; + +// Helper structure for doing lru w/ stl... is there a simpler way? +struct LLVFSFileBlock_less +{ + bool operator()(LLVFSFileBlock* const& lhs, LLVFSFileBlock* const& rhs) const + { + return (LLVFSFileBlock::insertLRU(lhs, rhs)) ? true : false; + } +}; + + +const S32 LLVFSFileBlock::SERIAL_SIZE = 34; + + +LLVFS::LLVFS(const char *index_filename, const char *data_filename, const BOOL read_only, const U32 presize, const BOOL remove_after_crash) +: mRemoveAfterCrash(remove_after_crash) +{ + mDataMutex = new LLMutex(0); + + S32 i; + for (i = 0; i < VFSLOCK_COUNT; i++) + { + mLockCounts[i] = 0; + } + mValid = VFSVALID_OK; + mReadOnly = read_only; + mIndexFilename = new char[strlen(index_filename) + 1]; + mDataFilename = new char[strlen(data_filename) + 1]; + strcpy(mIndexFilename, index_filename); + strcpy(mDataFilename, data_filename); + + const char *file_mode = mReadOnly ? "rb" : "r+b"; + + if (! (mDataFP = openAndLock(mDataFilename, file_mode, mReadOnly))) + { + + if (mReadOnly) + { + llwarns << "Can't find " << mDataFilename << " to open read-only VFS" << llendl; + mValid = VFSVALID_BAD_CANNOT_OPEN_READONLY; + return; + } + + if((mDataFP = openAndLock(mDataFilename, "w+b", FALSE))) + { + // Since we're creating this data file, assume any index file is bogus + // remove the index, since this vfs is now blank + LLFile::remove(mIndexFilename); + } + else + { + llwarns << "Can't open VFS data file " << mDataFilename << " attempting to use alternate" << llendl; + + char *temp_index = new char[strlen(mIndexFilename) + 10]; + char *temp_data = new char[strlen(mDataFilename) + 10]; + + for (U32 count = 0; count < 256; count++) + { + sprintf(temp_index, "%s.%u", mIndexFilename, count); + sprintf(temp_data, "%s.%u", mDataFilename, count); + + // try just opening, then creating, each alternate + if ((mDataFP = openAndLock(temp_data, "r+b", FALSE))) + { + break; + } + + if ((mDataFP = openAndLock(temp_data, "w+b", FALSE))) + { + // we're creating the datafile, so nuke the indexfile + LLFile::remove(temp_index); + break; + } + } + + if (! mDataFP) + { + llwarns << "Couldn't open vfs data file after trying many alternates" << llendl; + mValid = VFSVALID_BAD_CANNOT_CREATE; + return; + } + + delete[] mIndexFilename; + delete[] mDataFilename; + + mIndexFilename = temp_index; + mDataFilename = temp_data; + } + + if (presize) + { + presizeDataFile(presize); + } + } + + // Did we leave this file open for writing last time? + // If so, close it and start over. + if (!mReadOnly && mRemoveAfterCrash) + { + llstat marker_info; + char* marker = new char[strlen(mDataFilename) + strlen(".open") + 1]; + sprintf(marker, "%s.open", mDataFilename); + if (!LLFile::stat(marker, &marker_info)) + { + // marker exists, kill the lock and the VFS files + unlockAndClose(mDataFP); + mDataFP = NULL; + + llwarns << "VFS: File left open on last run, removing old VFS file " << mDataFilename << llendl; + LLFile::remove(mIndexFilename); + LLFile::remove(mDataFilename); + LLFile::remove(marker); + + mDataFP = openAndLock(mDataFilename, "w+b", FALSE); + if (!mDataFP) + { + llwarns << "Can't open VFS data file in crash recovery" << llendl; + mValid = VFSVALID_BAD_CANNOT_CREATE; + return; + } + + if (presize) + { + presizeDataFile(presize); + } + } + delete [] marker; + marker = NULL; + } + + // determine the real file size + fseek(mDataFP, 0, SEEK_END); + U32 data_size = ftell(mDataFP); + + // read the index file + // make sure there's at least one file in it too + // if not, we'll treat this as a new vfs + llstat fbuf; + if (! LLFile::stat(mIndexFilename, &fbuf) && + fbuf.st_size >= LLVFSFileBlock::SERIAL_SIZE && + (mIndexFP = openAndLock(mIndexFilename, file_mode, mReadOnly)) + ) + { + U8 *buffer = new U8[fbuf.st_size]; + fread(buffer, fbuf.st_size, 1, mIndexFP); + + U8 *tmp_ptr = buffer; + + LLLinkedList files_by_loc; + files_by_loc.setInsertBefore(LLVFSBlock::insertFirstLL); + + while (tmp_ptr < buffer + fbuf.st_size) + { + LLVFSFileBlock *block = new LLVFSFileBlock(); + + block->deserialize(tmp_ptr, (S32)(tmp_ptr - buffer)); + + // Do sanity check on this block. + // Note that this skips zero size blocks, which helps VFS + // to heal after some errors. JC + if (block->mLength > 0 && + (U32)block->mLength <= data_size && + block->mLocation >= 0 && + block->mLocation < data_size && + block->mSize > 0 && + block->mSize <= block->mLength && + block->mFileType >= LLAssetType::AT_NONE && + block->mFileType < LLAssetType::AT_COUNT) + { + mFileBlocks.insert(fileblock_map::value_type(*block, block)); + files_by_loc.addDataSorted(block); + } + else + if (block->mLength && block->mSize > 0) + { + // this is corrupt, not empty + llwarns << "VFS corruption: " << block->mFileID << " (" << block->mFileType << ") at index " << block->mIndexLocation << " DS: " << data_size << llendl; + llwarns << "Length: " << block->mLength << "\tLocation: " << block->mLocation << "\tSize: " << block->mSize << llendl; + llwarns << "File has bad data - VFS removed" << llendl; + + delete[] buffer; + delete block; + + unlockAndClose( mIndexFP ); + mIndexFP = NULL; + LLFile::remove( mIndexFilename ); + + unlockAndClose( mDataFP ); + mDataFP = NULL; + LLFile::remove( mDataFilename ); + + mValid = VFSVALID_BAD_CORRUPT; + return; + } + else + { + // this is a null or bad entry, skip it + S32 index_loc = (S32)(tmp_ptr - buffer); + mIndexHoles.push_back(index_loc); + + delete block; + } + + tmp_ptr += block->SERIAL_SIZE; + } + delete[] buffer; + + // discover all the free blocks + LLVFSFileBlock *last_file_block = (LLVFSFileBlock*)files_by_loc.getFirstData(); + + if (last_file_block) + { + // check for empty space at the beginning + if (last_file_block->mLocation > 0) + { + LLVFSBlock *block = new LLVFSBlock(0, last_file_block->mLocation); + addFreeBlock(block); + } + + LLVFSFileBlock *cur_file_block; + while ((cur_file_block = (LLVFSFileBlock*)files_by_loc.getNextData())) + { + if (cur_file_block->mLocation == last_file_block->mLocation + && cur_file_block->mLength == last_file_block->mLength) + { + llwarns << "VFS: removing duplicate entry" + << " at " << cur_file_block->mLocation + << " length " << cur_file_block->mLength + << " size " << cur_file_block->mSize + << " ID " << cur_file_block->mFileID + << " type " << cur_file_block->mFileType + << llendl; + + // Duplicate entries. Nuke them both for safety. + mFileBlocks.erase(*cur_file_block); // remove ID/type entry + if (cur_file_block->mLength > 0) + { + // convert to hole + LLVFSBlock* block = new LLVFSBlock(cur_file_block->mLocation, + cur_file_block->mLength); + addFreeBlock(block); + } + lockData(); // needed for sync() + sync(cur_file_block, TRUE); // remove first on disk + sync(last_file_block, TRUE); // remove last on disk + unlockData(); // needed for sync() + last_file_block = cur_file_block; + continue; + } + + U32 loc = last_file_block->mLocation + last_file_block->mLength; + S32 length = cur_file_block->mLocation - loc; + + if (length < 0 || loc < 0 || loc > data_size) + { + // Invalid VFS + unlockAndClose( mIndexFP ); + mIndexFP = NULL; + LLFile::remove( mIndexFilename ); + + unlockAndClose( mDataFP ); + mDataFP = NULL; + LLFile::remove( mDataFilename ); + + llwarns << "VFS: overlapping entries" + << " at " << cur_file_block->mLocation + << " length " << cur_file_block->mLength + << " ID " << cur_file_block->mFileID + << " type " << cur_file_block->mFileType + << llendl; + mValid = VFSVALID_BAD_CORRUPT; + return; + } + + if (length > 0) + { + LLVFSBlock *block = new LLVFSBlock(loc, length); + addFreeBlock(block); + } + + last_file_block = cur_file_block; + } + + // also note any empty space at the end + U32 loc = last_file_block->mLocation + last_file_block->mLength; + if (loc < data_size) + { + LLVFSBlock *block = new LLVFSBlock(loc, data_size - loc); + addFreeBlock(block); + } + } + else + { + LLVFSBlock *first_block = new LLVFSBlock(0, data_size); + addFreeBlock(first_block); + } + } + else + { + if (mReadOnly) + { + llwarns << "Can't find " << mIndexFilename << " to open read-only VFS" << llendl; + mValid = VFSVALID_BAD_CANNOT_OPEN_READONLY; + return; + } + + + mIndexFP = openAndLock(mIndexFilename, "w+b", FALSE); + if (!mIndexFP) + { + llwarns << "Couldn't open an index file for the VFS, probably a sharing violation!" << llendl; + + unlockAndClose( mDataFP ); + mDataFP = NULL; + LLFile::remove( mDataFilename ); + + mValid = VFSVALID_BAD_CANNOT_CREATE; + return; + } + + // no index file, start from scratch w/ 1GB allocation + LLVFSBlock *first_block = new LLVFSBlock(0, data_size ? data_size : 0x40000000); + addFreeBlock(first_block); + } + + // Open marker file to look for bad shutdowns + if (!mReadOnly && mRemoveAfterCrash) + { + char* marker = new char[strlen(mDataFilename) + strlen(".open") + 1]; + sprintf(marker, "%s.open", mDataFilename); + FILE* marker_fp = LLFile::fopen(marker, "w"); + if (marker_fp) + { + fclose(marker_fp); + marker_fp = NULL; + } + delete [] marker; + marker = NULL; + } + + llinfos << "VFS: Using index file " << mIndexFilename << " and data file " << mDataFilename << llendl; + + mValid = VFSVALID_OK; +} + +LLVFS::~LLVFS() +{ + if (mDataMutex->isLocked()) + { + llerrs << "LLVFS destroyed with mutex locked" << llendl; + } + + unlockAndClose(mIndexFP); + mIndexFP = NULL; + + fileblock_map::const_iterator it; + for (it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + delete (*it).second; + } + mFileBlocks.clear(); + + mFreeBlocksByLength.clear(); + + for_each(mFreeBlocksByLocation.begin(), mFreeBlocksByLocation.end(), DeletePairedPointer()); + + unlockAndClose(mDataFP); + mDataFP = NULL; + + // Remove marker file + if (!mReadOnly && mRemoveAfterCrash) + { + char* marker_file = new char[strlen(mDataFilename) + strlen(".open") + 1]; + sprintf(marker_file, "%s.open", mDataFilename); + LLFile::remove(marker_file); + delete [] marker_file; + marker_file = NULL; + } + + delete[] mIndexFilename; + mIndexFilename = NULL; + delete[] mDataFilename; + mDataFilename = NULL; + + delete mDataMutex; +} + +void LLVFS::presizeDataFile(const U32 size) +{ + if (!mDataFP) + { + llerrs << "LLVFS::presizeDataFile() with no data file open" << llendl; + } + + // we're creating this file for the first time, size it + fseek(mDataFP, size-1, SEEK_SET); + S32 tmp = 0; + tmp = (S32)fwrite(&tmp, 1, 1, mDataFP); + // fflush(mDataFP); + + // also remove any index, since this vfs is now blank + LLFile::remove(mIndexFilename); + + if (tmp) + { + llinfos << "Pre-sized VFS data file to " << ftell(mDataFP) << " bytes" << llendl; + } + else + { + llwarns << "Failed to pre-size VFS data file" << llendl; + } +} + +BOOL LLVFS::getExists(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + LLVFSFileBlock *block = NULL; + + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + block = (*it).second; + block->mAccessTime = (U32)time(NULL); + } + + BOOL res = (block && block->mLength > 0) ? TRUE : FALSE; + + unlockData(); + + return res; +} + +S32 LLVFS::getSize(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + S32 size = 0; + + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + block->mAccessTime = (U32)time(NULL); + size = block->mSize; + } + + unlockData(); + + return size; +} + +S32 LLVFS::getMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + S32 size = 0; + + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + block->mAccessTime = (U32)time(NULL); + size = block->mLength; + } + + unlockData(); + + return size; +} + +BOOL LLVFS::checkAvailable(S32 max_size) +{ + blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(max_size); // first entry >= size + return (iter == mFreeBlocksByLength.end()) ? FALSE : TRUE; +} + +BOOL LLVFS::setMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type, S32 max_size) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + if (mReadOnly) + { + llerrs << "Attempt to write to read-only VFS" << llendl; + } + if (max_size <= 0) + { + llwarns << "VFS: Attempt to assign size " << max_size << " to vfile " << file_id << llendl; + return FALSE; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + LLVFSFileBlock *block = NULL; + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + block = (*it).second; + } + + // round all sizes upward to KB increments + if (max_size & FILE_BLOCK_MASK) + { + max_size += FILE_BLOCK_MASK; + max_size &= ~FILE_BLOCK_MASK; + } + + if (block && block->mLength > 0) + { + block->mAccessTime = (U32)time(NULL); + + if (max_size == block->mLength) + { + unlockData(); + return TRUE; + } + else if (max_size < block->mLength) + { + // this file is shrinking + LLVFSBlock *free_block = new LLVFSBlock(block->mLocation + max_size, block->mLength - max_size); + + addFreeBlock(free_block); + + block->mLength = max_size; + + if (block->mLength < block->mSize) + { + // JC: Was a warning, but Ian says it's bad. + llerrs << "Truncating virtual file " << file_id << " to " << block->mLength << " bytes" << llendl; + block->mSize = block->mLength; + } + + sync(block); + //mergeFreeBlocks(); + + unlockData(); + return TRUE; + } + else if (max_size > block->mLength) + { + // this file is growing + // first check for an adjacent free block to grow into + S32 size_increase = max_size - block->mLength; + + // Find the first free block with and addres > block->mLocation + LLVFSBlock *free_block; + blocks_location_map_t::iterator iter = mFreeBlocksByLocation.upper_bound(block->mLocation); + if (iter != mFreeBlocksByLocation.end()) + { + free_block = iter->second; + + if (free_block->mLocation == block->mLocation + block->mLength && + free_block->mLength >= size_increase) + { + // this free block is at the end of the file and is large enough + + // Must call useFreeSpace before sync(), as sync() + // unlocks data structures. + useFreeSpace(free_block, size_increase); + block->mLength += size_increase; + sync(block); + + unlockData(); + return TRUE; + } + } + + // no adjecent free block, find one in the list + free_block = findFreeBlock(max_size, block); + + if (free_block) + { + if (block->mLength > 0) + { + // create a new free block where this file used to be + LLVFSBlock *new_free_block = new LLVFSBlock(block->mLocation, block->mLength); + + addFreeBlock(new_free_block); + + if (block->mSize > 0) + { + // move the file into the new block + U8 *buffer = new U8[block->mSize]; + fseek(mDataFP, block->mLocation, SEEK_SET); + fread(buffer, block->mSize, 1, mDataFP); + fseek(mDataFP, free_block->mLocation, SEEK_SET); + fwrite(buffer, block->mSize, 1, mDataFP); + // fflush(mDataFP); + + delete[] buffer; + } + } + + block->mLocation = free_block->mLocation; + + block->mLength = max_size; + + // Must call useFreeSpace before sync(), as sync() + // unlocks data structures. + useFreeSpace(free_block, max_size); + + sync(block); + + unlockData(); + return TRUE; + } + else + { + llwarns << "VFS: No space (" << max_size << ") to resize existing vfile " << file_id << llendl; + //dumpMap(); + unlockData(); + dumpStatistics(); + return FALSE; + } + } + } + else + { + // find a free block in the list + LLVFSBlock *free_block = findFreeBlock(max_size); + + if (free_block) + { + if (block) + { + block->mLocation = free_block->mLocation; + block->mLength = max_size; + } + else + { + // this file doesn't exist, create it + block = new LLVFSFileBlock(file_id, file_type, free_block->mLocation, max_size); + mFileBlocks.insert(fileblock_map::value_type(spec, block)); + } + + // Must call useFreeSpace before sync(), as sync() + // unlocks data structures. + useFreeSpace(free_block, max_size); + block->mAccessTime = (U32)time(NULL); + + sync(block); + } + else + { + llwarns << "VFS: No space (" << max_size << ") for new virtual file " << file_id << llendl; + //dumpMap(); + unlockData(); + dumpStatistics(); + return FALSE; + } + } + unlockData(); + return TRUE; +} + + +// WARNING: HERE BE DRAGONS! +// rename is the weirdest VFS op, because the file moves but the locks don't! +void LLVFS::renameFile(const LLUUID &file_id, const LLAssetType::EType file_type, + const LLUUID &new_id, const LLAssetType::EType &new_type) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + if (mReadOnly) + { + llerrs << "Attempt to write to read-only VFS" << llendl; + } + + lockData(); + + LLVFSFileSpecifier new_spec(new_id, new_type); + LLVFSFileSpecifier old_spec(file_id, file_type); + + fileblock_map::iterator it = mFileBlocks.find(old_spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *src_block = (*it).second; + + // this will purge the data but leave the file block in place, w/ locks, if any + // WAS: removeFile(new_id, new_type); NOW uses removeFileBlock() to avoid mutex lock recursion + fileblock_map::iterator new_it = mFileBlocks.find(new_spec); + if (new_it != mFileBlocks.end()) + { + LLVFSFileBlock *new_block = (*new_it).second; + removeFileBlock(new_block); + } + + // if there's something in the target location, remove it but inherit its locks + it = mFileBlocks.find(new_spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *dest_block = (*it).second; + + for (S32 i = 0; i < (S32)VFSLOCK_COUNT; i++) + { + src_block->mLocks[(EVFSLock)i] = dest_block->mLocks[(EVFSLock)i]; + } + + mFileBlocks.erase(new_spec); + delete dest_block; + } + + src_block->mFileID = new_id; + src_block->mFileType = new_type; + src_block->mAccessTime = (U32)time(NULL); + + mFileBlocks.erase(old_spec); + mFileBlocks.insert(fileblock_map::value_type(new_spec, src_block)); + + sync(src_block); + } + else + { + llwarns << "VFS: Attempt to rename nonexistent vfile " << file_id << ":" << file_type << llendl; + } + unlockData(); +} + +// mDataMutex must be LOCKED before calling this +void LLVFS::removeFileBlock(LLVFSFileBlock *fileblock) +{ + // convert this into an unsaved, dummy fileblock to preserve locks + // a more rubust solution would store the locks in a seperate data structure + sync(fileblock, TRUE); + + if (fileblock->mLength > 0) + { + // turn this file into an empty block + LLVFSBlock *free_block = new LLVFSBlock(fileblock->mLocation, fileblock->mLength); + + addFreeBlock(free_block); + } + + fileblock->mLocation = 0; + fileblock->mSize = 0; + fileblock->mLength = BLOCK_LENGTH_INVALID; + fileblock->mIndexLocation = -1; + + //mergeFreeBlocks(); +} + +void LLVFS::removeFile(const LLUUID &file_id, const LLAssetType::EType file_type) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + if (mReadOnly) + { + llerrs << "Attempt to write to read-only VFS" << llendl; + } + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + removeFileBlock(block); + } + else + { + llwarns << "VFS: attempting to remove nonexistent file " << file_id << " type " << file_type << llendl; + } + + unlockData(); +} + + +S32 LLVFS::getData(const LLUUID &file_id, const LLAssetType::EType file_type, U8 *buffer, S32 location, S32 length) +{ + S32 bytesread = 0; + + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + llassert(location >= 0); + llassert(length >= 0); + + BOOL do_read = FALSE; + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + block->mAccessTime = (U32)time(NULL); + + if (location > block->mSize) + { + llwarns << "VFS: Attempt to read location " << location << " in file " << file_id << " of length " << block->mSize << llendl; + } + else + { + if (length > block->mSize - location) + { + length = block->mSize - location; + } + location += block->mLocation; + do_read = TRUE; + } + } + + unlockData(); + + if (do_read) + { + fseek(mDataFP, location, SEEK_SET); + bytesread = (S32)fread(buffer, 1, length, mDataFP); + } + + return bytesread; +} + +S32 LLVFS::storeData(const LLUUID &file_id, const LLAssetType::EType file_type, const U8 *buffer, S32 location, S32 length) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + if (mReadOnly) + { + llerrs << "Attempt to write to read-only VFS" << llendl; + } + + llassert(length > 0); + + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + S32 in_loc = location; + if (location == -1) + { + location = block->mSize; + } + llassert(location >= 0); + + block->mAccessTime = (U32)time(NULL); + + if (block->mLength == BLOCK_LENGTH_INVALID) + { + // Block was removed, ignore write + llwarns << "VFS: Attempt to write to invalid block" + << " in file " << file_id + << " location: " << in_loc + << " bytes: " << length + << llendl; + unlockData(); + return length; + } + else if (location > block->mLength) + { + llwarns << "VFS: Attempt to write to location " << location + << " in file " << file_id + << " type " << S32(file_type) + << " of size " << block->mSize + << " block length " << block->mLength + << llendl; + unlockData(); + return length; + } + else + { + if (length > block->mLength - location ) + { + llwarns << "VFS: Truncating write to virtual file " << file_id << " type " << S32(file_type) << llendl; + length = block->mLength - location; + } + U32 file_location = location + block->mLocation; + + unlockData(); + + fseek(mDataFP, file_location, SEEK_SET); + S32 write_len = (S32)fwrite(buffer, 1, length, mDataFP); + if (write_len != length) + { + llwarns << llformat("VFS Write Error: %d != %d",write_len,length) << llendl; + } + // fflush(mDataFP); + + lockData(); + if (location + length > block->mSize) + { + block->mSize = location + write_len; + sync(block); + } + unlockData(); + + return write_len; + } + } + else + { + unlockData(); + return 0; + } +} + +void LLVFS::incLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) +{ + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + LLVFSFileBlock *block; + + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + block = (*it).second; + } + else + { + // Create a dummy block which isn't saved + block = new LLVFSFileBlock(file_id, file_type, 0, BLOCK_LENGTH_INVALID); + block->mAccessTime = (U32)time(NULL); + mFileBlocks.insert(fileblock_map::value_type(spec, block)); + } + + block->mLocks[lock]++; + mLockCounts[lock]++; + + unlockData(); +} + +void LLVFS::decLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) +{ + lockData(); + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + + if (block->mLocks[lock] > 0) + { + block->mLocks[lock]--; + } + else + { + llwarns << "VFS: Decrementing zero-value lock " << lock << llendl; + } + mLockCounts[lock]--; + } + + unlockData(); +} + +BOOL LLVFS::isLocked(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock) +{ + lockData(); + + BOOL res = FALSE; + + LLVFSFileSpecifier spec(file_id, file_type); + fileblock_map::iterator it = mFileBlocks.find(spec); + if (it != mFileBlocks.end()) + { + LLVFSFileBlock *block = (*it).second; + res = (block->mLocks[lock] > 0); + } + + unlockData(); + + return res; +} + +//============================================================================ +// protected +//============================================================================ + +void LLVFS::eraseBlockLength(LLVFSBlock *block) +{ + // find the corresponding map entry in the length map and erase it + S32 length = block->mLength; + blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(length); + blocks_length_map_t::iterator end = mFreeBlocksByLength.end(); + while(iter != end) + { + LLVFSBlock *tblock = iter->second; + llassert(tblock->mLength == length); // there had -better- be an entry with our length! + if (tblock == block) + { + mFreeBlocksByLength.erase(iter); + break; + } + ++iter; + } + if (iter == end) + { + llerrs << "eraseBlock could not find block" << llendl; + } +} + + +// Remove block from both free lists (by location and by length). +void LLVFS::eraseBlock(LLVFSBlock *block) +{ + eraseBlockLength(block); + // find the corresponding map entry in the location map and erase it + U32 location = block->mLocation; + llverify(mFreeBlocksByLocation.erase(location) == 1); // we should only have one entry per location. +} + + +// Add the region specified by block location and length to the free lists. +// Also incrementally defragment by merging with previous and next free blocks. +void LLVFS::addFreeBlock(LLVFSBlock *block) +{ +#if LL_DEBUG + size_t dbgcount = mFreeBlocksByLocation.count(block->mLocation); + if(dbgcount > 0) + { + llerrs << "addFreeBlock called with block already in list" << llendl; + } +#endif + + // Get a pointer to the next free block (by location). + blocks_location_map_t::iterator next_free_it = mFreeBlocksByLocation.lower_bound(block->mLocation); + + // We can merge with previous if it ends at our requested location. + LLVFSBlock* prev_block = NULL; + bool merge_prev = false; + if (next_free_it != mFreeBlocksByLocation.begin()) + { + blocks_location_map_t::iterator prev_free_it = next_free_it; + --prev_free_it; + prev_block = prev_free_it->second; + merge_prev = (prev_block->mLocation + prev_block->mLength == block->mLocation); + } + + // We can merge with next if our block ends at the next block's location. + LLVFSBlock* next_block = NULL; + bool merge_next = false; + if (next_free_it != mFreeBlocksByLocation.end()) + { + next_block = next_free_it->second; + merge_next = (block->mLocation + block->mLength == next_block->mLocation); + } + + if (merge_prev && merge_next) + { + // llinfos << "VFS merge BOTH" << llendl; + // Previous block is changing length (a lot), so only need to update length map. + // Next block is going away completely. JC + eraseBlockLength(prev_block); + eraseBlock(next_block); + prev_block->mLength += block->mLength + next_block->mLength; + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(prev_block->mLength, prev_block)); + delete block; + block = NULL; + delete next_block; + next_block = NULL; + } + else if (merge_prev) + { + // llinfos << "VFS merge previous" << llendl; + // Previous block is maintaining location, only changing length, + // therefore only need to update the length map. JC + eraseBlockLength(prev_block); + prev_block->mLength += block->mLength; + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(prev_block->mLength, prev_block)); // multimap insert + delete block; + block = NULL; + } + else if (merge_next) + { + // llinfos << "VFS merge next" << llendl; + // Next block is changing both location and length, + // so both free lists must update. JC + eraseBlock(next_block); + next_block->mLocation = block->mLocation; + next_block->mLength += block->mLength; + // Don't hint here, next_free_it iterator may be invalid. + mFreeBlocksByLocation.insert(blocks_location_map_t::value_type(next_block->mLocation, next_block)); // multimap insert + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(next_block->mLength, next_block)); // multimap insert + delete block; + block = NULL; + } + else + { + // Can't merge with other free blocks. + // Hint that insert should go near next_free_it. + mFreeBlocksByLocation.insert(next_free_it, blocks_location_map_t::value_type(block->mLocation, block)); // multimap insert + mFreeBlocksByLength.insert(blocks_length_map_t::value_type(block->mLength, block)); // multimap insert + } +} + +// Superceeded by new addFreeBlock which does incremental free space merging. +// Incremental is faster overall. +//void LLVFS::mergeFreeBlocks() +//{ +// if (!isValid()) +// { +// llerrs << "Attempting to use invalid VFS!" << llendl; +// } +// // TODO: could we optimize this with hints from the calling code? +// blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(); +// blocks_location_map_t::iterator end = mFreeBlocksByLocation.end(); +// LLVFSBlock *first_block = iter->second; +// while(iter != end) +// { +// blocks_location_map_t::iterator first_iter = iter; // save for if we do a merge +// if (++iter == end) +// break; +// LLVFSBlock *second_block = iter->second; +// if (first_block->mLocation + first_block->mLength == second_block->mLocation) +// { +// // remove the first block from the length map +// eraseBlockLength(first_block); +// // merge first_block with second_block, since they're adjacent +// first_block->mLength += second_block->mLength; +// // add the first block to the length map (with the new size) +// mFreeBlocksByLength.insert(blocks_length_map_t::value_type(first_block->mLength, first_block)); // multimap insert +// +// // erase and delete the second block +// eraseBlock(second_block); +// delete second_block; +// +// // reset iterator +// iter = first_iter; // haven't changed first_block, so corresponding iterator is still valid +// end = mFreeBlocksByLocation.end(); +// } +// first_block = second_block; +// } +//} + + +void LLVFS::useFreeSpace(LLVFSBlock *free_block, S32 length) +{ + if (free_block->mLength == length) + { + eraseBlock(free_block); + delete free_block; + } + else + { + eraseBlock(free_block); + + free_block->mLocation += length; + free_block->mLength -= length; + + addFreeBlock(free_block); + } +} + +// NOTE! mDataMutex must be LOCKED before calling this +// sync this index entry out to the index file +// we need to do this constantly to avoid corruption on viewer crash +void LLVFS::sync(LLVFSFileBlock *block, BOOL remove) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + if (mReadOnly) + { + llwarns << "Attempt to sync read-only VFS" << llendl; + return; + } + if (block->mLength == BLOCK_LENGTH_INVALID) + { + // This is a dummy file, don't save + return; + } + if (block->mLength == 0) + { + llerrs << "VFS syncing zero-length block" << llendl; + } + + BOOL set_index_to_end = FALSE; + S32 seek_pos = block->mIndexLocation; + + if (-1 == seek_pos) + { + if (!mIndexHoles.empty()) + { + seek_pos = mIndexHoles.front(); + mIndexHoles.pop_front(); + } + else + { + set_index_to_end = TRUE; + } + } + + if (set_index_to_end) + { + // Need fseek/ftell to update the seek_pos and hence data + // structures, so can't unlockData() before this. + fseek(mIndexFP, 0, SEEK_END); + seek_pos = ftell(mIndexFP); + } + + block->mIndexLocation = seek_pos; + if (remove) + { + mIndexHoles.push_back(seek_pos); + } + + U8 buffer[LLVFSFileBlock::SERIAL_SIZE]; + if (remove) + { + memset(buffer, 0, LLVFSFileBlock::SERIAL_SIZE); + } + else + { + block->serialize(buffer); + } + + unlockData(); + + // If set_index_to_end, file pointer is already at seek_pos + // and we don't need to do anything. Only seek if not at end. + if (!set_index_to_end) + { + fseek(mIndexFP, seek_pos, SEEK_SET); + } + + fwrite(buffer, LLVFSFileBlock::SERIAL_SIZE, 1, mIndexFP); + // fflush(mIndexFP); + + lockData(); + + return; +} + +// mDataMutex must be LOCKED before calling this +// Can initiate LRU-based file removal to make space. +// The immune file block will not be removed. +LLVFSBlock *LLVFS::findFreeBlock(S32 size, LLVFSFileBlock *immune) +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + + LLVFSBlock *block = NULL; + BOOL have_lru_list = FALSE; + + typedef std::set lru_set; + lru_set lru_list; + + LLTimer timer; + + while (! block) + { + // look for a suitable free block + blocks_length_map_t::iterator iter = mFreeBlocksByLength.lower_bound(size); // first entry >= size + if (iter != mFreeBlocksByLength.end()) + block = iter->second; + + // no large enough free blocks, time to clean out some junk + if (! block) + { + // create a list of files sorted by usage time + // this is far faster than sorting a linked list + if (! have_lru_list) + { + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *tmp = (*it).second; + + if (tmp != immune && + tmp->mLength > 0 && + ! tmp->mLocks[VFSLOCK_READ] && + ! tmp->mLocks[VFSLOCK_APPEND] && + ! tmp->mLocks[VFSLOCK_OPEN]) + { + lru_list.insert(tmp); + } + } + + have_lru_list = TRUE; + } + + if (lru_list.size() == 0) + { + // No more files to delete, and still not enough room! + llwarns << "VFS: Can't make " << size << " bytes of free space in VFS, giving up" << llendl; + break; + } + + // is the oldest file big enough? (Should be about half the time) + lru_set::iterator it = lru_list.begin(); + LLVFSFileBlock *file_block = *it; + if (file_block->mLength >= size && file_block != immune) + { + // ditch this file and look again for a free block - should find it + // TODO: it'll be faster just to assign the free block and break + llinfos << "LRU: Removing " << file_block->mFileID << ":" << file_block->mFileType << llendl; + lru_list.erase(it); + removeFileBlock(file_block); + file_block = NULL; + continue; + } + + + llinfos << "VFS: LRU: Aggressive: " << (S32)lru_list.size() << " files remain" << llendl; + dumpLockCounts(); + + // Now it's time to aggressively make more space + // Delete the oldest 5MB of the vfs or enough to hold the file, which ever is larger + // This may yield too much free space, but we'll use it up soon enough + U32 cleanup_target = (size > VFS_CLEANUP_SIZE) ? size : VFS_CLEANUP_SIZE; + U32 cleaned_up = 0; + for (it = lru_list.begin(); + it != lru_list.end() && cleaned_up < cleanup_target; + ) + { + file_block = *it; + + // TODO: it would be great to be able to batch all these sync() calls + // llinfos << "LRU2: Removing " << file_block->mFileID << ":" << file_block->mFileType << " last accessed" << file_block->mAccessTime << llendl; + + cleaned_up += file_block->mLength; + lru_list.erase(it++); + removeFileBlock(file_block); + file_block = NULL; + } + //mergeFreeBlocks(); + } + } + + F32 time = timer.getElapsedTimeF32(); + if (time > 0.5f) + { + llwarns << "VFS: Spent " << time << " seconds in findFreeBlock!" << llendl; + } + + return block; +} + +//============================================================================ +// public +//============================================================================ + +void LLVFS::pokeFiles() +{ + if (!isValid()) + { + llerrs << "Attempting to use invalid VFS!" << llendl; + } + U32 word; + + // only write data if we actually read 4 bytes + // otherwise we're writing garbage and screwing up the file + fseek(mDataFP, 0, SEEK_SET); + if (fread(&word, 1, 4, mDataFP) == 4) + { + fseek(mDataFP, 0, SEEK_SET); + fwrite(&word, 1, 4, mDataFP); + fflush(mDataFP); + } + + fseek(mIndexFP, 0, SEEK_SET); + if (fread(&word, 1, 4, mIndexFP) == 4) + { + fseek(mIndexFP, 0, SEEK_SET); + fwrite(&word, 1, 4, mIndexFP); + fflush(mIndexFP); + } +} + + +void LLVFS::dumpMap() +{ + llinfos << "Files:" << llendl; + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *file_block = (*it).second; + llinfos << "Location: " << file_block->mLocation << "\tLength: " << file_block->mLength << "\t" << file_block->mFileID << "\t" << file_block->mFileType << llendl; + } + + llinfos << "Free Blocks:" << llendl; + for (blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(), + end = mFreeBlocksByLocation.end(); + iter != end; iter++) + { + LLVFSBlock *free_block = iter->second; + llinfos << "Location: " << free_block->mLocation << "\tLength: " << free_block->mLength << llendl; + } +} + +// verify that the index file contents match the in-memory file structure +// Very slow, do not call routinely. JC +void LLVFS::audit() +{ + // Lock the mutex through this whole function. + LLMutexLock lock_data(mDataMutex); + + fflush(mIndexFP); + + fseek(mIndexFP, 0, SEEK_END); + S32 index_size = ftell(mIndexFP); + fseek(mIndexFP, 0, SEEK_SET); + + U8 *buffer = new U8[index_size]; + fread(buffer, index_size, 1, mIndexFP); + + U8 *tmp_ptr = buffer; + + std::map found_files; + U32 cur_time = (U32)time(NULL); + + BOOL vfs_corrupt = FALSE; + + std::vector audit_blocks; + while (tmp_ptr < buffer + index_size) + { + LLVFSFileBlock *block = new LLVFSFileBlock(); + audit_blocks.push_back(block); + + block->deserialize(tmp_ptr, (S32)(tmp_ptr - buffer)); + tmp_ptr += block->SERIAL_SIZE; + + // do sanity check on this block + if (block->mLength >= 0 && + block->mLocation >= 0 && + block->mSize >= 0 && + block->mSize <= block->mLength && + block->mFileType >= LLAssetType::AT_NONE && + block->mFileType < LLAssetType::AT_COUNT && + block->mAccessTime <= cur_time && + block->mFileID != LLUUID::null) + { + if (mFileBlocks.find(*block) == mFileBlocks.end()) + { + llwarns << "VFile " << block->mFileID << ":" << block->mFileType << " on disk, not in memory, loc " << block->mIndexLocation << llendl; + } + else if (found_files.find(*block) != found_files.end()) + { + std::map::iterator it; + it = found_files.find(*block); + LLVFSFileBlock* dupe = it->second; + // try to keep data from being lost + unlockAndClose(mIndexFP); + mIndexFP = NULL; + unlockAndClose(mDataFP); + mDataFP = NULL; + llwarns << "VFS: Original block index " << block->mIndexLocation + << " location " << block->mLocation + << " length " << block->mLength + << " size " << block->mSize + << " id " << block->mFileID + << " type " << block->mFileType + << llendl; + llwarns << "VFS: Duplicate block index " << dupe->mIndexLocation + << " location " << dupe->mLocation + << " length " << dupe->mLength + << " size " << dupe->mSize + << " id " << dupe->mFileID + << " type " << dupe->mFileType + << llendl; + llwarns << "VFS: Index size " << index_size << llendl; + llwarns << "VFS: INDEX CORRUPT" << llendl; + vfs_corrupt = TRUE; + break; + } + else + { + found_files[*block] = block; + } + } + else + { + if (block->mLength) + { + llwarns << "VFile " << block->mFileID << ":" << block->mFileType << " corrupt on disk" << llendl; + } + // else this is just a hole + } + } + + delete[] buffer; + + if (vfs_corrupt) + { + for (std::vector::iterator iter = audit_blocks.begin(); + iter != audit_blocks.end(); ++iter) + { + delete *iter; + } + audit_blocks.clear(); + return; + } + + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock* block = (*it).second; + + if (block->mSize > 0) + { + if (! found_files.count(*block)) + { + llwarns << "VFile " << block->mFileID << ":" << block->mFileType << " in memory, not on disk, loc " << block->mIndexLocation<< llendl; + fseek(mIndexFP, block->mIndexLocation, SEEK_SET); + U8 buf[LLVFSFileBlock::SERIAL_SIZE]; + fread(buf, LLVFSFileBlock::SERIAL_SIZE, 1, mIndexFP); + + LLVFSFileBlock disk_block; + disk_block.deserialize(buf, block->mIndexLocation); + + llwarns << "Instead found " << disk_block.mFileID << ":" << block->mFileType << llendl; + } + else + { + block = found_files.find(*block)->second; + found_files.erase(*block); + delete block; + } + } + } + + for (std::map::iterator iter = found_files.begin(); + iter != found_files.end(); iter++) + { + LLVFSFileBlock* block = iter->second; + llwarns << "VFile " << block->mFileID << ":" << block->mFileType << " szie:" << block->mSize << " leftover" << llendl; + } + + llinfos << "VFS: audit OK" << llendl; + // mutex released by LLMutexLock() destructor. +} + + +// quick check for uninitialized blocks +// Slow, do not call in release. +void LLVFS::checkMem() +{ + lockData(); + + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *block = (*it).second; + llassert(block->mFileType >= LLAssetType::AT_NONE && + block->mFileType < LLAssetType::AT_COUNT && + block->mFileID != LLUUID::null); + + for (std::deque::iterator iter = mIndexHoles.begin(); + iter != mIndexHoles.end(); ++iter) + { + S32 index_loc = *iter; + if (index_loc == block->mIndexLocation) + { + llwarns << "VFile block " << block->mFileID << ":" << block->mFileType << " is marked as a hole" << llendl; + } + } + } + + llinfos << "VFS: mem check OK" << llendl; + + unlockData(); +} + +void LLVFS::dumpLockCounts() +{ + S32 i; + for (i = 0; i < VFSLOCK_COUNT; i++) + { + llinfos << "LockType: " << i << ": " << mLockCounts[i] << llendl; + } +} + +void LLVFS::dumpStatistics() +{ + lockData(); + + // Investigate file blocks. + std::map size_counts; + std::map location_counts; + std::map > filetype_counts; + + S32 max_file_size = 0; + S32 total_file_size = 0; + S32 invalid_file_count = 0; + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileBlock *file_block = (*it).second; + if (file_block->mLength == BLOCK_LENGTH_INVALID) + { + invalid_file_count++; + } + else if (file_block->mLength <= 0) + { + llinfos << "Bad file block at: " << file_block->mLocation << "\tLength: " << file_block->mLength << "\t" << file_block->mFileID << "\t" << file_block->mFileType << llendl; + size_counts[file_block->mLength]++; + location_counts[file_block->mLocation]++; + } + else + { + total_file_size += file_block->mLength; + } + + if (file_block->mLength > max_file_size) + { + max_file_size = file_block->mLength; + } + + filetype_counts[file_block->mFileType].first++; + filetype_counts[file_block->mFileType].second += file_block->mLength; + } + + for (std::map::iterator it = size_counts.begin(); it != size_counts.end(); ++it) + { + S32 size = it->first; + S32 size_count = it->second; + llinfos << "Bad files size " << size << " count " << size_count << llendl; + } + for (std::map::iterator it = location_counts.begin(); it != location_counts.end(); ++it) + { + U32 location = it->first; + S32 location_count = it->second; + llinfos << "Bad files location " << location << " count " << location_count << llendl; + } + + // Investigate free list. + S32 max_free_size = 0; + S32 total_free_size = 0; + std::map free_length_counts; + for (blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(), + end = mFreeBlocksByLocation.end(); + iter != end; iter++) + { + LLVFSBlock *free_block = iter->second; + if (free_block->mLength <= 0) + { + llinfos << "Bad free block at: " << free_block->mLocation << "\tLength: " << free_block->mLength << llendl; + } + else + { + llinfos << "Block: " << free_block->mLocation + << "\tLength: " << free_block->mLength + << "\tEnd: " << free_block->mLocation + free_block->mLength + << llendl; + total_free_size += free_block->mLength; + } + + if (free_block->mLength > max_free_size) + { + max_free_size = free_block->mLength; + } + + free_length_counts[free_block->mLength]++; + } + + // Dump histogram of free block sizes + for (std::map::iterator it = free_length_counts.begin(); it != free_length_counts.end(); ++it) + { + llinfos << "Free length " << it->first << " count " << it->second << llendl; + } + + llinfos << "Invalid blocks: " << invalid_file_count << llendl; + llinfos << "File blocks: " << mFileBlocks.size() << llendl; + + S32 length_list_count = (S32)mFreeBlocksByLength.size(); + S32 location_list_count = (S32)mFreeBlocksByLocation.size(); + if (length_list_count == location_list_count) + { + llinfos << "Free list lengths match, free blocks: " << location_list_count << llendl; + } + else + { + llwarns << "Free list lengths do not match!" << llendl; + llwarns << "By length: " << length_list_count << llendl; + llwarns << "By location: " << location_list_count << llendl; + } + llinfos << "Max file: " << max_file_size/1024 << "K" << llendl; + llinfos << "Max free: " << max_free_size/1024 << "K" << llendl; + llinfos << "Total file size: " << total_file_size/1024 << "K" << llendl; + llinfos << "Total free size: " << total_free_size/1024 << "K" << llendl; + llinfos << "Sum: " << (total_file_size + total_free_size) << " bytes" << llendl; + llinfos << llformat("%.0f%% full",((F32)(total_file_size)/(F32)(total_file_size+total_free_size))*100.f) << llendl; + + llinfos << " " << llendl; + for (std::map >::iterator iter = filetype_counts.begin(); + iter != filetype_counts.end(); ++iter) + { + llinfos << "Type: " << LLAssetType::getDesc(iter->first) + << " Count: " << iter->second.first + << " Bytes: " << (iter->second.second>>20) << " MB" << llendl; + } + + // Look for potential merges + { + blocks_location_map_t::iterator iter = mFreeBlocksByLocation.begin(); + blocks_location_map_t::iterator end = mFreeBlocksByLocation.end(); + LLVFSBlock *first_block = iter->second; + while(iter != end) + { + if (++iter == end) + break; + LLVFSBlock *second_block = iter->second; + if (first_block->mLocation + first_block->mLength == second_block->mLocation) + { + llinfos << "Potential merge at " << first_block->mLocation << llendl; + } + first_block = second_block; + } + } + unlockData(); +} + +// Debug Only! +#include "llapr.h" +void LLVFS::dumpFiles() +{ + lockData(); + + for (fileblock_map::iterator it = mFileBlocks.begin(); it != mFileBlocks.end(); ++it) + { + LLVFSFileSpecifier file_spec = it->first; + LLVFSFileBlock *file_block = it->second; + S32 length = file_block->mLength; + S32 size = file_block->mSize; + if (length != BLOCK_LENGTH_INVALID && size > 0) + { + LLUUID id = file_spec.mFileID; + LLAssetType::EType type = file_spec.mFileType; + U8* buffer = new U8[size]; + + unlockData(); + getData(id, type, buffer, 0, size); + lockData(); + + LLString extention = ".data"; + switch(type) + { + case LLAssetType::AT_TEXTURE: + extention = ".jp2"; // ".j2c"; // IrfanView recognizes .jp2 -sjb + break; + default: + break; + } + LLString filename = id.getString() + extention; + llinfos << " Writing " << filename << llendl; + apr_file_t* file = ll_apr_file_open(filename, LL_APR_WB); + ll_apr_file_write(file, buffer, size); + apr_file_close(file); + delete[] buffer; + } + } + + unlockData(); +} + +//============================================================================ +// protected +//============================================================================ + +// static +FILE *LLVFS::openAndLock(const char *filename, const char *mode, BOOL read_lock) +{ +#if LL_WINDOWS + + return LLFile::_fsopen(filename, mode, (read_lock ? _SH_DENYWR : _SH_DENYRW)); + +#else + + FILE *fp; + int fd; + + // first test the lock in a non-destructive way + if (strstr(mode, "w")) + { + fp = LLFile::fopen(filename, "rb"); + if (fp) + { + fd = fileno(fp); + if (flock(fd, (read_lock ? LOCK_SH : LOCK_EX) | LOCK_NB) == -1) + { + fclose(fp); + return NULL; + } + + fclose(fp); + } + } + + // now actually open the file for use + fp = LLFile::fopen(filename, mode); + if (fp) + { + fd = fileno(fp); + if (flock(fd, (read_lock ? LOCK_SH : LOCK_EX) | LOCK_NB) == -1) + { + fclose(fp); + fp = NULL; + } + } + + return fp; + +#endif +} + +// static +void LLVFS::unlockAndClose(FILE *fp) +{ + if (fp) + { + // IW: we don't actually want to unlock on linux + // this is because a forked process can kill the parent's lock + // with an explicit unlock + // however, fclose() will implicitly remove the lock + // but only once both parent and child have closed the file + /* + #if !LL_WINDOWS + int fd = fileno(fp); + flock(fd, LOCK_UN); + #endif + */ + + fclose(fp); + } +} diff --git a/indra/llvfs/llvfs.h b/indra/llvfs/llvfs.h new file mode 100644 index 0000000000..c3fb0cdf22 --- /dev/null +++ b/indra/llvfs/llvfs.h @@ -0,0 +1,151 @@ +/** + * @file llvfs.h + * @brief Definition of virtual file system + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVFS_H +#define LL_LLVFS_H + +#include +#include +#include +#include "lluuid.h" +#include "linked_lists.h" +#include "llassettype.h" +#include "llthread.h" + +enum EVFSValid +{ + VFSVALID_UNKNOWN = 0, + VFSVALID_OK = 1, + VFSVALID_BAD_CORRUPT = 2, + VFSVALID_BAD_CANNOT_OPEN_READONLY = 3, + VFSVALID_BAD_CANNOT_CREATE = 4 +}; + +// Lock types for open vfiles, pending async reads, and pending async appends +// (There are no async normal writes, currently) +enum EVFSLock +{ + VFSLOCK_OPEN = 0, + VFSLOCK_READ = 1, + VFSLOCK_APPEND = 2, + + VFSLOCK_COUNT = 3 +}; + +// internal classes +class LLVFSBlock; +class LLVFSFileBlock; +class LLVFSFileSpecifier +{ +public: + LLVFSFileSpecifier(); + LLVFSFileSpecifier(const LLUUID &file_id, const LLAssetType::EType file_type); + bool operator<(const LLVFSFileSpecifier &rhs) const; + bool operator==(const LLVFSFileSpecifier &rhs) const; + +public: + LLUUID mFileID; + LLAssetType::EType mFileType; +}; + +class LLVFS +{ +public: + // Pass 0 to not presize + LLVFS(const char *index_filename, const char *data_filename, const BOOL read_only, const U32 presize, const BOOL remove_after_crash); + ~LLVFS(); + + BOOL isValid() const { return (VFSVALID_OK == mValid); } + EVFSValid getValidState() const { return mValid; } + + // ---------- The following fucntions lock/unlock mDataMutex ---------- + BOOL getExists(const LLUUID &file_id, const LLAssetType::EType file_type); + S32 getSize(const LLUUID &file_id, const LLAssetType::EType file_type); + + BOOL checkAvailable(S32 max_size); + + S32 getMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type); + BOOL setMaxSize(const LLUUID &file_id, const LLAssetType::EType file_type, S32 max_size); + + void renameFile(const LLUUID &file_id, const LLAssetType::EType file_type, + const LLUUID &new_id, const LLAssetType::EType &new_type); + void removeFile(const LLUUID &file_id, const LLAssetType::EType file_type); + + S32 getData(const LLUUID &file_id, const LLAssetType::EType file_type, U8 *buffer, S32 location, S32 length); + S32 storeData(const LLUUID &file_id, const LLAssetType::EType file_type, const U8 *buffer, S32 location, S32 length); + + void incLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); + void decLock(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); + BOOL isLocked(const LLUUID &file_id, const LLAssetType::EType file_type, EVFSLock lock); + // ---------------------------------------------------------------- + + // Used to trigger evil WinXP behavior of "preloading" entire file into memory. + void pokeFiles(); + + // Verify that the index file contents match the in-memory file structure + // Very slow, do not call routinely. JC + void audit(); + // Check for uninitialized blocks. Slow, do not call in release. JC + void checkMem(); + // for debugging, prints a map of the vfs + void dumpMap(); + void dumpLockCounts(); + void dumpStatistics(); + void dumpFiles(); + +protected: + void removeFileBlock(LLVFSFileBlock *fileblock); + + void eraseBlockLength(LLVFSBlock *block); + void eraseBlock(LLVFSBlock *block); + void addFreeBlock(LLVFSBlock *block); + //void mergeFreeBlocks(); + void useFreeSpace(LLVFSBlock *free_block, S32 length); + void sync(LLVFSFileBlock *block, BOOL remove = FALSE); + void presizeDataFile(const U32 size); + + static FILE *openAndLock(const char *filename, const char *mode, BOOL read_lock); + static void unlockAndClose(FILE *fp); + + // Can initiate LRU-based file removal to make space. + // The immune file block will not be removed. + LLVFSBlock *findFreeBlock(S32 size, LLVFSFileBlock *immune = NULL); + + // lock/unlock data mutex (mDataMutex) + void lockData() { mDataMutex->lock(); } + void unlockData() { mDataMutex->unlock(); } + +protected: + LLMutex* mDataMutex; + + typedef std::map fileblock_map; + fileblock_map mFileBlocks; + + typedef std::multimap blocks_length_map_t; + blocks_length_map_t mFreeBlocksByLength; + typedef std::multimap blocks_location_map_t; + blocks_location_map_t mFreeBlocksByLocation; + + FILE *mDataFP; + FILE *mIndexFP; + + std::deque mIndexHoles; + + char *mIndexFilename; + char *mDataFilename; + BOOL mReadOnly; + + EVFSValid mValid; + + S32 mLockCounts[VFSLOCK_COUNT]; + BOOL mRemoveAfterCrash; +}; + +extern LLVFS *gVFS; + +#endif diff --git a/indra/llvfs/llvfsthread.cpp b/indra/llvfs/llvfsthread.cpp new file mode 100644 index 0000000000..8ea98ab462 --- /dev/null +++ b/indra/llvfs/llvfsthread.cpp @@ -0,0 +1,294 @@ +/** + * @file llvfsthread.cpp + * @brief LLVFSThread implementation + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "llmath.h" +#include "llvfsthread.h" +#include "llstl.h" + +//============================================================================ + +/*static*/ std::string LLVFSThread::sDataPath = ""; + +/*static*/ LLVFSThread* LLVFSThread::sLocal = NULL; + +//============================================================================ +// Run on MAIN thread +//static +void LLVFSThread::initClass(bool local_is_threaded, bool local_run_always) +{ + llassert(sLocal == NULL); + sLocal = new LLVFSThread(local_is_threaded, local_run_always); +} + +//static +S32 LLVFSThread::updateClass(U32 ms_elapsed) +{ + sLocal->update(ms_elapsed); + return sLocal->getPending(); +} + +//static +void LLVFSThread::cleanupClass() +{ + sLocal->setQuitting(); + while (sLocal->getPending()) + { + sLocal->update(0); + } + delete sLocal; + sLocal = 0; +} + +//---------------------------------------------------------------------------- + +LLVFSThread::LLVFSThread(bool threaded, bool runalways) : + LLQueuedThread("VFS", threaded, runalways) +{ +} + +LLVFSThread::~LLVFSThread() +{ + // ~LLQueuedThread() will be called here +} + +//---------------------------------------------------------------------------- + +LLVFSThread::handle_t LLVFSThread::read(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 priority, U32 flags) +{ + handle_t handle = generateHandle(); + + priority = llmax(priority, (U32)PRIORITY_LOW); // All reads are at least PRIORITY_LOW + Request* req = new Request(handle, priority, flags, FILE_READ, vfs, file_id, file_type, + buffer, offset, numbytes); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +S32 LLVFSThread::readImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, FILE_READ, vfs, file_id, file_type, + buffer, offset, numbytes); + + S32 res = addRequest(req) ? 1 : 0; + if (res == 0) + { + llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + } + else + { + llverify(waitForResult(handle, false) == true); + res = req->getBytesRead(); + completeRequest(handle); + } + return res; +} + +LLVFSThread::handle_t LLVFSThread::write(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 flags) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, 0, flags, FILE_WRITE, vfs, file_id, file_type, + buffer, offset, numbytes); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +S32 LLVFSThread::writeImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes) +{ + handle_t handle = generateHandle(); + + Request* req = new Request(handle, PRIORITY_IMMEDIATE, 0, FILE_WRITE, vfs, file_id, file_type, + buffer, offset, numbytes); + + S32 res = addRequest(req) ? 1 : 0; + if (res == 0) + { + llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + } + else + { + llverify(waitForResult(handle, false) == true); + res = req->getBytesRead(); + completeRequest(handle); + } + return res; +} + + +LLVFSThread::handle_t LLVFSThread::rename(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + const LLUUID &new_id, const LLAssetType::EType new_type, U32 flags) +{ + handle_t handle = generateHandle(); + + LLUUID* new_idp = new LLUUID(new_id); // deleted with Request + // new_type is passed as "numbytes" + Request* req = new Request(handle, 0, flags, FILE_RENAME, vfs, file_id, file_type, + (U8*)new_idp, 0, (S32)new_type); + + bool res = addRequest(req); + if (!res) + { + llerrs << "LLVFSThread::read called after LLVFSThread::cleanupClass()" << llendl; + req->deleteRequest(); + handle = nullHandle(); + } + + return handle; +} + +//============================================================================ +// Runs on its OWN thread + +bool LLVFSThread::processRequest(QueuedRequest* qreq) +{ + Request *req = (Request*)qreq; + + bool complete = req->processIO(); + + return complete; +} + +//============================================================================ + +LLVFSThread::Request::Request(handle_t handle, U32 priority, U32 flags, + operation_t op, LLVFS* vfs, + const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes) : + QueuedRequest(handle, priority, flags), + mOperation(op), + mVFS(vfs), + mFileID(file_id), + mFileType(file_type), + mBuffer(buffer), + mOffset(offset), + mBytes(numbytes), + mBytesRead(0) +{ + llassert(mBuffer); + + if (numbytes <= 0 && mOperation != FILE_RENAME) + { + llwarns << "LLVFSThread: Request with numbytes = " << numbytes + << " operation = " << op + << " offset " << offset + << " file_type " << file_type << llendl; + } + if (mOperation == FILE_WRITE) + { + S32 blocksize = mVFS->getMaxSize(mFileID, mFileType); + if (blocksize < 0) + { + llwarns << "VFS write to temporary block (shouldn't happen)" << llendl; + } + mVFS->incLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else if (mOperation == FILE_RENAME) + { + mVFS->incLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else // if (mOperation == FILE_READ) + { + mVFS->incLock(mFileID, mFileType, VFSLOCK_READ); + } +} + +// dec locks as soon as a request finishes +void LLVFSThread::Request::finishRequest() +{ + if (mOperation == FILE_WRITE) + { + mVFS->decLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else if (mOperation == FILE_RENAME) + { + mVFS->decLock(mFileID, mFileType, VFSLOCK_APPEND); + } + else // if (mOperation == FILE_READ) + { + mVFS->decLock(mFileID, mFileType, VFSLOCK_READ); + } +} + +void LLVFSThread::Request::deleteRequest() +{ + if (getStatus() == STATUS_QUEUED || getStatus() == STATUS_ABORT) + { + llerrs << "Attempt to delete a queued LLVFSThread::Request!" << llendl; + } + if (mOperation == FILE_WRITE) + { + if (mFlags & AUTO_DELETE) + { + delete [] mBuffer; + } + } + else if (mOperation == FILE_RENAME) + { + LLUUID* new_idp = (LLUUID*)mBuffer; + delete new_idp; + } + LLQueuedThread::QueuedRequest::deleteRequest(); +} + +bool LLVFSThread::Request::processIO() +{ + bool complete = false; + if (mOperation == FILE_READ) + { + llassert(mOffset >= 0); + mBytesRead = mVFS->getData(mFileID, mFileType, mBuffer, mOffset, mBytes); + complete = true; + //llinfos << llformat("LLVFSThread::READ '%s': %d bytes arg:%d",getFilename(),mBytesRead) << llendl; + } + else if (mOperation == FILE_WRITE) + { + mBytesRead = mVFS->storeData(mFileID, mFileType, mBuffer, mOffset, mBytes); + complete = true; + //llinfos << llformat("LLVFSThread::WRITE '%s': %d bytes arg:%d",getFilename(),mBytesRead) << llendl; + } + else if (mOperation == FILE_RENAME) + { + LLUUID* new_idp = (LLUUID*)mBuffer; + LLAssetType::EType new_type = (LLAssetType::EType)mBytes; + mVFS->renameFile(mFileID, mFileType, *new_idp, new_type); + complete = true; + //llinfos << llformat("LLVFSThread::WRITE '%s': %d bytes arg:%d",getFilename(),mBytesRead) << llendl; + } + else + { + llerrs << llformat("LLVFSThread::unknown operation: %d", mOperation) << llendl; + } + return complete; +} + +//============================================================================ diff --git a/indra/llvfs/llvfsthread.h b/indra/llvfs/llvfsthread.h new file mode 100644 index 0000000000..14a2fe0ba7 --- /dev/null +++ b/indra/llvfs/llvfsthread.h @@ -0,0 +1,125 @@ +/** + * @file llvfsthread.h + * @brief LLVFSThread definition + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLVFSTHREAD_H +#define LL_LLVFSTHREAD_H + +#include +#include +#include +#include + +#include "llapr.h" + +#include "llqueuedthread.h" + +#include "llvfs.h" + +//============================================================================ + +class LLVFSThread : public LLQueuedThread +{ + //------------------------------------------------------------------------ +public: + enum operation_t { + FILE_READ, + FILE_WRITE, + FILE_RENAME + }; + + //------------------------------------------------------------------------ +public: + + class Request : public QueuedRequest + { + protected: + ~Request() {}; // use deleteRequest() + + public: + Request(handle_t handle, U32 priority, U32 flags, + operation_t op, LLVFS* vfs, + const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes); + + S32 getBytesRead() + { + return mBytesRead; + } + S32 getOperation() + { + return mOperation; + } + U8* getBuffer() + { + return mBuffer; + } + LLVFS* getVFS() + { + return mVFS; + } + std::string getFilename() + { + char tbuf[40]; + mFileID.toString(tbuf); + return std::string(tbuf); + } + + /*virtual*/ void finishRequest(); + /*virtual*/ void deleteRequest(); + + bool processIO(); + + private: + operation_t mOperation; + + LLVFS* mVFS; + LLUUID mFileID; + LLAssetType::EType mFileType; + + U8* mBuffer; // dest for reads, source for writes, new UUID for rename + S32 mOffset; // offset into file, -1 = append (WRITE only) + S32 mBytes; // bytes to read from file, -1 = all (new mFileType for rename) + S32 mBytesRead; // bytes read from file + }; + + //------------------------------------------------------------------------ +public: + static std::string sDataPath; + static void setDataPath(const std::string& path) { sDataPath = path; } + +public: + LLVFSThread(bool threaded = TRUE, bool runalways = TRUE); + ~LLVFSThread(); + + // Return a Request handle + handle_t read(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 pri=PRIORITY_NORMAL, U32 flags = 0); + handle_t write(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes, U32 flags); + handle_t rename(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + const LLUUID &new_id, const LLAssetType::EType new_type, U32 flags); + // Return number of bytes read + S32 readImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes); + S32 writeImmediate(LLVFS* vfs, const LLUUID &file_id, const LLAssetType::EType file_type, + U8* buffer, S32 offset, S32 numbytes); + + /*virtual*/ bool processRequest(QueuedRequest* req); + + static void initClass(bool local_is_threaded = TRUE, bool run_always = TRUE); // Setup sLocal + static S32 updateClass(U32 ms_elapsed); + static void cleanupClass(); // Delete sLocal + +public: + static LLVFSThread* sLocal; // Default worker thread +}; + +//============================================================================ + + +#endif // LL_LLVFSTHREAD_H -- cgit v1.2.3