mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 20:54:46 +00:00
599 lines
13 KiB
C++
599 lines
13 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
|
|
#include "core/crc.h"
|
|
#include "core/frameAllocator.h"
|
|
|
|
#include "core/util/str.h"
|
|
#include "core/strings/stringFunctions.h"
|
|
|
|
#include "platform/platformVolume.h"
|
|
#include "platformPOSIX/posixVolume.h"
|
|
|
|
#ifndef PATH_MAX
|
|
#include <sys/syslimits.h>
|
|
#endif
|
|
|
|
#ifndef NGROUPS_UMAX
|
|
#define NGROUPS_UMAX 32
|
|
#endif
|
|
|
|
|
|
//#define DEBUG_SPEW
|
|
|
|
|
|
namespace Torque
|
|
{
|
|
namespace Posix
|
|
{
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static String buildFileName(const String& prefix,const Path& path)
|
|
{
|
|
// Need to join the path (minus the root) with our
|
|
// internal path name.
|
|
String file = prefix;
|
|
file = Path::Join(file,'/',path.getPath());
|
|
file = Path::Join(file,'/',path.getFileName());
|
|
file = Path::Join(file,'.',path.getExtension());
|
|
return file;
|
|
}
|
|
|
|
/*
|
|
static bool isFile(const String& file)
|
|
{
|
|
struct stat info;
|
|
if (stat(file.c_str(),&info) == 0)
|
|
return S_ISREG(info.st_mode);
|
|
return false;
|
|
}
|
|
|
|
static bool isDirectory(const String& file)
|
|
{
|
|
struct stat info;
|
|
if (stat(file.c_str(),&info) == 0)
|
|
return S_ISDIR(info.st_mode);
|
|
return false;
|
|
}
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static uid_t _Uid; // Current user id
|
|
static int _GroupCount; // Number of groups in the table
|
|
static gid_t _Groups[NGROUPS_UMAX+1]; // Table of all the user groups
|
|
|
|
static void copyStatAttributes(const struct stat& info, FileNode::Attributes* attr)
|
|
{
|
|
// We need to user and group id's in order to determin file
|
|
// read-only access permission. This information is only retrieved
|
|
// once per execution.
|
|
if (!_Uid)
|
|
{
|
|
_Uid = getuid();
|
|
_GroupCount = getgroups(NGROUPS_UMAX,_Groups);
|
|
_Groups[_GroupCount++] = getegid();
|
|
}
|
|
|
|
// Fill in the return struct. The read-only flag is determined
|
|
// by comparing file user and group ownership.
|
|
attr->flags = 0;
|
|
if (S_ISDIR(info.st_mode))
|
|
attr->flags |= FileNode::Directory;
|
|
|
|
if (S_ISREG(info.st_mode))
|
|
attr->flags |= FileNode::File;
|
|
|
|
if (info.st_uid == _Uid)
|
|
{
|
|
if (!(info.st_mode & S_IWUSR))
|
|
attr->flags |= FileNode::ReadOnly;
|
|
}
|
|
else
|
|
{
|
|
S32 i = 0;
|
|
for (; i < _GroupCount; i++)
|
|
{
|
|
if (_Groups[i] == info.st_gid)
|
|
break;
|
|
}
|
|
if (i != _GroupCount)
|
|
{
|
|
if (!(info.st_mode & S_IWGRP))
|
|
attr->flags |= FileNode::ReadOnly;
|
|
}
|
|
else
|
|
{
|
|
if (!(info.st_mode & S_IWOTH))
|
|
attr->flags |= FileNode::ReadOnly;
|
|
}
|
|
}
|
|
|
|
attr->size = info.st_size;
|
|
attr->mtime = UnixTimeToTime(info.st_mtime);
|
|
attr->atime = UnixTimeToTime(info.st_atime);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
PosixFileSystem::PosixFileSystem(String volume)
|
|
{
|
|
_volume = volume;
|
|
}
|
|
|
|
PosixFileSystem::~PosixFileSystem()
|
|
{
|
|
}
|
|
|
|
FileNodeRef PosixFileSystem::resolve(const Path& path)
|
|
{
|
|
String file = buildFileName(_volume,path);
|
|
struct stat info;
|
|
if (stat(file.c_str(),&info) == 0)
|
|
{
|
|
// Construct the appropriate object
|
|
if (S_ISREG(info.st_mode))
|
|
return new PosixFile(path,file);
|
|
|
|
if (S_ISDIR(info.st_mode))
|
|
return new PosixDirectory(path,file);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
FileNodeRef PosixFileSystem::create(const Path& path, FileNode::Mode mode)
|
|
{
|
|
// The file will be created on disk when it's opened.
|
|
if (mode & FileNode::File)
|
|
return new PosixFile(path,buildFileName(_volume,path));
|
|
|
|
// Default permissions are read/write/search/executate by everyone,
|
|
// though this will be modified by the current umask
|
|
if (mode & FileNode::Directory)
|
|
{
|
|
String file = buildFileName(_volume,path);
|
|
|
|
if (mkdir(file.c_str(),S_IRWXU | S_IRWXG | S_IRWXO))
|
|
return new PosixDirectory(path,file);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool PosixFileSystem::remove(const Path& path)
|
|
{
|
|
// Should probably check for outstanding files or directory objects.
|
|
String file = buildFileName(_volume,path);
|
|
|
|
struct stat info;
|
|
int error = stat(file.c_str(),&info);
|
|
if (error < 0)
|
|
return false;
|
|
|
|
if (S_ISDIR(info.st_mode))
|
|
return !rmdir(file);
|
|
|
|
return !unlink(file);
|
|
}
|
|
|
|
bool PosixFileSystem::rename(const Path& from,const Path& to)
|
|
{
|
|
String fa = buildFileName(_volume,from);
|
|
String fb = buildFileName(_volume,to);
|
|
|
|
if (!::rename(fa.c_str(),fb.c_str()))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
Path PosixFileSystem::mapTo(const Path& path)
|
|
{
|
|
return buildFileName(_volume,path);
|
|
}
|
|
|
|
|
|
Path PosixFileSystem::mapFrom(const Path& path)
|
|
{
|
|
const String::SizeType volumePathLen = _volume.length();
|
|
|
|
String pathStr = path.getFullPath();
|
|
|
|
if ( _volume.compare( pathStr, volumePathLen, String::NoCase ))
|
|
return Path();
|
|
|
|
return pathStr.substr( volumePathLen, pathStr.length() - volumePathLen );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
PosixFile::PosixFile(const Path& path,String name)
|
|
{
|
|
_path = path;
|
|
_name = name;
|
|
_status = Closed;
|
|
_handle = 0;
|
|
}
|
|
|
|
PosixFile::~PosixFile()
|
|
{
|
|
if (_handle)
|
|
close();
|
|
}
|
|
|
|
Path PosixFile::getName() const
|
|
{
|
|
return _path;
|
|
}
|
|
|
|
FileNode::NodeStatus PosixFile::getStatus() const
|
|
{
|
|
return _status;
|
|
}
|
|
|
|
bool PosixFile::getAttributes(Attributes* attr)
|
|
{
|
|
struct stat info;
|
|
int error = _handle? fstat(fileno(_handle),&info): stat(_name.c_str(),&info);
|
|
|
|
if (error < 0)
|
|
{
|
|
_updateStatus();
|
|
return false;
|
|
}
|
|
|
|
copyStatAttributes(info,attr);
|
|
attr->name = _path;
|
|
|
|
return true;
|
|
}
|
|
|
|
U32 PosixFile::calculateChecksum()
|
|
{
|
|
if (!open( Read ))
|
|
return 0;
|
|
|
|
U64 fileSize = getSize();
|
|
U32 bufSize = 1024 * 1024 * 4;
|
|
FrameTemp< U8 > buf( bufSize );
|
|
U32 crc = CRC::INITIAL_CRC_VALUE;
|
|
|
|
while( fileSize > 0 )
|
|
{
|
|
U32 bytesRead = getMin( fileSize, bufSize );
|
|
if( read( buf, bytesRead ) != bytesRead )
|
|
{
|
|
close();
|
|
return 0;
|
|
}
|
|
|
|
fileSize -= bytesRead;
|
|
crc = CRC::calculateCRC( buf, bytesRead, crc );
|
|
}
|
|
|
|
close();
|
|
|
|
return crc;
|
|
}
|
|
|
|
bool PosixFile::open(AccessMode mode)
|
|
{
|
|
close();
|
|
|
|
if (_name.isEmpty())
|
|
{
|
|
return _status;
|
|
}
|
|
|
|
#ifdef DEBUG_SPEW
|
|
Platform::outputDebugString( "[PosixFile] opening '%s'", _name.c_str() );
|
|
#endif
|
|
|
|
const char* fmode = "r";
|
|
switch (mode)
|
|
{
|
|
case Read: fmode = "r"; break;
|
|
case Write: fmode = "w"; break;
|
|
case ReadWrite:
|
|
{
|
|
fmode = "r+";
|
|
// Ensure the file exists.
|
|
FILE* temp = fopen( _name.c_str(), "a+" );
|
|
fclose( temp );
|
|
break;
|
|
}
|
|
case WriteAppend: fmode = "a"; break;
|
|
default: break;
|
|
}
|
|
|
|
if (!(_handle = fopen(_name.c_str(), fmode)))
|
|
{
|
|
_updateStatus();
|
|
return false;
|
|
}
|
|
|
|
_status = Open;
|
|
return true;
|
|
}
|
|
|
|
bool PosixFile::close()
|
|
{
|
|
if (_handle)
|
|
{
|
|
#ifdef DEBUG_SPEW
|
|
Platform::outputDebugString( "[PosixFile] closing '%s'", _name.c_str() );
|
|
#endif
|
|
|
|
fflush(_handle);
|
|
fclose(_handle);
|
|
_handle = 0;
|
|
}
|
|
|
|
_status = Closed;
|
|
return true;
|
|
}
|
|
|
|
U32 PosixFile::getPosition()
|
|
{
|
|
if (_status == Open || _status == EndOfFile)
|
|
return ftell(_handle);
|
|
|
|
return 0;
|
|
}
|
|
|
|
U32 PosixFile::setPosition(U32 delta, SeekMode mode)
|
|
{
|
|
if (_status != Open && _status != EndOfFile)
|
|
return 0;
|
|
|
|
S32 fmode = 0;
|
|
switch (mode)
|
|
{
|
|
case Begin: fmode = SEEK_SET; break;
|
|
case Current: fmode = SEEK_CUR; break;
|
|
case End: fmode = SEEK_END; break;
|
|
default: break;
|
|
}
|
|
|
|
if (fseek(_handle, delta, fmode))
|
|
{
|
|
_status = UnknownError;
|
|
return 0;
|
|
}
|
|
|
|
_status = Open;
|
|
|
|
return ftell(_handle);
|
|
}
|
|
|
|
U32 PosixFile::read(void* dst, U32 size)
|
|
{
|
|
if (_status != Open && _status != EndOfFile)
|
|
return 0;
|
|
|
|
U32 bytesRead = fread(dst, 1, size, _handle);
|
|
|
|
if (bytesRead != size)
|
|
{
|
|
if (feof(_handle))
|
|
_status = EndOfFile;
|
|
else
|
|
_updateStatus();
|
|
}
|
|
|
|
return bytesRead;
|
|
}
|
|
|
|
U32 PosixFile::write(const void* src, U32 size)
|
|
{
|
|
if ((_status != Open && _status != EndOfFile) || !size)
|
|
return 0;
|
|
|
|
U32 bytesWritten = fwrite(src, 1, size, _handle);
|
|
|
|
if (bytesWritten != size)
|
|
_updateStatus();
|
|
|
|
return bytesWritten;
|
|
}
|
|
|
|
void PosixFile::_updateStatus()
|
|
{
|
|
switch (errno)
|
|
{
|
|
case EACCES: _status = AccessDenied; break;
|
|
case ENOSPC: _status = FileSystemFull; break;
|
|
case ENOTDIR: _status = NoSuchFile; break;
|
|
case ENOENT: _status = NoSuchFile; break;
|
|
case EISDIR: _status = AccessDenied; break;
|
|
case EROFS: _status = AccessDenied; break;
|
|
default: _status = UnknownError; break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
PosixDirectory::PosixDirectory(const Path& path,String name)
|
|
{
|
|
_path = path;
|
|
_name = name;
|
|
_status = Closed;
|
|
_handle = 0;
|
|
}
|
|
|
|
PosixDirectory::~PosixDirectory()
|
|
{
|
|
if (_handle)
|
|
close();
|
|
}
|
|
|
|
Path PosixDirectory::getName() const
|
|
{
|
|
return _path;
|
|
}
|
|
|
|
bool PosixDirectory::open()
|
|
{
|
|
if ((_handle = opendir(_name)) == 0)
|
|
{
|
|
_updateStatus();
|
|
return false;
|
|
}
|
|
|
|
_status = Open;
|
|
return true;
|
|
}
|
|
|
|
bool PosixDirectory::close()
|
|
{
|
|
if (_handle)
|
|
{
|
|
closedir(_handle);
|
|
_handle = NULL;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool PosixDirectory::read(Attributes* entry)
|
|
{
|
|
if (_status != Open)
|
|
return false;
|
|
|
|
struct dirent* de = readdir(_handle);
|
|
|
|
if (!de)
|
|
{
|
|
_status = EndOfFile;
|
|
return false;
|
|
}
|
|
|
|
// Skip "." and ".." entries
|
|
if (de->d_name[0] == '.' && (de->d_name[1] == '\0' ||
|
|
(de->d_name[1] == '.' && de->d_name[2] == '\0')))
|
|
return read(entry);
|
|
|
|
// The dirent structure doesn't actually return much beside
|
|
// the name, so we must call stat for more info.
|
|
struct stat info;
|
|
String file = _name + "/" + de->d_name;
|
|
|
|
int error = stat(file.c_str(),&info);
|
|
|
|
if (error < 0)
|
|
{
|
|
_updateStatus();
|
|
return false;
|
|
}
|
|
copyStatAttributes(info,entry);
|
|
entry->name = de->d_name;
|
|
return true;
|
|
}
|
|
|
|
U32 PosixDirectory::calculateChecksum()
|
|
{
|
|
// Return checksum of current entry
|
|
return 0;
|
|
}
|
|
|
|
bool PosixDirectory::getAttributes(Attributes* attr)
|
|
{
|
|
struct stat info;
|
|
if (stat(_name.c_str(),&info))
|
|
{
|
|
_updateStatus();
|
|
return false;
|
|
}
|
|
|
|
copyStatAttributes(info,attr);
|
|
attr->name = _path;
|
|
return true;
|
|
}
|
|
|
|
FileNode::NodeStatus PosixDirectory::getStatus() const
|
|
{
|
|
return _status;
|
|
}
|
|
|
|
void PosixDirectory::_updateStatus()
|
|
{
|
|
switch (errno)
|
|
{
|
|
case EACCES: _status = AccessDenied; break;
|
|
case ENOTDIR: _status = NoSuchFile; break;
|
|
case ENOENT: _status = NoSuchFile; break;
|
|
default: _status = UnknownError; break;
|
|
}
|
|
}
|
|
|
|
} // Namespace POSIX
|
|
|
|
} // Namespace Torque
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifndef TORQUE_OS_MAC // Mac has its own native FS build on top of the POSIX one.
|
|
|
|
Torque::FS::FileSystemRef Platform::FS::createNativeFS( const String &volume )
|
|
{
|
|
return new Posix::PosixFileSystem( volume );
|
|
}
|
|
|
|
#endif
|
|
|
|
String Platform::FS::getAssetDir()
|
|
{
|
|
return Platform::getExecutablePath();
|
|
}
|
|
|
|
/// Function invoked by the kernel layer to install OS specific
|
|
/// file systems.
|
|
bool Platform::FS::InstallFileSystems()
|
|
{
|
|
Platform::FS::Mount( "/", Platform::FS::createNativeFS( String() ) );
|
|
|
|
// Setup the current working dir.
|
|
char buffer[PATH_MAX];
|
|
if (::getcwd(buffer,sizeof(buffer)))
|
|
{
|
|
// add trailing '/' if it isn't there
|
|
if (buffer[dStrlen(buffer) - 1] != '/')
|
|
dStrcat(buffer, "/", PATH_MAX);
|
|
|
|
Platform::FS::SetCwd(buffer);
|
|
}
|
|
|
|
// Mount the home directory
|
|
if (char* home = getenv("HOME"))
|
|
Platform::FS::Mount( "home", Platform::FS::createNativeFS(home) );
|
|
|
|
return true;
|
|
}
|