
/** 
 * @file lldir.cpp
 * @brief implementation of directory utilities base class
 *
 * $LicenseInfo:firstyear=2002&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"

#if !LL_WINDOWS
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#else
#include <direct.h>
#endif

#include "lldir.h"

#include "llerror.h"
#include "lltimer.h"	// ms_sleep()
#include "lluuid.h"

#include "lldiriterator.h"
#include "stringize.h"
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <algorithm>

using boost::assign::list_of;
using boost::assign::map_list_of;

#if LL_WINDOWS
#include "lldir_win32.h"
LLDir_Win32 gDirUtil;
#elif LL_DARWIN
#include "lldir_mac.h"
LLDir_Mac gDirUtil;
#elif LL_SOLARIS
#include "lldir_solaris.h"
LLDir_Solaris gDirUtil;
#else
#include "lldir_linux.h"
LLDir_Linux gDirUtil;
#endif

LLDir *gDirUtilp = (LLDir *)&gDirUtil;

static const char* const empty = "";
std::string LLDir::sDumpDir = "";
LLDir::LLDir()
:	mAppName(""),
	mExecutablePathAndName(""),
	mExecutableFilename(""),
	mExecutableDir(""),
	mAppRODataDir(""),
	mOSUserDir(""),
	mOSUserAppDir(""),
	mLindenUserDir(""),
	mOSCacheDir(""),
	mCAFile(""),
	mTempDir(""),
	mDirDelimiter("/") // fallback to forward slash if not overridden
{
}

LLDir::~LLDir()
{
}

std::vector<std::string> LLDir::getFilesInDir(const std::string &dirname)
{
    //Returns a vector of fullpath filenames.
    
    boost::filesystem::path p (dirname);
    std::vector<std::string> v;
    
    if (exists(p))
    {
        if (is_directory(p))
        {
            boost::filesystem::directory_iterator end_iter;
            for (boost::filesystem::directory_iterator dir_itr(p);
                 dir_itr != end_iter;
                 ++dir_itr)
            {
                if (boost::filesystem::is_regular_file(dir_itr->status()))
                {
                    v.push_back(dir_itr->path().filename().string());
                }
            }
        }
    }
    return v;
}   
            
S32 LLDir::deleteFilesInDir(const std::string &dirname, const std::string &mask)
{
	S32 count = 0;
	std::string filename; 
	std::string fullpath;
	S32 result;

	// File masks starting with "/" will match nothing, so we consider them invalid.
	if (LLStringUtil::startsWith(mask, getDirDelimiter()))
	{
		llwarns << "Invalid file mask: " << mask << llendl;
		llassert(!"Invalid file mask");
	}

	try
	{
		LLDirIterator iter(dirname, mask);
		while (iter.next(filename))
		{
			fullpath = add(dirname, filename);

			if(LLFile::isdir(fullpath))
			{
				// skipping directory traversal filenames
				count++;
				continue;
			}

			S32 retry_count = 0;
			while (retry_count < 5)
			{
				if (0 != LLFile::remove(fullpath))
				{
					retry_count++;
					result = errno;
					llwarns << "Problem removing " << fullpath << " - errorcode: "
						<< result << " attempt " << retry_count << llendl;

					if(retry_count >= 5)
					{
						llwarns << "Failed to remove " << fullpath << llendl ;
						return count ;
					}

					ms_sleep(100);
				}
				else
				{
					if (retry_count)
					{
						llwarns << "Successfully removed " << fullpath << llendl;
					}
					break;
				}			
			}
			count++;
		}
	}
	catch(...)
	{
		llwarns << "Unable to remove some files from " + dirname  << llendl;
	}

	return count;
}

U32 LLDir::deleteDirAndContents(const std::string& dir_name)
{
	//Removes the directory and its contents.  Returns number of files removed.
	// Singu Note: boost::filesystem throws exceptions
	S32 res = 0;

	try 
	{
		res = boost::filesystem::remove_all(dir_name);
	}
	catch(const boost::filesystem::filesystem_error& e)
	{
		llwarns << "boost::filesystem::remove_all(\"" + dir_name + "\") failed: '" + e.code().message() + "'" << llendl;
	}

	return res;
}

const std::string LLDir::findFile(const std::string &filename, 
						   const std::string& searchPath1, 
						   const std::string& searchPath2, 
						   const std::string& searchPath3) const
{
	std::vector<std::string> search_paths;
	search_paths.push_back(searchPath1);
	search_paths.push_back(searchPath2);
	search_paths.push_back(searchPath3);
	return findFile(filename, search_paths);
}

const std::string LLDir::findFile(const std::string& filename, const std::vector<std::string> search_paths) const
{
	std::vector<std::string>::const_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);
			if (!filename.empty())
			{
				filename_and_path += 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(bool empty_ok) const
{
	llassert(empty_ok || !mLindenUserDir.empty());
	return mLindenUserDir;
}

const std::string &LLDir::getChatLogsDir() const
{
	return mChatLogsDir;
}

void LLDir::setDumpDir( const std::string& path )
{
    LLDir::sDumpDir = path;
    if (! sDumpDir.empty() && sDumpDir.rbegin() == mDirDelimiter.rbegin() )
    {
        sDumpDir.erase(sDumpDir.size() -1);
    }
}

const std::string &LLDir::getDumpDir() const
{
    if (sDumpDir.empty() )
    {
		/* Singu Note: don't generate a different dump dir each time
        LLUUID uid;
        uid.generate();
        
        sDumpDir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "")
                    + "dump-" + uid.asString();
		*/

		sDumpDir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "") + "singularity-debug";
        dir_exists_or_crash(sDumpDir);  
    }

	return LLDir::sDumpDir;
}

const std::string &LLDir::getPerAccountChatLogsDir() const
{
	return mPerAccountChatLogsDir;
}

const std::string &LLDir::getTempDir() const
{
	return mTempDir;
}

const std::string  LLDir::getCacheDir(bool get_default) const
{
	if (mCacheDir.empty() || get_default)
	{
		if (!mDefaultCacheDir.empty())
		{	// Set at startup - can't set here due to const API
			return mDefaultCacheDir;
		}
		
		std::string res = buildSLOSCacheDir();
		return res;
	}
	else
	{
		return mCacheDir;
	}
}

// Return the default cache directory
std::string LLDir::buildSLOSCacheDir() const
{
	std::string res;
	if (getOSCacheDir().empty())
	{
		if (getOSUserAppDir().empty())
		{
			res = "data";
		}
		else
		{
			res = add(getOSUserAppDir(), "cache_sg1");
		}
	}
	else
	{
#if defined(_WIN64)
		res = add(getOSCacheDir(), "SingularityViewer64");
#else
		res = add(getOSCacheDir(), "SingularityViewer");
#endif
	}
	return res;
}



const std::string &LLDir::getOSCacheDir() const
{
	return mOSCacheDir;
}


const std::string &LLDir::getCAFile() const
{
	return mCAFile;
}

const std::string &LLDir::getDirDelimiter() const
{
	return mDirDelimiter;
}

const std::string &LLDir::getSkinDir() const
{
	return mSkinDir;
}

const std::string &LLDir::getUserSkinDir() const
{
	return mUserSkinDir;
}

const std::string& LLDir::getDefaultSkinDir() const
{
	return mDefaultSkinDir;
}

const std::string LLDir::getSkinBaseDir() const
{
	return mSkinBaseDir;
}

const std::string &LLDir::getLLPluginDir() const
{
	return mLLPluginDir;
}

static std::string ELLPathToString(ELLPath location)
{
	typedef std::map<ELLPath, const char*> ELLPathMap;
#define ENT(symbol) (symbol, #symbol)
	static const ELLPathMap sMap = map_list_of
		ENT(LL_PATH_NONE)
		ENT(LL_PATH_USER_SETTINGS)
		ENT(LL_PATH_APP_SETTINGS)
		ENT(LL_PATH_PER_SL_ACCOUNT) // returns/expands to blank string if we don't know the account name yet
		ENT(LL_PATH_CACHE)
		ENT(LL_PATH_CHARACTER)
		ENT(LL_PATH_HELP)
		ENT(LL_PATH_LOGS)
		ENT(LL_PATH_TEMP)
		ENT(LL_PATH_SKINS)
		ENT(LL_PATH_TOP_SKIN)
		ENT(LL_PATH_CHAT_LOGS)
		ENT(LL_PATH_PER_ACCOUNT_CHAT_LOGS)
		ENT(LL_PATH_USER_SKIN)
		ENT(LL_PATH_LOCAL_ASSETS)
		ENT(LL_PATH_EXECUTABLE)
		ENT(LL_PATH_DEFAULT_SKIN)
		ENT(LL_PATH_FONTS)
		ENT(LL_PATH_LAST)
	;
#undef ENT

	ELLPathMap::const_iterator found = sMap.find(location);
	if (found != sMap.end())
		return found->second;
	return STRINGIZE("Invalid ELLPath value " << location);
}

std::string LLDir::getExpandedFilename(ELLPath location, const std::string& filename) const
{
	return getExpandedFilename(location, "", filename);
}

std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subdir, const std::string& filename) const
{
	return getExpandedFilename(location, "", subdir, filename);
}

std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subdir1, const std::string& subdir2, const std::string& in_filename) const
{
	std::string prefix;
	switch (location)
	{
	case LL_PATH_NONE:
		// Do nothing
		break;

	case LL_PATH_APP_SETTINGS:
		prefix = add(getAppRODataDir(), "app_settings");
		break;
	
	case LL_PATH_CHARACTER:
		prefix = add(getAppRODataDir(), "character");
		break;
		
	case LL_PATH_HELP:
		prefix = "help";
		break;
		
	case LL_PATH_CACHE:
		prefix = getCacheDir();
		break;
		
    case LL_PATH_DUMP:
        prefix=getDumpDir();
        break;
            
	case LL_PATH_USER_SETTINGS:
		prefix = add(getOSUserAppDir(), "user_settings");
		break;

	case LL_PATH_PER_SL_ACCOUNT:
		prefix = getLindenUserDir();
		if (prefix.empty())
		{
			// if we're asking for the per-SL-account directory but we haven't
			// logged in yet (or otherwise don't know the account name from
			// which to build this string), then intentionally return a blank
			// string to the caller and skip the below warning about a blank
			// prefix.
			LL_DEBUGS("LLDir") << "getLindenUserDir() not yet set: "
							   << ELLPathToString(location)
							   << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
							   << "' => ''" << LL_ENDL;
			return std::string();
		}
		break;
		
	case LL_PATH_CHAT_LOGS:
		prefix = getChatLogsDir();
		break;
		
	case LL_PATH_PER_ACCOUNT_CHAT_LOGS:
		prefix = getPerAccountChatLogsDir();
		break;

	case LL_PATH_LOGS:
		prefix = add(getOSUserAppDir(), "logs");
		break;

	case LL_PATH_TEMP:
		prefix = getTempDir();
		break;

	case LL_PATH_TOP_SKIN:
		prefix = getSkinDir();
		break;

	case LL_PATH_DEFAULT_SKIN:
		prefix = getDefaultSkinDir();
		break;

	case LL_PATH_USER_SKIN:
		prefix = getUserSkinDir();
		break;

	case LL_PATH_SKINS:
		prefix = getSkinBaseDir();
		break;

	case LL_PATH_LOCAL_ASSETS:
		prefix = add(getAppRODataDir(), "local_assets");
		break;

	case LL_PATH_EXECUTABLE:
		prefix = getExecutableDir();
		break;
		
	case LL_PATH_FONTS:
		prefix = add(getAppRODataDir(), "fonts");
		break;
		
	default:
		llassert(0);
	}

	if (prefix.empty())
	{
		llwarns << ELLPathToString(location)
				<< ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
				<< "': prefix is empty, possible bad filename" << llendl;
	}

	std::string expanded_filename = add(add(prefix, subdir1), subdir2);
	if (expanded_filename.empty() && in_filename.empty())
	{
		return "";
	}
	// Use explicit concatenation here instead of another add() call. Callers
	// passing in_filename as "" expect to obtain a pathname ending with
	// mDirSeparator so they can later directly concatenate with a specific
	// filename. A caller using add() doesn't care, but there's still code
	// loose in the system that uses std::string::operator+().
	expanded_filename += mDirDelimiter;
	expanded_filename += in_filename;

	LL_DEBUGS("LLDir") << ELLPathToString(location)
					   << ", '" << subdir1 << "', '" << subdir2 << "', '" << in_filename
					   << "' => '" << expanded_filename << "'" << LL_ENDL;
	return expanded_filename;
}

std::string LLDir::getBaseFileName(const std::string& filepath, bool strip_exten) const
{
	std::size_t offset = filepath.find_last_of(getDirDelimiter());
	offset = (offset == std::string::npos) ? 0 : offset+1;
	std::string res = filepath.substr(offset, std::string::npos);
	if (strip_exten)
	{
		offset = res.find_last_of('.');
		if (offset != std::string::npos &&
		    offset != 0) // if basename STARTS with '.', don't strip
		{
			res = res.substr(0, offset);
		}
	}
	return res;
}

std::string LLDir::getDirName(const std::string& filepath) const
{
	std::size_t offset = filepath.find_last_of(getDirDelimiter());
	S32 len = (offset == std::string::npos) ? 0 : offset;
	std::string dirname = filepath.substr(0, len);
	return dirname;
}

std::string LLDir::getExtension(const std::string& filepath) const
{
	if (filepath.empty())
		return std::string();
	std::string basename = getBaseFileName(filepath, false);
	std::size_t offset = basename.find_last_of('.');
	std::string exten = (offset == std::string::npos || offset == 0) ? "" : basename.substr(offset+1);
	LLStringUtil::toLower(exten);
	return exten;
}

std::string LLDir::findSkinnedFilename(const std::string &filename) const
{
	return findSkinnedFilename("", "", filename);
}

std::string LLDir::findSkinnedFilename(const std::string &subdir, const std::string &filename) const
{
	return findSkinnedFilename("", subdir, filename);
}

std::string LLDir::findSkinnedFilename(const std::string &subdir1, const std::string &subdir2, const std::string &filename) const
{
	// generate subdirectory path fragment, e.g. "/foo/bar", "/foo", ""
	std::string subdirs = ((subdir1.empty() ? "" : mDirDelimiter) + subdir1)
						 + ((subdir2.empty() ? "" : mDirDelimiter) + subdir2);

	std::vector<std::string> search_paths;
	
	search_paths.push_back(getUserSkinDir() + subdirs);		// first look in user skin override
	search_paths.push_back(getSkinDir() + subdirs);			// then in current skin
	search_paths.push_back(getDefaultSkinDir() + subdirs);  // then default skin
	search_paths.push_back(getCacheDir() + subdirs);		// and last in preload directory

	std::string found_file = findFile(filename, search_paths);
	return found_file;
}

std::string LLDir::getTempFilename() const
{
	LLUUID random_uuid;
	std::string uuid_str;

	random_uuid.generate();
	random_uuid.toString(uuid_str);

	return add(getTempDir(), uuid_str + ".tmp");
}

// static
std::string LLDir::getScrubbedFileName(const std::string uncleanFileName)
{
	std::string name(uncleanFileName);
	std::string illegalChars(getForbiddenFileChars());
#if LL_LINUX || LL_SOLARIS
	// Spaces in filenames are REALLY annoying on UNIX.
	illegalChars += ' ';
#endif
	// replace any illegal file chars with and underscore '_'
	for( unsigned int i = 0; i < illegalChars.length(); i++ )
	{
		int j = -1;
		while((j = name.find(illegalChars[i])) > -1)
		{
			name[j] = '_';
		}
	}
	return name;
}

// static
std::string LLDir::getForbiddenFileChars()
{
	return "\\/:*?\"<>|";
}

void LLDir::setLindenUserDir(const std::string &grid, 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.
		std::string userlower(first+"_"+last);
		LLStringUtil::toLower(userlower);
		mLindenUserDir = add(getOSUserAppDir(), userlower);
		
		if (!grid.empty())
		{
			std::string gridlower(grid);
			LLStringUtil::toLower(gridlower);
			mLindenUserDir += "@";
			mLindenUserDir += gridlower;
		}		
	}
	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 &grid, 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.
		std::string userlower(first+"_"+last);
		LLStringUtil::toLower(userlower);
		mPerAccountChatLogsDir = add(getChatLogsDir(), userlower);
		if (!grid.empty())
		{
			std::string gridlower(grid);
			LLStringUtil::toLower(gridlower);
			mPerAccountChatLogsDir += "@";
			mPerAccountChatLogsDir += gridlower;
		}
	}
	else
	{
		llwarns << "Invalid name for LLDir::setPerAccountChatLogsDir" << llendl;
	}
}

void LLDir::setSkinFolder(const std::string &skin_folder)
{
	mSkinDir = getSkinBaseDir();
	append(mSkinDir, skin_folder);

	// user modifications to current skin
	// e.g. c:\documents and settings\users\username\application data\second life\skins\dazzle
	mUserSkinDir = getOSUserAppDir();
	append(mUserSkinDir, "skins_sg1");
	append(mUserSkinDir, skin_folder);

	// base skin which is used as fallback for all skinned files
	// e.g. c:\program files\secondlife\skins\default
	mDefaultSkinDir = getSkinBaseDir();
	append(mDefaultSkinDir, "default");
}

bool LLDir::setCacheDir(const std::string &path)
{
	if (path.empty() )
	{
		// reset to default
		mCacheDir = "";
		return true;
	}
	else
	{
		LLFile::mkdir(path);
		std::string tempname = add(path, "temp");
		LLFILE* file = LLFile::fopen(tempname,"wt");
		if (file)
		{
			fclose(file);
			LLFile::remove(tempname);
			mCacheDir = path;
			return true;
		}
		return false;
	}
}

void LLDir::dumpCurrentDirectories()
{
	LL_DEBUGS2("AppInit","Directories") << "Current Directories:" << LL_ENDL;

	LL_DEBUGS2("AppInit","Directories") << "  CurPath:               " << getCurPath() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  AppName:               " << getAppName() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  ExecutableFilename:    " << getExecutableFilename() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  ExecutableDir:         " << getExecutableDir() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  ExecutablePathAndName: " << getExecutablePathAndName() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  WorkingDir:            " << getWorkingDir() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  AppRODataDir:          " << getAppRODataDir() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  OSUserDir:             " << getOSUserDir() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  OSUserAppDir:          " << getOSUserAppDir() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  LindenUserDir:         " << getLindenUserDir() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  TempDir:               " << getTempDir() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  CAFile:				 " << getCAFile() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  SkinBaseDir:           " << getSkinBaseDir() << LL_ENDL;
	LL_DEBUGS2("AppInit","Directories") << "  SkinDir:               " << getSkinDir() << LL_ENDL;
}

std::string LLDir::add(const std::string& path, const std::string& name) const
{
	std::string destpath(path);
	append(destpath, name);
	return destpath;
}

void LLDir::append(std::string& destpath, const std::string& name) const
{
	// Delegate question of whether we need a separator to helper method.
	SepOff sepoff(needSep(destpath, name));
	if (sepoff.first)               // do we need a separator?
	{
		destpath += mDirDelimiter;
	}
	// If destpath ends with a separator, AND name starts with one, skip
	// name's leading separator.
	destpath += name.substr(sepoff.second);
}

LLDir::SepOff LLDir::needSep(const std::string& path, const std::string& name) const
{
	if (path.empty() || name.empty())
	{
		// If either path or name are empty, we do not need a separator
		// between them.
		return SepOff(false, 0);
	}
	// Here we know path and name are both non-empty. But if path already ends
	// with a separator, or if name already starts with a separator, we need
	// not add one.
	std::string::size_type seplen(mDirDelimiter.length());
	bool path_ends_sep(path.substr(path.length() - seplen) == mDirDelimiter);
	bool name_starts_sep(name.substr(0, seplen) == mDirDelimiter);
	if ((! path_ends_sep) && (! name_starts_sep))
	{
		// If neither path nor name brings a separator to the junction, then
		// we need one.
		return SepOff(true, 0);
	}
	if (path_ends_sep && name_starts_sep)
	{
		// But if BOTH path and name bring a separator, we need not add one.
		// Moreover, we should actually skip the leading separator of 'name'.
		return SepOff(false, seplen);
	}
	// Here we know that either path_ends_sep or name_starts_sep is true --
	// but not both. So don't add a separator, and don't skip any characters:
	// simple concatenation will do the trick.
	return SepOff(false, 0);
}

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, 0700);
#else
	struct stat dir_stat;
	if(0 != LLFile::stat(dir_name, &dir_stat))
	{
		S32 stat_rv = errno;
		if(ENOENT == stat_rv)
		{
		   if(0 != LLFile::mkdir(dir_name, 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
}
