7652b3d64a
Updater tests for path utils The updater now doesn't use splitpath on Windows (fixes problems with Windows XP) Fix up paths for the OSX updater - should now install the updates into the right place Fix translations install path - translation isntall and deploy should be fixed
518 lines
11 KiB
C++
518 lines
11 KiB
C++
#include "FileUtils.h"
|
|
|
|
#include "DirIterator.h"
|
|
#include "Log.h"
|
|
#include "Platform.h"
|
|
#include "StringUtils.h"
|
|
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
// this actually works with mingw32, which we use.
|
|
#include <libgen.h>
|
|
|
|
#ifdef PLATFORM_UNIX
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/time.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
FileUtils::IOException::IOException(const std::string& error)
|
|
{
|
|
init(errno,error);
|
|
}
|
|
|
|
FileUtils::IOException::IOException(int errorCode, const std::string& error)
|
|
{
|
|
init(errorCode,error);
|
|
}
|
|
|
|
void FileUtils::IOException::init(int errorCode, const std::string& error)
|
|
{
|
|
m_error = error;
|
|
|
|
#ifdef PLATFORM_UNIX
|
|
m_errorCode = errorCode;
|
|
|
|
if (m_errorCode > 0)
|
|
{
|
|
m_error += " details: " + std::string(strerror(m_errorCode));
|
|
}
|
|
#endif
|
|
|
|
#ifdef PLATFORM_WINDOWS
|
|
m_errorCode = 0;
|
|
m_error += " GetLastError returned: " + intToStr(GetLastError());
|
|
#endif
|
|
}
|
|
|
|
FileUtils::IOException::~IOException() throw ()
|
|
{
|
|
}
|
|
|
|
FileUtils::IOException::Type FileUtils::IOException::type() const
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
switch (m_errorCode)
|
|
{
|
|
case 0:
|
|
return NoError;
|
|
case EROFS:
|
|
return ReadOnlyFileSystem;
|
|
case ENOSPC:
|
|
return DiskFull;
|
|
default:
|
|
return Unknown;
|
|
}
|
|
#else
|
|
return Unknown;
|
|
#endif
|
|
}
|
|
|
|
bool FileUtils::fileExists(const char* path) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
struct stat fileInfo;
|
|
if (lstat(path,&fileInfo) != 0)
|
|
{
|
|
if (errno == ENOENT)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
throw IOException("Error checking for file " + std::string(path));
|
|
}
|
|
}
|
|
return true;
|
|
#else
|
|
DWORD result = GetFileAttributes(path);
|
|
if (result == INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
int FileUtils::fileMode(const char* path) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
struct stat fileInfo;
|
|
if (stat(path,&fileInfo) != 0)
|
|
{
|
|
throw IOException("Error reading file permissions for " + std::string(path));
|
|
}
|
|
return fileInfo.st_mode;
|
|
#else
|
|
// not implemented for Windows
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
void FileUtils::chmod(const char* path, int mode) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
if (::chmod(path,static_cast<mode_t>(mode)) != 0)
|
|
{
|
|
throw IOException("Failed to set permissions on " + std::string(path) + " to " + intToStr(mode));
|
|
}
|
|
#else
|
|
// TODO - Not implemented under Windows - all files
|
|
// get default permissions
|
|
#endif
|
|
}
|
|
|
|
void FileUtils::moveFile(const char* src, const char* dest) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
if (rename(src,dest) != 0)
|
|
{
|
|
throw IOException("Unable to rename " + std::string(src) + " to " + std::string(dest));
|
|
}
|
|
#else
|
|
if (!MoveFile(src,dest))
|
|
{
|
|
throw IOException("Unable to rename " + std::string(src) + " to " + std::string(dest));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FileUtils::mkpath(const char* dir) throw (IOException)
|
|
{
|
|
std::string currentPath;
|
|
std::istringstream stream(dir);
|
|
while (!stream.eof())
|
|
{
|
|
std::string segment;
|
|
std::getline(stream,segment,'/');
|
|
currentPath += segment;
|
|
if (!currentPath.empty() && !fileExists(currentPath.c_str()))
|
|
{
|
|
mkdir(currentPath.c_str());
|
|
}
|
|
currentPath += '/';
|
|
}
|
|
}
|
|
|
|
void FileUtils::mkdir(const char* dir) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
if (::mkdir(dir,S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) != 0)
|
|
{
|
|
throw IOException("Unable to create directory " + std::string(dir));
|
|
}
|
|
#else
|
|
if (!CreateDirectory(dir,0 /* default security attributes */))
|
|
{
|
|
throw IOException("Unable to create directory " + std::string(dir));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FileUtils::rmdir(const char* dir) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
if (::rmdir(dir) != 0)
|
|
{
|
|
throw IOException("Unable to remove directory " + std::string(dir));
|
|
}
|
|
#else
|
|
if (!RemoveDirectory(dir))
|
|
{
|
|
throw IOException("Unable to remove directory " + std::string(dir));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FileUtils::createSymLink(const char* link, const char* target) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
if (symlink(target,link) != 0)
|
|
{
|
|
throw IOException("Unable to create symlink " + std::string(link) + " to " + std::string(target));
|
|
}
|
|
#else
|
|
// symlinks are not supported under Windows (at least, not universally.
|
|
// Windows Vista and later do actually support symlinks)
|
|
LOG(Warn,"Skipping symlink creation - not implemented in Windows");
|
|
#endif
|
|
}
|
|
|
|
void FileUtils::removeFile(const char* src) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
if (unlink(src) != 0)
|
|
{
|
|
if (errno != ENOENT)
|
|
{
|
|
throw IOException("Unable to remove file " + std::string(src));
|
|
}
|
|
}
|
|
#else
|
|
if (!DeleteFile(src))
|
|
{
|
|
if (GetLastError() == ERROR_ACCESS_DENIED)
|
|
{
|
|
// if another process is using the file, try moving it to
|
|
// a temporary directory and then
|
|
// scheduling it for deletion on reboot
|
|
std::string tempDeletePathBase = tempPath();
|
|
tempDeletePathBase += '/';
|
|
tempDeletePathBase += fileName(src);
|
|
|
|
int suffix = 0;
|
|
std::string tempDeletePath = tempDeletePathBase;
|
|
while (fileExists(tempDeletePath.c_str()))
|
|
{
|
|
++suffix;
|
|
tempDeletePath = tempDeletePathBase + '_' + intToStr(suffix);
|
|
}
|
|
|
|
LOG(Warn,"Unable to remove file " + std::string(src) + " - it may be in use. Moving to "
|
|
+ tempDeletePath + " and scheduling delete on reboot.");
|
|
moveFile(src,tempDeletePath.c_str());
|
|
MoveFileEx(tempDeletePath.c_str(),0,MOVEFILE_DELAY_UNTIL_REBOOT);
|
|
}
|
|
else if (GetLastError() != ERROR_FILE_NOT_FOUND)
|
|
{
|
|
throw IOException("Unable to remove file " + std::string(src));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
std::string FileUtils::fileName(const char* path)
|
|
{
|
|
char* pathCopy = strdup(path);
|
|
std::string basename = ::basename(pathCopy);
|
|
free(pathCopy);
|
|
return basename;
|
|
}
|
|
|
|
std::string FileUtils::dirname(const char* path)
|
|
{
|
|
char* pathCopy = strdup(path);
|
|
std::string dirname = ::dirname(pathCopy);
|
|
free(pathCopy);
|
|
return dirname;
|
|
}
|
|
|
|
void FileUtils::touch(const char* path) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
// see http://pubs.opengroup.org/onlinepubs/9699919799/utilities/touch.html
|
|
//
|
|
// we use utimes/futimes instead of utimensat/futimens for compatibility
|
|
// with older Linux and Mac
|
|
|
|
if (fileExists(path))
|
|
{
|
|
utimes(path,0 /* use current date/time */);
|
|
}
|
|
else
|
|
{
|
|
int fd = creat(path,S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
|
|
if (fd != -1)
|
|
{
|
|
futimes(fd,0 /* use current date/time */);
|
|
close(fd);
|
|
}
|
|
else
|
|
{
|
|
throw IOException("Unable to touch file " + std::string(path));
|
|
}
|
|
}
|
|
#else
|
|
HANDLE result = CreateFile(path,GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
0,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
0);
|
|
if (result == INVALID_HANDLE_VALUE)
|
|
{
|
|
throw IOException("Unable to touch file " + std::string(path));
|
|
}
|
|
else
|
|
{
|
|
CloseHandle(result);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FileUtils::rmdirRecursive(const char* path) throw (IOException)
|
|
{
|
|
// remove dir contents
|
|
DirIterator dir(path);
|
|
while (dir.next())
|
|
{
|
|
std::string name = dir.fileName();
|
|
if (name != "." && name != "..")
|
|
{
|
|
if (dir.isDir())
|
|
{
|
|
rmdir(dir.filePath().c_str());
|
|
}
|
|
else
|
|
{
|
|
removeFile(dir.filePath().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove the directory itself
|
|
rmdir(path);
|
|
}
|
|
|
|
std::string FileUtils::canonicalPath(const char* path)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
// on Linux and Mac OS 10.6, realpath() can allocate the required
|
|
// amount of memory automatically, however Mac OS 10.5 does not support
|
|
// this, so we used a fixed-sized buffer on all platforms
|
|
char canonicalPathBuffer[PATH_MAX+1];
|
|
if (realpath(path,canonicalPathBuffer) != 0)
|
|
{
|
|
return std::string(canonicalPathBuffer);
|
|
}
|
|
else
|
|
{
|
|
throw IOException("Error reading canonical path for " + std::string(path));
|
|
}
|
|
#else
|
|
throw IOException("canonicalPath() not implemented");
|
|
#endif
|
|
}
|
|
|
|
std::string FileUtils::toWindowsPathSeparators(const std::string& str)
|
|
{
|
|
std::string result = str;
|
|
std::replace(result.begin(),result.end(),'/','\\');
|
|
return result;
|
|
}
|
|
|
|
std::string FileUtils::toUnixPathSeparators(const std::string& str)
|
|
{
|
|
std::string result = str;
|
|
std::replace(result.begin(),result.end(),'\\','/');
|
|
return result;
|
|
}
|
|
|
|
std::string FileUtils::tempPath()
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
std::string tmpDir(notNullString(getenv("TMPDIR")));
|
|
if (tmpDir.empty())
|
|
{
|
|
tmpDir = "/tmp";
|
|
}
|
|
return tmpDir;
|
|
#else
|
|
char buffer[MAX_PATH+1];
|
|
GetTempPath(MAX_PATH+1,buffer);
|
|
return toUnixPathSeparators(buffer);
|
|
#endif
|
|
}
|
|
|
|
bool startsWithDriveLetter(const char* path)
|
|
{
|
|
return strlen(path) >= 2 &&
|
|
(isalpha(path[0])) &&
|
|
path[1] == ':';
|
|
}
|
|
|
|
bool FileUtils::isRelative(const char* path)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
return strlen(path) == 0 || path[0] != '/';
|
|
#else
|
|
// on Windows, a path is relative if it does not start with:
|
|
// - '\\' (a UNC name)
|
|
// - '[Drive Letter]:\'
|
|
// - A single backslash
|
|
//
|
|
// the input path is assumed to have already been converted to use
|
|
// Unix-style path separators
|
|
|
|
std::string pathStr(path);
|
|
|
|
if ((!pathStr.empty() && pathStr.at(0) == '/') ||
|
|
(startsWith(pathStr,"//")) ||
|
|
(startsWithDriveLetter(pathStr.c_str())))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FileUtils::writeFile(const char* path, const char* data, int length) throw (IOException)
|
|
{
|
|
std::ofstream stream(path,std::ios::binary | std::ios::trunc);
|
|
stream.write(data,length);
|
|
}
|
|
|
|
std::string FileUtils::readFile(const char* path) throw (IOException)
|
|
{
|
|
std::ifstream inputFile(path, std::ios::in | std::ios::binary);
|
|
std::string content;
|
|
inputFile.seekg(0, std::ios::end);
|
|
content.resize(static_cast<unsigned int>(inputFile.tellg()));
|
|
inputFile.seekg(0, std::ios::beg);
|
|
inputFile.read(&content[0], static_cast<int>(content.size()));
|
|
return content;
|
|
}
|
|
|
|
void FileUtils::copyFile(const char* src, const char* dest) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
std::ifstream inputFile(src,std::ios::binary);
|
|
std::ofstream outputFile(dest,std::ios::binary | std::ios::trunc);
|
|
|
|
if (!inputFile.good())
|
|
{
|
|
throw IOException("Failed to read file " + std::string(src));
|
|
}
|
|
if (!outputFile.good())
|
|
{
|
|
throw IOException("Failed to write file " + std::string(dest));
|
|
}
|
|
|
|
outputFile << inputFile.rdbuf();
|
|
|
|
if (inputFile.bad())
|
|
{
|
|
throw IOException("Error reading file " + std::string(src));
|
|
}
|
|
if (outputFile.bad())
|
|
{
|
|
throw IOException("Error writing file " + std::string(dest));
|
|
}
|
|
|
|
chmod(dest,fileMode(src));
|
|
#else
|
|
if (!CopyFile(src,dest,FALSE))
|
|
{
|
|
throw IOException("Failed to copy " + std::string(src) + " to " + std::string(dest));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
std::string FileUtils::makeAbsolute(const char* path, const char* basePath)
|
|
{
|
|
if (isRelative(path))
|
|
{
|
|
assert(!isRelative(basePath));
|
|
return std::string(basePath) + '/' + std::string(path);
|
|
}
|
|
else
|
|
{
|
|
return path;
|
|
}
|
|
}
|
|
|
|
void FileUtils::chdir(const char* path) throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
if (::chdir(path) != 0)
|
|
{
|
|
throw FileUtils::IOException("Unable to change directory");
|
|
}
|
|
#else
|
|
if (!SetCurrentDirectory(path))
|
|
{
|
|
throw FileUtils::IOException("Unable to change directory");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
std::string FileUtils::getcwd() throw (IOException)
|
|
{
|
|
#ifdef PLATFORM_UNIX
|
|
char path[PATH_MAX];
|
|
if (!::getcwd(path,PATH_MAX))
|
|
{
|
|
throw FileUtils::IOException("Failed to get current directory");
|
|
}
|
|
return std::string(path);
|
|
#else
|
|
char path[MAX_PATH];
|
|
if (GetCurrentDirectory(MAX_PATH,path) == 0)
|
|
{
|
|
throw FileUtils::IOException("Failed to get current directory");
|
|
}
|
|
return toUnixPathSeparators(std::string(path));
|
|
#endif
|
|
}
|
|
|