1208 lines
38 KiB
C++
Executable File
1208 lines
38 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
/* JMQ:
|
|
|
|
Here's the scoop on unix file IO. The windows platform makes some
|
|
assumptions about fileio: 1) the file system is case-insensitive, and
|
|
2) the platform can write to the directory in which
|
|
the game is running. Both of these are usually false on linux. So, to
|
|
compensate, we "route" created files and directories to the user's home
|
|
directory (see GetPrefPath()). When a file is to be accessed, the code
|
|
looks in the home directory first. If the file is not found there and the
|
|
open mode is read only, the code will look in the game installation
|
|
directory. Files are never created or modified in the game directory.
|
|
|
|
For case-sensitivity, the MungePath code will test whether a given path
|
|
specified by the engine exists. If not, it will use the MungeCase function
|
|
which will try to determine if an actual filesystem path matches the
|
|
specified path case insensitive. If one is found, the actual path
|
|
transparently (we hope) replaces the one requested by the engine.
|
|
|
|
The preference directory is global to all torque executables with the same
|
|
name. You should make sure you keep it clean if you build from multiple
|
|
torque development trees.
|
|
*/
|
|
|
|
#include "platformX86UNIX/x86UNIXState.h"
|
|
// evil hack to get around insane X windows #define-happy header files
|
|
#ifdef Status
|
|
#undef Status
|
|
#endif
|
|
|
|
#include "platformX86UNIX/platformX86UNIX.h"
|
|
#include "core/fileio.h"
|
|
#include "core/tVector.h"
|
|
#include "core/stringTable.h"
|
|
#include "console/console.h"
|
|
#include "core/resManager.h"
|
|
#include "platform/gameInterface.h"
|
|
|
|
#if defined(__FreeBSD__)
|
|
#include <sys/types.h>
|
|
#endif
|
|
#include <utime.h>
|
|
|
|
/* include sys/param.h for MAXPATHLEN */
|
|
#include <sys/param.h>
|
|
#ifndef MAX_PATH
|
|
#define MAX_PATH MAXPATHLEN
|
|
#endif
|
|
|
|
/* these are for reading directors, getting stats, etc. */
|
|
#include <dirent.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
|
|
extern int x86UNIXOpen(const char *path, int oflag);
|
|
extern int x86UNIXClose(int fd);
|
|
extern ssize_t x86UNIXRead(int fd, void *buf, size_t nbytes);
|
|
extern ssize_t x86UNIXWrite(int fd, const void *buf, size_t nbytes);
|
|
|
|
const int MaxPath = 2048;
|
|
|
|
// Various handy utility functions:
|
|
//------------------------------------------------------------------------------
|
|
// find all \ in a path and convert them in place to /
|
|
static void ForwardSlash(char *str)
|
|
{
|
|
while(*str)
|
|
{
|
|
if(*str == '\\')
|
|
*str = '/';
|
|
str++;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// copy a file from src to dest
|
|
static bool CopyFile(const char* src, const char* dest)
|
|
{
|
|
S32 srcFd = x86UNIXOpen(src, O_RDONLY);
|
|
S32 destFd = x86UNIXOpen(dest, O_WRONLY | O_CREAT | O_TRUNC);
|
|
bool error = false;
|
|
|
|
if (srcFd != -1 && destFd != -1)
|
|
{
|
|
const int BufSize = 8192;
|
|
char buf[BufSize];
|
|
S32 bytesRead = 0;
|
|
while ((bytesRead = x86UNIXRead(srcFd, buf, BufSize)) > 0)
|
|
{
|
|
// write data
|
|
if (x86UNIXWrite(destFd, buf, bytesRead) == -1)
|
|
{
|
|
error = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bytesRead == -1)
|
|
error = true;
|
|
}
|
|
|
|
if (srcFd != -1)
|
|
x86UNIXClose(srcFd);
|
|
if (destFd != -1)
|
|
x86UNIXClose(destFd);
|
|
|
|
if (error)
|
|
{
|
|
Con::errorf("Error copying file: %s, %s", src, dest);
|
|
remove(dest);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
bool dPathCopy(const char *fromName, const char *toName, bool nooverwrite)
|
|
{
|
|
return CopyFile(fromName,toName);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static char sgPrefDir[MaxPath];
|
|
static bool sgPrefDirInitialized = false;
|
|
|
|
// get the "pref dir", which is where game output files are stored. the pref
|
|
// dir is ~/PREF_DIR_ROOT/PREF_DIR_GAME_NAME
|
|
static const char* GetPrefDir()
|
|
{
|
|
if (sgPrefDirInitialized)
|
|
return sgPrefDir;
|
|
|
|
if (x86UNIXState->getUseRedirect())
|
|
{
|
|
const char *home = getenv("HOME");
|
|
AssertFatal(home, "HOME environment variable must be set");
|
|
|
|
dSprintf(sgPrefDir, MaxPath, "%s/%s/%s",
|
|
home, PREF_DIR_ROOT, PREF_DIR_GAME_NAME);
|
|
}
|
|
else
|
|
{
|
|
getcwd(sgPrefDir, MaxPath);
|
|
}
|
|
|
|
sgPrefDirInitialized = true;
|
|
return sgPrefDir;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// munge the case of the specified pathName. This means try to find the actual
|
|
// filename in with case-insensitive matching on the specified pathName, and
|
|
// store the actual found name.
|
|
static void MungeCase(char* pathName, S32 pathNameSize)
|
|
{
|
|
char tempBuf[MaxPath];
|
|
dStrncpy(tempBuf, pathName, pathNameSize);
|
|
|
|
AssertFatal(pathName[0] == '/', "PATH must be absolute");
|
|
|
|
struct stat filestat;
|
|
const int MaxPathEl = 200;
|
|
char *currChar = pathName;
|
|
char testPath[MaxPath];
|
|
char pathEl[MaxPathEl];
|
|
bool done = false;
|
|
|
|
dStrncpy(tempBuf, "/", MaxPath);
|
|
currChar++;
|
|
|
|
while (!done)
|
|
{
|
|
char* termChar = dStrchr(currChar, '/');
|
|
if (termChar == NULL)
|
|
termChar = dStrchr(currChar, '\0');
|
|
AssertFatal(termChar, "Can't find / or NULL terminator");
|
|
|
|
S32 pathElLen = (termChar - currChar);
|
|
dStrncpy(pathEl, currChar, pathElLen);
|
|
pathEl[pathElLen] = '\0';
|
|
dStrncpy(testPath, tempBuf, MaxPath);
|
|
dStrcat(testPath, pathEl);
|
|
if (stat(testPath, &filestat) != -1)
|
|
{
|
|
dStrncpy(tempBuf, testPath, MaxPath);
|
|
}
|
|
else
|
|
{
|
|
DIR *dir = opendir(tempBuf);
|
|
struct dirent* ent;
|
|
bool foundMatch = false;
|
|
while (dir != NULL && (ent = readdir(dir)) != NULL)
|
|
{
|
|
if (dStricmp(pathEl, ent->d_name) == 0)
|
|
{
|
|
foundMatch = true;
|
|
dStrcat(tempBuf, ent->d_name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundMatch)
|
|
dStrncpy(tempBuf, testPath, MaxPath);
|
|
if (dir)
|
|
closedir(dir);
|
|
}
|
|
if (*termChar == '/')
|
|
{
|
|
dStrcat(tempBuf, "/");
|
|
termChar++;
|
|
currChar = termChar;
|
|
}
|
|
else
|
|
done = true;
|
|
}
|
|
|
|
dStrncpy(pathName, tempBuf, pathNameSize);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns true if the pathname exists, false otherwise. If isFile is true,
|
|
// the pathname is assumed to be a file path, and only the directory part
|
|
// will be examined (everything before last /)
|
|
bool DirExists(char* pathname, bool isFile)
|
|
{
|
|
static char testpath[20000];
|
|
dStrncpy(testpath, pathname, sizeof(testpath));
|
|
if (isFile)
|
|
{
|
|
// find the last / and make it into null
|
|
char* lastSlash = dStrrchr(testpath, '/');
|
|
if (lastSlash != NULL)
|
|
*lastSlash = 0;
|
|
}
|
|
return Platform::isDirectory(testpath);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Munge the specified path.
|
|
static void MungePath(char* dest, S32 destSize,
|
|
const char* src, const char* absolutePrefix)
|
|
{
|
|
char tempBuf[MaxPath];
|
|
dStrncpy(dest, src, MaxPath);
|
|
|
|
// translate all \ to /
|
|
ForwardSlash(dest);
|
|
|
|
// if it is relative, make it absolute with the absolutePrefix
|
|
if (dest[0] != '/')
|
|
{
|
|
AssertFatal(absolutePrefix, "Absolute Prefix must not be NULL");
|
|
|
|
dSprintf(tempBuf, MaxPath, "%s/%s",
|
|
absolutePrefix, dest);
|
|
|
|
// copy the result back into dest
|
|
dStrncpy(dest, tempBuf, destSize);
|
|
}
|
|
|
|
// if the path exists, we're done
|
|
struct stat filestat;
|
|
if (stat(dest, &filestat) != -1)
|
|
return;
|
|
|
|
// otherwise munge the case of the path
|
|
MungeCase(dest, destSize);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
enum
|
|
{
|
|
TOUCH,
|
|
DELETE
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// perform a modification on the specified file. allowed modifications are
|
|
// specified in the enum above.
|
|
bool ModifyFile(const char * name, S32 modType)
|
|
{
|
|
if(!name || (dStrlen(name) >= MAX_PATH) || dStrstr(name, "../") != NULL)
|
|
return(false);
|
|
|
|
// if its absolute skip it
|
|
if (name[0]=='/' || name[0]=='\\')
|
|
return(false);
|
|
|
|
// only modify files in home directory
|
|
char prefPathName[MaxPath];
|
|
MungePath(prefPathName, MaxPath, name, GetPrefDir());
|
|
|
|
if (modType == TOUCH)
|
|
return(utime(prefPathName, 0) != -1);
|
|
else if (modType == DELETE)
|
|
return (remove(prefPathName) != -1);
|
|
else
|
|
AssertFatal(false, "Unknown File Mod type");
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static bool RecurseDumpPath(const char *path, const char* relativePath, const char *pattern, Vector<Platform::FileInfo> &fileVector, int recurseDepth)
|
|
{
|
|
char search[1024];
|
|
|
|
dSprintf(search, sizeof(search), "%s", path, pattern);
|
|
|
|
DIR *directory = opendir(search);
|
|
|
|
if (directory == NULL)
|
|
return false;
|
|
|
|
struct dirent *fEntry;
|
|
fEntry = readdir(directory); // read the first "file" in the directory
|
|
|
|
if (fEntry == NULL)
|
|
return false;
|
|
|
|
do
|
|
{
|
|
char filename[BUFSIZ+1];
|
|
struct stat fStat;
|
|
|
|
dSprintf(filename, sizeof(filename), "%s/%s", search, fEntry->d_name); // "construct" the file name
|
|
stat(filename, &fStat); // get the file stats
|
|
|
|
if ( (fStat.st_mode & S_IFMT) == S_IFDIR )
|
|
{
|
|
// Directory
|
|
// skip . and .. directories
|
|
if (dStrcmp(fEntry->d_name, ".") == 0 || dStrcmp(fEntry->d_name, "..") == 0)
|
|
continue;
|
|
|
|
// skip excluded directories
|
|
if( Platform::isExcludedDirectory(fEntry->d_name))
|
|
continue;
|
|
|
|
|
|
char child[MaxPath];
|
|
dSprintf(child, sizeof(child), "%s/%s", path, fEntry->d_name);
|
|
char* childRelative = NULL;
|
|
char childRelativeBuf[MaxPath];
|
|
if (relativePath)
|
|
{
|
|
dSprintf(childRelativeBuf, sizeof(childRelativeBuf), "%s/%s",
|
|
relativePath, fEntry->d_name);
|
|
childRelative = childRelativeBuf;
|
|
}
|
|
|
|
if (recurseDepth > 0)
|
|
RecurseDumpPath(child, childRelative, pattern, fileVector, recurseDepth - 1);
|
|
else if (recurseDepth == -1)
|
|
RecurseDumpPath(child, childRelative, pattern, fileVector, -1);
|
|
}
|
|
else
|
|
{
|
|
// File
|
|
|
|
// add it to the list
|
|
fileVector.increment();
|
|
Platform::FileInfo& rInfo = fileVector.last();
|
|
|
|
if (relativePath)
|
|
rInfo.pFullPath = StringTable->insert(relativePath);
|
|
else
|
|
rInfo.pFullPath = StringTable->insert(path);
|
|
rInfo.pFileName = StringTable->insert(fEntry->d_name);
|
|
rInfo.fileSize = fStat.st_size;
|
|
//dPrintf("Adding file: %s/%s\n", rInfo.pFullPath, rInfo.pFileName);
|
|
}
|
|
|
|
} while( (fEntry = readdir(directory)) != NULL );
|
|
|
|
closedir(directory);
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool dFileDelete(const char * name)
|
|
{
|
|
return ModifyFile(name, DELETE);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool dFileTouch(const char * name)
|
|
{
|
|
return ModifyFile(name, TOUCH);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constructors & Destructor
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// After construction, the currentStatus will be Closed and the capabilities
|
|
// will be 0.
|
|
//-----------------------------------------------------------------------------
|
|
File::File()
|
|
: currentStatus(Closed), capability(0)
|
|
{
|
|
// AssertFatal(sizeof(int) == sizeof(void *), "File::File: cannot cast void* to int");
|
|
|
|
handle = (void *)NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// insert a copy constructor here... (currently disabled)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Destructor
|
|
//-----------------------------------------------------------------------------
|
|
File::~File()
|
|
{
|
|
close();
|
|
handle = (void *)NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Open a file in the mode specified by openMode (Read, Write, or ReadWrite).
|
|
// Truncate the file if the mode is either Write or ReadWrite and truncate is
|
|
// true.
|
|
//
|
|
// Sets capability appropriate to the openMode.
|
|
// Returns the currentStatus of the file.
|
|
//-----------------------------------------------------------------------------
|
|
File::Status File::open(const char *filename, const AccessMode openMode)
|
|
{
|
|
AssertFatal(NULL != filename, "File::open: NULL filename");
|
|
AssertWarn(NULL == handle, "File::open: handle already valid");
|
|
|
|
// Close the file if it was already open...
|
|
if (Closed != currentStatus)
|
|
close();
|
|
|
|
char prefPathName[MaxPath];
|
|
char gamePathName[MaxPath];
|
|
char cwd[MaxPath];
|
|
getcwd(cwd, MaxPath);
|
|
MungePath(prefPathName, MaxPath, filename, GetPrefDir());
|
|
MungePath(gamePathName, MaxPath, filename, cwd);
|
|
|
|
int oflag;
|
|
struct stat filestat;
|
|
handle = (void *)dRealMalloc(sizeof(int));
|
|
|
|
switch (openMode)
|
|
{
|
|
case Read:
|
|
oflag = O_RDONLY;
|
|
break;
|
|
case Write:
|
|
oflag = O_WRONLY | O_CREAT | O_TRUNC;
|
|
break;
|
|
case ReadWrite:
|
|
oflag = O_RDWR | O_CREAT;
|
|
// if the file does not exist copy it before reading/writing
|
|
if (stat(prefPathName, &filestat) == -1)
|
|
bool ret = CopyFile(gamePathName, prefPathName);
|
|
break;
|
|
case WriteAppend:
|
|
oflag = O_WRONLY | O_CREAT | O_APPEND;
|
|
// if the file does not exist copy it before appending
|
|
if (stat(prefPathName, &filestat) == -1)
|
|
bool ret = CopyFile(gamePathName, prefPathName);
|
|
break;
|
|
default:
|
|
AssertFatal(false, "File::open: bad access mode"); // impossible
|
|
}
|
|
|
|
// if we are writing, make sure output path exists
|
|
if (openMode == Write || openMode == ReadWrite || openMode == WriteAppend)
|
|
Platform::createPath(prefPathName);
|
|
|
|
int fd = -1;
|
|
fd = x86UNIXOpen(prefPathName, oflag);
|
|
if (fd == -1 && openMode == Read)
|
|
// for read only files we can use the gamePathName
|
|
fd = x86UNIXOpen(gamePathName, oflag);
|
|
|
|
dMemcpy(handle, &fd, sizeof(int));
|
|
|
|
#ifdef DEBUG
|
|
// fprintf(stdout,"fd = %d, handle = %d\n", fd, *((int *)handle));
|
|
#endif
|
|
|
|
if (*((int *)handle) == -1)
|
|
{
|
|
// handle not created successfully
|
|
Con::errorf("Can't open file: %s", filename);
|
|
return setStatus();
|
|
}
|
|
else
|
|
{
|
|
// successfully created file, so set the file capabilities...
|
|
switch (openMode)
|
|
{
|
|
case Read:
|
|
capability = U32(FileRead);
|
|
break;
|
|
case Write:
|
|
case WriteAppend:
|
|
capability = U32(FileWrite);
|
|
break;
|
|
case ReadWrite:
|
|
capability = U32(FileRead) |
|
|
U32(FileWrite);
|
|
break;
|
|
default:
|
|
AssertFatal(false, "File::open: bad access mode");
|
|
}
|
|
return currentStatus = Ok; // success!
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Get the current position of the file pointer.
|
|
//-----------------------------------------------------------------------------
|
|
U32 File::getPosition() const
|
|
{
|
|
AssertFatal(Closed != currentStatus, "File::getPosition: file closed");
|
|
AssertFatal(NULL != handle, "File::getPosition: invalid file handle");
|
|
|
|
#ifdef DEBUG
|
|
// fprintf(stdout, "handle = %d\n",*((int *)handle));fflush(stdout);
|
|
#endif
|
|
return (U32) lseek(*((int *)handle), 0, SEEK_CUR);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Set the position of the file pointer.
|
|
// Absolute and relative positioning is supported via the absolutePos
|
|
// parameter.
|
|
//
|
|
// If positioning absolutely, position MUST be positive - an IOError results if
|
|
// position is negative.
|
|
// Position can be negative if positioning relatively, however positioning
|
|
// before the start of the file is an IOError.
|
|
//
|
|
// Returns the currentStatus of the file.
|
|
//-----------------------------------------------------------------------------
|
|
File::Status File::setPosition(S32 position, bool absolutePos)
|
|
{
|
|
AssertFatal(Closed != currentStatus, "File::setPosition: file closed");
|
|
AssertFatal(NULL != handle, "File::setPosition: invalid file handle");
|
|
|
|
if (Ok != currentStatus && EOS != currentStatus)
|
|
return currentStatus;
|
|
|
|
U32 finalPos = 0;
|
|
switch (absolutePos)
|
|
{
|
|
case true: // absolute position
|
|
AssertFatal(0 <= position, "File::setPosition: negative absolute position");
|
|
|
|
// position beyond EOS is OK
|
|
finalPos = lseek(*((int *)handle), position, SEEK_SET);
|
|
break;
|
|
case false: // relative position
|
|
AssertFatal((getPosition() >= (U32)abs(position) && 0 > position) || 0 <= position, "File::setPosition: negative relative position");
|
|
|
|
// position beyond EOS is OK
|
|
finalPos = lseek(*((int *)handle), position, SEEK_CUR);
|
|
break;
|
|
}
|
|
|
|
if (0xffffffff == finalPos)
|
|
return setStatus(); // unsuccessful
|
|
else if (finalPos >= getSize())
|
|
return currentStatus = EOS; // success, at end of file
|
|
else
|
|
return currentStatus = Ok; // success!
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Get the size of the file in bytes.
|
|
// It is an error to query the file size for a Closed file, or for one with an
|
|
// error status.
|
|
//-----------------------------------------------------------------------------
|
|
U32 File::getSize() const
|
|
{
|
|
AssertWarn(Closed != currentStatus, "File::getSize: file closed");
|
|
AssertFatal(NULL != handle, "File::getSize: invalid file handle");
|
|
|
|
if (Ok == currentStatus || EOS == currentStatus)
|
|
{
|
|
long currentOffset = getPosition(); // keep track of our current position
|
|
long fileSize;
|
|
lseek(*((int *)handle), 0, SEEK_END); // seek to the end of the file
|
|
fileSize = getPosition(); // get the file size
|
|
lseek(*((int *)handle), currentOffset, SEEK_SET); // seek back to our old offset
|
|
return fileSize; // success!
|
|
}
|
|
else
|
|
return 0; // unsuccessful
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Flush the file.
|
|
// It is an error to flush a read-only file.
|
|
// Returns the currentStatus of the file.
|
|
//-----------------------------------------------------------------------------
|
|
File::Status File::flush()
|
|
{
|
|
AssertFatal(Closed != currentStatus, "File::flush: file closed");
|
|
AssertFatal(NULL != handle, "File::flush: invalid file handle");
|
|
AssertFatal(true == hasCapability(FileWrite), "File::flush: cannot flush a read-only file");
|
|
|
|
if (fsync(*((int *)handle)) == 0)
|
|
return currentStatus = Ok; // success!
|
|
else
|
|
return setStatus(); // unsuccessful
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Close the File.
|
|
//
|
|
// Returns the currentStatus
|
|
//-----------------------------------------------------------------------------
|
|
File::Status File::close()
|
|
{
|
|
// if the handle is non-NULL, close it if necessary and free it
|
|
if (NULL != handle)
|
|
{
|
|
// make a local copy of the handle value and
|
|
// free the handle
|
|
int handleVal = *((int *)handle);
|
|
dRealFree(handle);
|
|
handle = (void *)NULL;
|
|
|
|
// close the handle if it is valid
|
|
if (handleVal != -1 && x86UNIXClose(handleVal) != 0)
|
|
return setStatus(); // unsuccessful
|
|
}
|
|
// Set the status to closed
|
|
return currentStatus = Closed;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Self-explanatory.
|
|
//-----------------------------------------------------------------------------
|
|
File::Status File::getStatus() const
|
|
{
|
|
return currentStatus;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets and returns the currentStatus when an error has been encountered.
|
|
//-----------------------------------------------------------------------------
|
|
File::Status File::setStatus()
|
|
{
|
|
Con::printf("File IO error: %s", strerror(errno));
|
|
return currentStatus = IOError;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets and returns the currentStatus to status.
|
|
//-----------------------------------------------------------------------------
|
|
File::Status File::setStatus(File::Status status)
|
|
{
|
|
return currentStatus = status;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Read from a file.
|
|
// The number of bytes to read is passed in size, the data is returned in src.
|
|
// The number of bytes read is available in bytesRead if a non-Null pointer is
|
|
// provided.
|
|
//-----------------------------------------------------------------------------
|
|
File::Status File::read(U32 size, char *dst, U32 *bytesRead)
|
|
{
|
|
#ifdef DEBUG
|
|
// fprintf(stdout,"reading %d bytes\n",size);fflush(stdout);
|
|
#endif
|
|
AssertFatal(Closed != currentStatus, "File::read: file closed");
|
|
AssertFatal(NULL != handle, "File::read: invalid file handle");
|
|
AssertFatal(NULL != dst, "File::read: NULL destination pointer");
|
|
AssertFatal(true == hasCapability(FileRead), "File::read: file lacks capability");
|
|
AssertWarn(0 != size, "File::read: size of zero");
|
|
|
|
/* show stats for this file */
|
|
#ifdef DEBUG
|
|
//struct stat st;
|
|
//fstat(*((int *)handle), &st);
|
|
//fprintf(stdout,"file size = %d\n", st.st_size);
|
|
#endif
|
|
/****************************/
|
|
|
|
if (Ok != currentStatus || 0 == size)
|
|
return currentStatus;
|
|
else
|
|
{
|
|
long lastBytes;
|
|
long *bytes = (NULL == bytesRead) ? &lastBytes : (long *)bytesRead;
|
|
if ( (*((U32 *)bytes) = x86UNIXRead(*((int *)handle), dst, size)) == -1)
|
|
{
|
|
#ifdef DEBUG
|
|
// fprintf(stdout,"unsuccessful: %d\n", *((U32 *)bytes));fflush(stdout);
|
|
#endif
|
|
return setStatus(); // unsuccessful
|
|
} else {
|
|
// dst[*((U32 *)bytes)] = '\0';
|
|
if (*((U32 *)bytes) != size || *((U32 *)bytes) == 0) {
|
|
#ifdef DEBUG
|
|
// fprintf(stdout,"end of stream: %d\n", *((U32 *)bytes));fflush(stdout);
|
|
#endif
|
|
return currentStatus = EOS; // end of stream
|
|
}
|
|
}
|
|
}
|
|
// dst[*bytesRead] = '\0';
|
|
#ifdef DEBUG
|
|
//fprintf(stdout, "We read:\n");
|
|
//fprintf(stdout, "====================================================\n");
|
|
//fprintf(stdout, "%s\n",dst);
|
|
//fprintf(stdout, "====================================================\n");
|
|
//fprintf(stdout,"read ok: %d\n", *bytesRead);fflush(stdout);
|
|
#endif
|
|
return currentStatus = Ok; // successfully read size bytes
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Write to a file.
|
|
// The number of bytes to write is passed in size, the data is passed in src.
|
|
// The number of bytes written is available in bytesWritten if a non-Null
|
|
// pointer is provided.
|
|
//-----------------------------------------------------------------------------
|
|
File::Status File::write(U32 size, const char *src, U32 *bytesWritten)
|
|
{
|
|
// JMQ: despite the U32 parameters, the maximum filesize supported by this
|
|
// function is probably the max value of S32, due to the unix syscall
|
|
// api.
|
|
AssertFatal(Closed != currentStatus, "File::write: file closed");
|
|
AssertFatal(NULL != handle, "File::write: invalid file handle");
|
|
AssertFatal(NULL != src, "File::write: NULL source pointer");
|
|
AssertFatal(true == hasCapability(FileWrite), "File::write: file lacks capability");
|
|
AssertWarn(0 != size, "File::write: size of zero");
|
|
|
|
if ((Ok != currentStatus && EOS != currentStatus) || 0 == size)
|
|
return currentStatus;
|
|
else
|
|
{
|
|
S32 numWritten = x86UNIXWrite(*((int *)handle), src, size);
|
|
if (numWritten < 0)
|
|
return setStatus();
|
|
|
|
if (bytesWritten)
|
|
*bytesWritten = static_cast<U32>(numWritten);
|
|
return currentStatus = Ok;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Self-explanatory. JMQ: No explanation needed. Move along. These aren't
|
|
// the droids you're looking for.
|
|
//-----------------------------------------------------------------------------
|
|
bool File::hasCapability(Capability cap) const
|
|
{
|
|
return (0 != (U32(cap) & capability));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
S32 Platform::compareFileTimes(const FileTime &a, const FileTime &b)
|
|
{
|
|
if(a > b)
|
|
return 1;
|
|
if(a < b)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
static bool GetFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime)
|
|
{
|
|
struct stat fStat;
|
|
|
|
if (stat(filePath, &fStat) == -1)
|
|
return false;
|
|
|
|
if(createTime)
|
|
{
|
|
// no where does SysV/BSD UNIX keep a record of a file's
|
|
// creation time. instead of creation time I'll just use
|
|
// changed time for now.
|
|
*createTime = fStat.st_ctime;
|
|
}
|
|
if(modifyTime)
|
|
{
|
|
*modifyTime = fStat.st_mtime;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool Platform::getFileTimes(const char *filePath, FileTime *createTime, FileTime *modifyTime)
|
|
{
|
|
char pathName[MaxPath];
|
|
|
|
// if it starts with cwd, we need to strip that off so that we can look for
|
|
// the file in the pref dir
|
|
char cwd[MaxPath];
|
|
getcwd(cwd, MaxPath);
|
|
if (dStrstr(filePath, cwd) == filePath)
|
|
filePath = filePath + dStrlen(cwd) + 1;
|
|
|
|
// if its relative, first look in the pref dir
|
|
if (filePath[0] != '/' && filePath[0] != '\\')
|
|
{
|
|
MungePath(pathName, MaxPath, filePath, GetPrefDir());
|
|
if (GetFileTimes(pathName, createTime, modifyTime))
|
|
return true;
|
|
}
|
|
|
|
// here if the path is absolute or not in the pref dir
|
|
MungePath(pathName, MaxPath, filePath, cwd);
|
|
return GetFileTimes(pathName, createTime, modifyTime);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool Platform::createPath(const char *file)
|
|
{
|
|
char pathbuf[MaxPath];
|
|
const char *dir;
|
|
pathbuf[0] = 0;
|
|
U32 pathLen = 0;
|
|
|
|
// all paths should be created in home directory
|
|
char prefPathName[MaxPath];
|
|
MungePath(prefPathName, MaxPath, file, GetPrefDir());
|
|
file = prefPathName;
|
|
|
|
// does the directory exist already?
|
|
if (DirExists(prefPathName, true)) // true means that the path is a filepath
|
|
return true;
|
|
|
|
while((dir = dStrchr(file, '/')) != NULL)
|
|
{
|
|
dStrncpy(pathbuf + pathLen, file, dir - file);
|
|
pathbuf[pathLen + dir-file] = 0;
|
|
bool ret = mkdir(pathbuf, 0700);
|
|
pathLen += dir - file;
|
|
pathbuf[pathLen++] = '/';
|
|
file = dir + 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// JMQ: Platform:cdFileExists in unimplemented
|
|
//------------------------------------------------------------------------------
|
|
// bool Platform::cdFileExists(const char *filePath, const char *volumeName,
|
|
// S32 serialNum)
|
|
// {
|
|
// }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool Platform::dumpPath(const char *path, Vector<Platform::FileInfo> &fileVector, int depth)
|
|
{
|
|
const char* pattern = "*";
|
|
|
|
// if it is not absolute, dump the pref dir first
|
|
if (path[0] != '/' && path[0] != '\\')
|
|
{
|
|
char prefPathName[MaxPath];
|
|
MungePath(prefPathName, MaxPath, path, GetPrefDir());
|
|
RecurseDumpPath(prefPathName, path, pattern, fileVector, depth);
|
|
}
|
|
|
|
// munge the requested path and dump it
|
|
char mungedPath[MaxPath];
|
|
char cwd[MaxPath];
|
|
getcwd(cwd, MaxPath);
|
|
MungePath(mungedPath, MaxPath, path, cwd);
|
|
return RecurseDumpPath(mungedPath, path, pattern, fileVector, depth);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
StringTableEntry Platform::getWorkingDirectory()
|
|
{
|
|
static StringTableEntry cwd = NULL;
|
|
|
|
if (!cwd)
|
|
{
|
|
char cwd_buf[2048];
|
|
getcwd(cwd_buf, 2047);
|
|
cwd = StringTable->insert(cwd_buf);
|
|
}
|
|
return cwd;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool Platform::isFile(const char *pFilePath)
|
|
{
|
|
if (!pFilePath || !*pFilePath)
|
|
return false;
|
|
// Get file info
|
|
struct stat fStat;
|
|
if (stat(pFilePath, &fStat) < 0)
|
|
return false;
|
|
|
|
// if the file is a "regular file" then true
|
|
if ( (fStat.st_mode & S_IFMT) == S_IFREG)
|
|
return true;
|
|
// must be some other file (directory, device, etc.)
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
S32 Platform::getFileSize(const char *pFilePath)
|
|
{
|
|
if (!pFilePath || !*pFilePath)
|
|
return -1;
|
|
// Get the file info
|
|
struct stat fStat;
|
|
if (stat(pFilePath, &fStat) < 0)
|
|
return -1;
|
|
// if the file is a "regular file" then return the size
|
|
if ( (fStat.st_mode & S_IFMT) == S_IFREG)
|
|
return fStat.st_size;
|
|
// Must be something else or we can't read the file.
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool Platform::isDirectory(const char *pDirPath)
|
|
{
|
|
if (!pDirPath || !*pDirPath)
|
|
return false;
|
|
|
|
// Get file info
|
|
struct stat fStat;
|
|
if (stat(pDirPath, &fStat) < 0)
|
|
return false;
|
|
|
|
// if the file is a Directory then true
|
|
if ( (fStat.st_mode & S_IFMT) == S_IFDIR)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool Platform::isSubDirectory(const char *pParent, const char *pDir)
|
|
{
|
|
if (!pParent || !*pDir)
|
|
return false;
|
|
|
|
// this is somewhat of a brute force method but we need to be 100% sure
|
|
// that the user cannot enter things like ../dir or /dir etc,...
|
|
DIR *directory;
|
|
|
|
directory = opendir(pParent);
|
|
if (directory == NULL)
|
|
return false;
|
|
|
|
struct dirent *fEntry;
|
|
fEntry = readdir(directory);
|
|
if ( fEntry == NULL )
|
|
return false;
|
|
|
|
do
|
|
{
|
|
char dirBuf[MAXPATHLEN];
|
|
struct stat fStat;
|
|
|
|
dSprintf(dirBuf, sizeof(dirBuf), "%s/%s", pParent, fEntry->d_name);
|
|
if (stat(dirBuf, &fStat) < 0)
|
|
continue;
|
|
// if it is a directory...
|
|
if ( (fStat.st_mode & S_IFMT) == S_IFDIR)
|
|
{
|
|
// and the names match
|
|
if (dStrcmp(pDir, fEntry->d_name ) == 0)
|
|
{
|
|
// then we have a real sub directory
|
|
closedir(directory);
|
|
return true;
|
|
}
|
|
}
|
|
} while( (fEntry = readdir(directory)) != NULL );
|
|
|
|
closedir(directory);
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
// This is untested -- BJG
|
|
|
|
bool Platform::fileTimeToString(FileTime * time, char * string, U32 strLen)
|
|
{
|
|
if(!time || !string)
|
|
return(false);
|
|
|
|
dSprintf(string, strLen, "%ld", *time);
|
|
return(true);
|
|
}
|
|
|
|
bool Platform::stringToFileTime(const char * string, FileTime * time)
|
|
{
|
|
if(!time || !string)
|
|
return(false);
|
|
|
|
*time = dAtoi(string);
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool Platform::hasSubDirectory(const char *pPath)
|
|
{
|
|
if (!pPath)
|
|
return false;
|
|
ResourceManager->initExcludedDirectories();
|
|
|
|
struct dirent *d;
|
|
DIR *dip;
|
|
dip = opendir(pPath);
|
|
if (dip == NULL)
|
|
return false;
|
|
|
|
while (d = readdir(dip))
|
|
{
|
|
bool isDir = false;
|
|
if (d->d_type == DT_UNKNOWN)
|
|
{
|
|
char child [1024];
|
|
if ((pPath[dStrlen(pPath) - 1] == '/'))
|
|
dSprintf(child, 1024, "%s%s", pPath, d->d_name);
|
|
else
|
|
dSprintf(child, 1024, "%s/%s", pPath, d->d_name);
|
|
isDir = Platform::isDirectory (child);
|
|
}
|
|
else if (d->d_type & DT_DIR)
|
|
isDir = true;
|
|
if( isDir )
|
|
{
|
|
// Skip the . and .. directories
|
|
if (dStrcmp(d->d_name, ".") == 0 ||dStrcmp(d->d_name, "..") == 0)
|
|
continue;
|
|
if (Platform::isExcludedDirectory(d->d_name))
|
|
continue;
|
|
Platform::clearExcludedDirectories();
|
|
closedir(dip);
|
|
return true;
|
|
}
|
|
}
|
|
closedir(dip);
|
|
Platform::clearExcludedDirectories();
|
|
return false;
|
|
}
|
|
|
|
static bool recurseDumpDirectories(const char *basePath, const char *subPath, Vector<StringTableEntry> &directoryVector, S32 currentDepth, S32 recurseDepth, bool noBasePath)
|
|
{
|
|
char Path[1024];
|
|
DIR *dip;
|
|
struct dirent *d;
|
|
|
|
if (subPath && (dStrncmp(subPath, "", 1) != 0))
|
|
{
|
|
if ((basePath[dStrlen(basePath) - 1]) == '/')
|
|
dSprintf(Path, 1024, "%s%s", basePath, subPath);
|
|
else
|
|
dSprintf(Path, 1024, "%s/%s", basePath, subPath);
|
|
}
|
|
else
|
|
dSprintf(Path, 1024, "%s", basePath);
|
|
dip = opendir(Path);
|
|
if (dip == NULL)
|
|
return false;
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// add path to our return list ( provided it is valid )
|
|
//////////////////////////////////////////////////////////////////////////
|
|
if (!Platform::isExcludedDirectory(subPath))
|
|
{
|
|
if (noBasePath)
|
|
{
|
|
// We have a path and it's not an empty string or an excluded directory
|
|
if ( (subPath && (dStrncmp (subPath, "", 1) != 0)) )
|
|
directoryVector.push_back(StringTable->insert(subPath));
|
|
}
|
|
else
|
|
{
|
|
if ( (subPath && (dStrncmp(subPath, "", 1) != 0)) )
|
|
{
|
|
char szPath[1024];
|
|
dMemset(szPath, 0, 1024);
|
|
if ( (basePath[dStrlen(basePath) - 1]) != '/')
|
|
dSprintf(szPath, 1024, "%s%s", basePath, subPath);
|
|
else
|
|
dSprintf(szPath, 1024, "%s%s", basePath, &subPath[1]);
|
|
directoryVector.push_back(StringTable->insert(szPath));
|
|
}
|
|
else
|
|
directoryVector.push_back(StringTable->insert(basePath));
|
|
}
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Iterate through and grab valid directories
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
while (d = readdir(dip))
|
|
{
|
|
bool isDir;
|
|
isDir = false;
|
|
if (d->d_type == DT_UNKNOWN)
|
|
{
|
|
char child [1024];
|
|
if ((Path[dStrlen(Path) - 1] == '/'))
|
|
dSprintf(child, 1024, "%s%s", Path, d->d_name);
|
|
else
|
|
dSprintf(child, 1024, "%s/%s", Path, d->d_name);
|
|
isDir = Platform::isDirectory (child);
|
|
}
|
|
else if (d->d_type & DT_DIR)
|
|
isDir = true;
|
|
|
|
if ( isDir )
|
|
{
|
|
if (dStrcmp(d->d_name, ".") == 0 ||
|
|
dStrcmp(d->d_name, "..") == 0)
|
|
continue;
|
|
if (Platform::isExcludedDirectory(d->d_name))
|
|
continue;
|
|
if ( (subPath && (dStrncmp(subPath, "", 1) != 0)) )
|
|
{
|
|
char child[1024];
|
|
if ((subPath[dStrlen(subPath) - 1] == '/'))
|
|
dSprintf(child, 1024, "%s%s", subPath, d->d_name);
|
|
else
|
|
dSprintf(child, 1024, "%s/%s", subPath, d->d_name);
|
|
if (currentDepth < recurseDepth || recurseDepth == -1 )
|
|
recurseDumpDirectories(basePath, child, directoryVector,
|
|
currentDepth + 1, recurseDepth,
|
|
noBasePath);
|
|
}
|
|
else
|
|
{
|
|
char child[1024];
|
|
if ( (basePath[dStrlen(basePath) - 1]) == '/')
|
|
dStrcpy (child, d->d_name);
|
|
else
|
|
dSprintf(child, 1024, "/%s", d->d_name);
|
|
if (currentDepth < recurseDepth || recurseDepth == -1)
|
|
recurseDumpDirectories(basePath, child, directoryVector,
|
|
currentDepth + 1, recurseDepth,
|
|
noBasePath);
|
|
}
|
|
}
|
|
}
|
|
closedir(dip);
|
|
return true;
|
|
}
|
|
|
|
bool Platform::dumpDirectories(const char *path, Vector<StringTableEntry> &directoryVector, S32 depth, bool noBasePath)
|
|
{
|
|
ResourceManager->initExcludedDirectories();
|
|
bool retVal = recurseDumpDirectories(path, "", directoryVector, -1, depth, noBasePath);
|
|
clearExcludedDirectories();
|
|
return retVal;
|
|
}
|
|
|
|
StringTableEntry Platform::getExecutableName()
|
|
{
|
|
static StringTableEntry cwd = NULL;
|
|
|
|
if (!cwd)
|
|
{
|
|
cwd = StringTable->insert(x86UNIXState->getExeName());
|
|
}
|
|
return cwd;
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void Platform::restartInstance()
|
|
{
|
|
// execl() leaves open file descriptors open, that's the main reason it's not
|
|
// used here. We want to start fresh.
|
|
|
|
if (Game->isRunning() )
|
|
{
|
|
Con::errorf("The game is still running, we cant relaunch now!");
|
|
return;
|
|
}
|
|
|
|
char cmd[MaxPath];
|
|
sprintf(cmd, "\"%s &\"", x86UNIXState->getExePathName());
|
|
Con::printf("---- %s -----",cmd);
|
|
if(!system(cmd))
|
|
{
|
|
Con::errorf("Cannot fork new instance.");
|
|
return;
|
|
}
|
|
|
|
exit(0);
|
|
}
|
|
|