587 lines
15 KiB
C++
587 lines
15 KiB
C++
// Copyright (c) 2013, IOnU Security, Inc.
|
|
// Copyright (c) 2016, Sequence Logic, Inc. All rights reserved.
|
|
#include "slfile.h"
|
|
|
|
#include "eyetime.h"
|
|
|
|
#include "../internal.h"
|
|
|
|
#ifdef WIN32
|
|
#include <Windows.h>
|
|
#include <io.h>
|
|
#include <direct.h>
|
|
#else
|
|
#include <libgen.h>
|
|
#include <unistd.h>
|
|
#include <sys/time.h>
|
|
#include <dirent.h>
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
|
|
using namespace sequencelogic;
|
|
|
|
|
|
const char File::DIR_SEP = '/';
|
|
|
|
File& File::operator=(const File &oldFile)
|
|
{
|
|
if (&oldFile != this)
|
|
{
|
|
_mount = oldFile._mount;
|
|
_path = oldFile._path;
|
|
_fileName = oldFile._fileName;
|
|
_ext = oldFile._ext;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
File::File(const File &parent, const std::string &child)
|
|
{
|
|
std::string tmpName = parent._mount + parent._path;
|
|
if ((tmpName != "") && (tmpName[tmpName.size()-1] != DIR_SEP))
|
|
tmpName += DIR_SEP;
|
|
if ((child != "") && (tmpName != "") &&
|
|
(tmpName[tmpName.size()-1] == DIR_SEP) && (child[0] == DIR_SEP))
|
|
tmpName.erase(tmpName.size()-1);
|
|
tmpName += child;
|
|
|
|
setFilename(tmpName);
|
|
}
|
|
|
|
File::File(const File &parent, const char *pChild)
|
|
{
|
|
std::string tmpName = parent._mount + parent._path;
|
|
if ((tmpName != "") && (tmpName[tmpName.size()-1] != DIR_SEP))
|
|
tmpName += DIR_SEP;
|
|
if ((pChild != NULL) && (pChild[0] != '\0'))
|
|
tmpName += pChild;
|
|
|
|
setFilename(tmpName);
|
|
}
|
|
|
|
File::File(const std::string &mount, const std::string &path, const char *pFileName) : _mount(mount), _path(path)
|
|
{
|
|
// Parse the filename...
|
|
if ((pFileName != NULL) && (pFileName[0] != '\0'))
|
|
{
|
|
_ext = "";
|
|
_fileName = pFileName;
|
|
|
|
if (_fileName.rfind('.') != std::string::npos)
|
|
{
|
|
_ext = _fileName;
|
|
_ext.erase(0, _fileName.rfind('.'));
|
|
_fileName.erase(_fileName.rfind('.'));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Split this string into it's various "filename" parts, and set the member
|
|
* variables accordingly.
|
|
*/
|
|
void File::setFilename(const char *pFilename)
|
|
{
|
|
std::string tmpFilename = pFilename;
|
|
std::replace_if(tmpFilename.begin(), tmpFilename.end(), [] (std::string::value_type nChar) { return (nChar == '\\'); }, File::DIR_SEP);
|
|
#ifdef WIN32
|
|
_mount.resize(_MAX_DRIVE);
|
|
_path.resize(_MAX_DIR);
|
|
_fileName.resize(_MAX_FNAME);
|
|
_ext.resize(_MAX_EXT);
|
|
|
|
if (_splitpath_s(tmpFilename.c_str(), &_mount[0], _MAX_DRIVE, &_path[0], _MAX_DIR, &_fileName[0], _MAX_FNAME, &_ext[0], _MAX_EXT) != 0)
|
|
{
|
|
_mount = "";
|
|
_path = "";
|
|
_fileName = "";
|
|
_ext = "";
|
|
}
|
|
else
|
|
{
|
|
_mount.resize(::strlen(_mount.c_str()));
|
|
_path.resize(::strlen(_path.c_str()));
|
|
_fileName.resize(::strlen(_fileName.c_str()));
|
|
_ext.resize(::strlen(_ext.c_str()));
|
|
}
|
|
#else
|
|
// Good ol' *NIX. No parser for filenames...
|
|
// Hmmm, 'dirname' and 'pathname' appear to modify the buffer passed in...
|
|
_mount = "";
|
|
_ext = "";
|
|
if (tmpFilename[tmpFilename.length()-1] == DIR_SEP)
|
|
_path = tmpFilename;
|
|
else
|
|
{
|
|
_path = dirname(&tmpFilename[0]);
|
|
tmpFilename = pFilename;
|
|
_fileName = basename(&tmpFilename[0]);
|
|
if (_fileName.rfind('.') != std::string::npos)
|
|
{
|
|
_ext = _fileName;
|
|
_ext.erase(0, _fileName.rfind('.'));
|
|
_fileName.erase(_fileName.rfind('.'));
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Get the parent directory.
|
|
*/
|
|
File File::getParentFile() const
|
|
{
|
|
// The "parent file" is the full path, without the file..
|
|
std::string tmpName = _mount;
|
|
tmpName += _path;
|
|
if ((tmpName.length() > 0) && (tmpName[tmpName.length()-1] != DIR_SEP))
|
|
tmpName += DIR_SEP;
|
|
|
|
return File(tmpName);
|
|
}
|
|
|
|
/**
|
|
* Does this file exist?
|
|
*/
|
|
bool File::exists() const
|
|
{
|
|
#ifdef WIN32
|
|
return (_access_s(getAbsolutePath().c_str(), 00) == 0);
|
|
#else
|
|
return (access(getAbsolutePath().c_str(), F_OK) == 0);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Can I read this file?
|
|
*/
|
|
bool File::canRead() const
|
|
{
|
|
#ifdef WIN32
|
|
return (_access_s(getAbsolutePath().c_str(), 04) == 0);
|
|
#else
|
|
return (access(getAbsolutePath().c_str(), R_OK) == 0);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Can I write this file?
|
|
*/
|
|
bool File::canWrite() const
|
|
{
|
|
#ifdef WIN32
|
|
return (_access_s(getAbsolutePath().c_str(), 02) == 0);
|
|
#else
|
|
return (access(getAbsolutePath().c_str(), W_OK) == 0);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Is this a file?
|
|
*/
|
|
bool File::isFile() const
|
|
{
|
|
bool bRetVal = false;
|
|
|
|
if (_fileName != "" || _ext != "")
|
|
{
|
|
#ifdef WIN32
|
|
struct _stat fStat = {0};
|
|
if (_stat(getAbsolutePath().c_str(), &fStat) == 0)
|
|
bRetVal = (fStat.st_mode & _S_IFREG) == _S_IFREG;
|
|
#else
|
|
struct stat fStat;
|
|
if(stat(getAbsolutePath().c_str(), &fStat) == 0)
|
|
bRetVal = S_ISREG(fStat.st_mode);
|
|
#endif
|
|
}
|
|
|
|
return bRetVal;
|
|
}
|
|
|
|
/**
|
|
* Is this a directory?
|
|
*/
|
|
bool File::isDirectory() const
|
|
{
|
|
bool bRetVal = false;
|
|
|
|
if (_fileName == "")
|
|
{
|
|
std::string dirName = getAbsolutePath();
|
|
if (*dirName.rbegin() == DIR_SEP)
|
|
{
|
|
// Workaround for Android's lack of pop_back
|
|
//dirName.pop_back();
|
|
std::string::reverse_iterator rIter = dirName.rbegin();
|
|
++rIter;
|
|
dirName.erase(rIter.base());
|
|
}
|
|
|
|
#ifdef WIN32
|
|
struct _stat fStat = {0};
|
|
if (_stat(dirName.c_str(), &fStat) == 0)
|
|
bRetVal = (fStat.st_mode & _S_IFDIR) == _S_IFDIR;
|
|
#else
|
|
struct stat fStat;
|
|
if(stat(getAbsolutePath().c_str(), &fStat) == 0)
|
|
bRetVal = S_ISDIR(fStat.st_mode);
|
|
#endif
|
|
}
|
|
|
|
return bRetVal;
|
|
}
|
|
|
|
/**
|
|
* Delete the file.
|
|
*/
|
|
bool File::deleteFile() const
|
|
{
|
|
bool bRetVal = false;
|
|
if (isFile())
|
|
bRetVal = (remove(getAbsolutePath().c_str()) == 0);
|
|
#ifdef _DEBUG
|
|
if (!bRetVal)
|
|
{
|
|
bool bReadOnly = (errno == EACCES);
|
|
bool bFileNotFound (errno == ENOENT);
|
|
}
|
|
#endif
|
|
return bRetVal;
|
|
}
|
|
|
|
/**
|
|
* Get the size of this file...
|
|
*/
|
|
unsigned int File::length() const
|
|
{
|
|
unsigned int nRetVal = 0;
|
|
|
|
if (isFile())
|
|
{
|
|
std::ifstream fileStr;
|
|
fileStr.open(getAbsolutePath(), std::ios_base::binary | std::ios_base::in);
|
|
if (fileStr.good() && !fileStr.eof() && fileStr.is_open())
|
|
{
|
|
fileStr.seekg(0, std::ios_base::beg);
|
|
std::ifstream::pos_type startPos = fileStr.tellg();
|
|
fileStr.seekg(0, std::ios_base::end);
|
|
nRetVal = static_cast<unsigned int>(fileStr.tellg() - startPos);
|
|
fileStr.close();
|
|
}
|
|
}
|
|
|
|
return nRetVal;
|
|
}
|
|
|
|
/**
|
|
* Make all the directories in the path.
|
|
*/
|
|
bool File::mkdirs() const
|
|
{
|
|
bool bRetVal = true;
|
|
if (!exists())
|
|
{
|
|
// Directory doesn't exist... Make it...
|
|
std::vector<std::string> dirs;
|
|
SplitString(_path, DIR_SEP, dirs);
|
|
std::string tmpPath = _mount;
|
|
|
|
for (unsigned int i = 0; i < dirs.size(); ++i)
|
|
{
|
|
if ((dirs[i] == ".") || (dirs[i] == ""))
|
|
continue;
|
|
|
|
if ((tmpPath != "") && (*tmpPath.rbegin() != DIR_SEP))
|
|
tmpPath += DIR_SEP;
|
|
tmpPath += dirs[i];
|
|
#ifdef WIN32
|
|
bRetVal &= ((_mkdir(tmpPath.c_str()) == 0) || (errno == EEXIST));
|
|
#else
|
|
bRetVal &= ((mkdir(tmpPath.c_str(), 0777) == 0) || (errno == EEXIST));
|
|
#endif
|
|
}
|
|
}
|
|
else if (isDirectory())
|
|
bRetVal = true;
|
|
|
|
return bRetVal;
|
|
}
|
|
|
|
/**
|
|
* When was this file last modified?
|
|
*/
|
|
sequencelogic::EyeTime File::lastModified() const
|
|
{
|
|
EyeTime retVal;
|
|
|
|
#ifdef WIN32
|
|
HANDLE hFile = ::CreateFile(getAbsolutePath().c_str(), GENERIC_READ,
|
|
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
FILETIME lastMod;
|
|
if (::GetFileTime(hFile, NULL, NULL, &lastMod))
|
|
{
|
|
SYSTEMTIME sysTime;
|
|
::FileTimeToSystemTime(&lastMod, &sysTime);
|
|
retVal.SetTime(sysTime);
|
|
}
|
|
::CloseHandle(hFile);
|
|
}
|
|
#else
|
|
struct stat fStat;
|
|
if(stat(getAbsolutePath().c_str(), &fStat) == 0)
|
|
{
|
|
#if defined(ANDROID)
|
|
long long timeMS = fStat.st_mtime * 1000;
|
|
timeMS += (fStat.st_mtime_nsec / 1000000);
|
|
#elif !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
|
|
long long timeMS = fStat.st_mtimespec.tv_sec * 1000;
|
|
timeMS += (fStat.st_mtimespec.tv_nsec / 1000000);
|
|
#else
|
|
long long timeMS = fStat.st_mtim.tv_sec * 1000;
|
|
timeMS += (fStat.st_mtim.tv_nsec / 1000000);
|
|
#endif
|
|
|
|
retVal.SetTimeMs(timeMS);
|
|
}
|
|
#endif
|
|
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* Set a file's last modified time.
|
|
*/
|
|
void File::setLastModified (const EyeTime &lastModified)
|
|
{
|
|
#ifdef WIN32
|
|
HANDLE hFile = ::CreateFile(getAbsolutePath().c_str(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
FILETIME lastWriteTime;
|
|
SYSTEMTIME tmpSysTime = const_cast<EyeTime &>(lastModified).GetSysTime();
|
|
if (!::SystemTimeToFileTime(&tmpSysTime, &lastWriteTime))
|
|
{
|
|
#ifdef DEBUG
|
|
std::stringstream msg;
|
|
msg << "File::setLastModified (" << __LINE__ << "): ERROR: Cannot convert SYSTEMTIME to FILETIME:\n";
|
|
msg << "File::setLastModified (" << __LINE__ << "): " << getLastError() << "\n";
|
|
::OutputDebugString(msg.str().c_str();
|
|
#endif
|
|
}
|
|
if (!::SetFileTime(hFile, NULL, NULL, &lastWriteTime))
|
|
{
|
|
#ifdef DEBUG
|
|
std::stringstream msg;
|
|
msg << "File::setLastModified (" << __LINE__ << "): ERROR: Cannot SetFileTime:\n";
|
|
msg << "File::setLastModified (" << __LINE__ << "): " << getLastError() << "\n";
|
|
::OutputDebugString(msg.str().c_str();
|
|
#endif
|
|
}
|
|
::CloseHandle(hFile);
|
|
}
|
|
#else
|
|
struct stat fStat;
|
|
if(stat(getAbsolutePath().c_str(), &fStat) == 0)
|
|
{
|
|
// Get the last access time. 'utime' set's the access/modified time to the current
|
|
// time if we don't specify a time.
|
|
timeval uTimeBuf[2];
|
|
|
|
#if defined(ANDROID)
|
|
uTimeBuf[0].tv_sec = fStat.st_atime;
|
|
uTimeBuf[0].tv_usec = (int32_t)(fStat.st_atime_nsec) / 1000;
|
|
#elif !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
|
|
uTimeBuf[0].tv_sec = fStat.st_atimespec.tv_sec;
|
|
uTimeBuf[0].tv_usec = (int32_t)(fStat.st_atimespec.tv_nsec) / 1000;
|
|
#else
|
|
uTimeBuf[0].tv_sec = fStat.st_atim.tv_sec;
|
|
uTimeBuf[0].tv_usec = (int32_t)(fStat.st_atim.tv_nsec) / 1000;
|
|
#endif
|
|
|
|
uTimeBuf[1].tv_sec = const_cast<EyeTime &>(lastModified).GetTime();
|
|
int32_t microSec = ((const_cast<EyeTime &>(lastModified).GetTimeMs()) % 1000) * 1000;
|
|
uTimeBuf[1].tv_usec = microSec;
|
|
|
|
utimes(getAbsolutePath().c_str(), uTimeBuf);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
std::string File::getAbsolutePath() const
|
|
{
|
|
std::string retVal = _mount;
|
|
retVal += _path;
|
|
if ((retVal != "") && (*retVal.rbegin() != DIR_SEP))
|
|
retVal += DIR_SEP;
|
|
retVal += _fileName + _ext;
|
|
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* Copy this file to the new filename.
|
|
*/
|
|
bool File::copyFile(const File &destFile)const
|
|
{
|
|
bool bRetVal = false;
|
|
|
|
std::ifstream inFile (getAbsolutePath().c_str(), std::ios::binary);
|
|
std::ofstream outFile (destFile.getAbsolutePath(), std::ios::binary);
|
|
|
|
if (!inFile.bad() && !outFile.bad())
|
|
{
|
|
outFile << inFile.rdbuf();
|
|
bRetVal = !outFile.bad();
|
|
}
|
|
|
|
outFile.close();
|
|
inFile.close();
|
|
|
|
return bRetVal;
|
|
}
|
|
|
|
/**
|
|
* If this is a directory, get a list of all the files in the directory. The list does *not*
|
|
* include the '.' or '..' names.
|
|
*
|
|
* @param fileList The list of files in the directory
|
|
* @return The number of files found
|
|
*/
|
|
unsigned int File::listFiles(std::vector<File> &fileList, const std::string &fileFilter /*= ""*/) const
|
|
{
|
|
fileList.clear();
|
|
|
|
if (isDirectory())
|
|
{
|
|
#ifdef WIN32
|
|
::WIN32_FIND_DATA fileData = {0};
|
|
|
|
std::string filesToFind = getDirectory();
|
|
if (fileFilter != "")
|
|
{
|
|
if (*filesToFind.rbegin() != DIR_SEP)
|
|
filesToFind += DIR_SEP;
|
|
filesToFind += "*";
|
|
filesToFind += fileFilter;
|
|
}
|
|
else if (*(filesToFind.rbegin()) == DIR_SEP)
|
|
filesToFind += "*";
|
|
else
|
|
{
|
|
filesToFind += DIR_SEP;
|
|
filesToFind += "*";
|
|
}
|
|
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
if ((hFile = ::FindFirstFile(filesToFind.c_str(), &fileData)) != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
if ((::strcmp(fileData.cFileName, ".") == 0) ||
|
|
(::strcmp(fileData.cFileName, "..") == 0))
|
|
continue;
|
|
|
|
if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
fileList.push_back(File(_mount, _path + fileData.cFileName + DIR_SEP, ""));
|
|
else
|
|
fileList.push_back(File(_mount, _path, fileData.cFileName));
|
|
} while (::FindNextFile(hFile, &fileData));
|
|
|
|
::FindClose(hFile);
|
|
}
|
|
#else
|
|
std::string dirName = getDirectory();
|
|
dirent *pDirEntry;
|
|
struct stat fStat;
|
|
DIR *pDir = opendir(dirName.c_str());
|
|
if (pDir != NULL)
|
|
{
|
|
if (dirName[dirName.length()-1] != DIR_SEP)
|
|
dirName += DIR_SEP;
|
|
|
|
while ((pDirEntry = readdir(pDir)) != NULL)
|
|
{
|
|
std::string fileName = pDirEntry->d_name;
|
|
if ((fileName == ".") || (fileName == ".."))
|
|
continue;
|
|
|
|
if ((fileFilter != "") && (fileName.find(fileFilter) == std::string::npos))
|
|
continue;
|
|
|
|
if (stat(std::string(dirName + fileName).c_str(), &fStat) == 0)
|
|
{
|
|
if (S_ISDIR(fStat.st_mode))
|
|
fileList.push_back(File(_mount, _path + fileName + DIR_SEP, ""));
|
|
else
|
|
fileList.push_back(File(_mount, _path, fileName.c_str()));
|
|
}
|
|
}
|
|
closedir(pDir);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return (unsigned int)fileList.size();
|
|
}
|
|
|
|
/**
|
|
* Get the name. This is just the filename (no extension), or the last directory name (if there is no filename set).
|
|
*/
|
|
std::string File::getName() const
|
|
{
|
|
if (_fileName != "")
|
|
return _fileName;
|
|
else
|
|
{
|
|
std::string retVal = _path;
|
|
if (retVal != "")
|
|
{
|
|
if (*retVal.rbegin() == DIR_SEP)
|
|
retVal.pop_back();
|
|
retVal.erase(0, retVal.rfind(DIR_SEP));
|
|
if (*(retVal.begin()) == DIR_SEP)
|
|
retVal.erase(0, 1);
|
|
}
|
|
return retVal;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the absolute filename, in a URI format:
|
|
* 'file://{absolute path name'
|
|
*/
|
|
std::string File::toURI() const
|
|
{
|
|
std::string retVal = "file://";
|
|
if (isDirectory())
|
|
retVal += (getDirectory() + "/");
|
|
else
|
|
retVal += getAbsolutePath();
|
|
|
|
return retVal;
|
|
}
|
|
|
|
namespace sequencelogic
|
|
{
|
|
std::ostream& operator<<(std::ostream &out, const File &node)
|
|
{
|
|
out << node.getAbsolutePath();
|
|
return out;
|
|
}
|
|
}
|