2023-06-20 04:33:09 +00:00
|
|
|
#include "StarFile.hpp"
|
|
|
|
#include "StarFormat.hpp"
|
|
|
|
#include "StarRandom.hpp"
|
|
|
|
#include "StarEncode.hpp"
|
|
|
|
#include "StarMathCommon.hpp"
|
|
|
|
#include "StarThread.hpp"
|
|
|
|
|
|
|
|
#include "StarString_windows.hpp"
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <io.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <windows.h>
|
|
|
|
|
|
|
|
#ifndef MAX_PATH
|
|
|
|
#define MAX_PATH 1024
|
|
|
|
#endif
|
|
|
|
|
|
|
|
namespace Star {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
OVERLAPPED makeOverlapped(StreamOffset offset) {
|
|
|
|
OVERLAPPED overlapped = {};
|
|
|
|
overlapped.Offset = offset;
|
|
|
|
overlapped.OffsetHigh = offset >> 32;
|
|
|
|
return overlapped;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String File::convertDirSeparators(String const& path) {
|
|
|
|
return path.replace("/", "\\");
|
|
|
|
}
|
|
|
|
|
|
|
|
String File::currentDirectory() {
|
|
|
|
WCHAR buffer[MAX_PATH];
|
|
|
|
size_t len = GetCurrentDirectoryW(MAX_PATH, buffer);
|
|
|
|
if (len == 0)
|
|
|
|
throw IOException("GetCurrentDirectory failed");
|
|
|
|
|
|
|
|
return utf16ToString(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
void File::changeDirectory(const String& dirName) {
|
|
|
|
if (!SetCurrentDirectoryW(stringToUtf16(dirName).get()))
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("could not change directory to {}", dirName));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void File::makeDirectory(String const& dirName) {
|
|
|
|
if (CreateDirectoryW(stringToUtf16(dirName).get(), NULL) == 0) {
|
|
|
|
auto error = GetLastError();
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("could not create directory '{}', {}", dirName, error));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool File::exists(String const& path) {
|
|
|
|
WIN32_FIND_DATAW findFileData;
|
|
|
|
const HANDLE handle = FindFirstFileW(stringToUtf16(path).get(), &findFileData);
|
|
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
|
|
return false;
|
|
|
|
FindClose(handle);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool File::isFile(String const& path) {
|
|
|
|
WIN32_FIND_DATAW findFileData;
|
|
|
|
const HANDLE handle = FindFirstFileW(stringToUtf16(path).get(), &findFileData);
|
|
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
|
|
return false;
|
|
|
|
FindClose(handle);
|
|
|
|
return (FILE_ATTRIBUTE_DIRECTORY & findFileData.dwFileAttributes) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool File::isDirectory(String const& path) {
|
|
|
|
DWORD attribs = GetFileAttributesW(stringToUtf16(path.trimEnd("\\/")).get());
|
|
|
|
if (attribs == INVALID_FILE_ATTRIBUTES)
|
|
|
|
return false;
|
|
|
|
return attribs & FILE_ATTRIBUTE_DIRECTORY;
|
|
|
|
}
|
|
|
|
|
|
|
|
String File::fullPath(const String& path) {
|
|
|
|
WCHAR buffer[MAX_PATH];
|
|
|
|
|
|
|
|
size_t fullpath_size;
|
|
|
|
WCHAR* lpszLastNamePart;
|
|
|
|
|
|
|
|
fullpath_size = GetFullPathNameW(stringToUtf16(path).get(), (DWORD)MAX_PATH, buffer, (WCHAR**)&lpszLastNamePart);
|
|
|
|
if (0 == fullpath_size)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("GetFullPathName failed on path: '{}'", path);
|
2023-06-20 04:33:09 +00:00
|
|
|
if (fullpath_size >= MAX_PATH)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("GetFullPathName failed on path: '{}'", path);
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
return utf16ToString(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<std::pair<String, bool>> File::dirList(const String& dirName, bool skipDots) {
|
|
|
|
List<std::pair<String, bool>> fileList;
|
|
|
|
WIN32_FIND_DATAW findFileData;
|
|
|
|
HANDLE hFind;
|
|
|
|
|
|
|
|
hFind = FindFirstFileW(stringToUtf16(File::relativeTo(dirName, "*")).get(), &findFileData);
|
|
|
|
if (hFind == INVALID_HANDLE_VALUE)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("Invalid file handle in dirList of '{}', error is %u", dirName, GetLastError()));
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
while (true) {
|
|
|
|
String entry = utf16ToString(findFileData.cFileName);
|
|
|
|
if (!skipDots || (entry != "." && entry != ".."))
|
|
|
|
fileList.append({entry, (FILE_ATTRIBUTE_DIRECTORY & findFileData.dwFileAttributes) != 0});
|
|
|
|
if (!FindNextFileW(hFind, &findFileData))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
DWORD dwError = GetLastError();
|
|
|
|
FindClose(hFind);
|
|
|
|
|
|
|
|
if ((dwError != ERROR_NO_MORE_FILES) && (dwError != NO_ERROR))
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("FindNextFile error in dirList of '{}'. Error is %u", dirName, dwError));
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
return fileList;
|
|
|
|
}
|
|
|
|
|
|
|
|
String File::baseName(const String& fileName) {
|
|
|
|
return String(fileName).rextract("\\/");
|
|
|
|
}
|
|
|
|
|
|
|
|
String File::dirName(const String& fileName) {
|
|
|
|
if (fileName == "\\" || fileName == "/")
|
|
|
|
return "\\";
|
|
|
|
|
|
|
|
String directory = fileName;
|
|
|
|
directory.rextract("\\/");
|
|
|
|
if (directory.empty())
|
|
|
|
return ".";
|
|
|
|
else
|
|
|
|
return directory;
|
|
|
|
}
|
|
|
|
|
|
|
|
String File::relativeTo(String const& relativeTo, String const& path) {
|
|
|
|
if (path.beginsWith('/') || path.beginsWith('\\') || path.regexMatch("^[a-z]:", false, false))
|
|
|
|
return path;
|
|
|
|
|
|
|
|
String finalPath;
|
|
|
|
if (relativeTo.endsWith('\\') || relativeTo.endsWith('/'))
|
|
|
|
finalPath = relativeTo.substr(0, relativeTo.size() - 1);
|
|
|
|
else if (relativeTo.endsWith("\\.") || relativeTo.endsWith("/."))
|
|
|
|
finalPath = relativeTo.substr(0, relativeTo.size() - 2);
|
|
|
|
else
|
|
|
|
finalPath = relativeTo;
|
|
|
|
|
|
|
|
if (path.beginsWith(".\\") || path.beginsWith("./"))
|
|
|
|
finalPath += '\\' + path.substr(2);
|
|
|
|
else
|
|
|
|
finalPath += '\\' + path;
|
|
|
|
|
|
|
|
return finalPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
String File::temporaryFileName() {
|
|
|
|
WCHAR tempPath[MAX_PATH];
|
|
|
|
if (!GetTempPathW(MAX_PATH, tempPath)) {
|
|
|
|
auto error = GetLastError();
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("Could not call GetTempPath {}", error));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-27 10:23:44 +00:00
|
|
|
return relativeTo(utf16ToString(tempPath), strf("starbound.tmpfile.{}", hexEncode(Random::randBytes(16))));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FilePtr File::temporaryFile() {
|
|
|
|
return open(temporaryFileName(), IOMode::ReadWrite);
|
|
|
|
}
|
|
|
|
|
|
|
|
FilePtr File::ephemeralFile() {
|
|
|
|
auto file = temporaryFile();
|
|
|
|
DeleteFileW(stringToUtf16(file->fileName()).get());
|
|
|
|
file->m_filename = "";
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
|
|
|
String File::temporaryDirectory() {
|
|
|
|
WCHAR tempPath[MAX_PATH];
|
|
|
|
if (!GetTempPathW(MAX_PATH, tempPath)) {
|
|
|
|
auto error = GetLastError();
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("Could not call GetTempPath {}", error));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
2023-06-27 10:23:44 +00:00
|
|
|
String dirname = relativeTo(utf16ToString(tempPath), strf("starbound.tmpdir.{}", hexEncode(Random::randBytes(16))));
|
2023-06-20 04:33:09 +00:00
|
|
|
makeDirectory(dirname);
|
|
|
|
return dirname;
|
|
|
|
}
|
|
|
|
|
|
|
|
void File::remove(String const& filename) {
|
|
|
|
if (isDirectory(filename)) {
|
|
|
|
if (!RemoveDirectoryW(stringToUtf16(filename).get())) {
|
|
|
|
auto error = GetLastError();
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("Rename error: {}", error));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
} else if (::_wremove(stringToUtf16(filename).get()) < 0) {
|
|
|
|
auto error = errno;
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("remove error: {}", strerror(error));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void File::rename(String const& source, String const& target) {
|
|
|
|
bool replace = File::exists(target);
|
|
|
|
auto temp = target + ".tmp";
|
|
|
|
|
|
|
|
if (replace) {
|
|
|
|
if (!DeleteFileW(stringToUtf16(temp).get())) {
|
|
|
|
auto error = GetLastError();
|
|
|
|
if (error != ERROR_FILE_NOT_FOUND)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("error deleting existing temp file: {}", error));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
if (!MoveFileExW(stringToUtf16(target).get(), stringToUtf16(temp).get(), MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH)) {
|
|
|
|
auto error = GetLastError();
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("error temporary file '{}': {}", temp, GetLastError()));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!MoveFileExW(stringToUtf16(source).get(), stringToUtf16(target).get(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH)) {
|
|
|
|
auto error = GetLastError();
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("Rename error: {}", error));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (replace && !DeleteFileW(stringToUtf16(temp).get())) {
|
|
|
|
auto error = GetLastError();
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException(strf("error deleting temp file '{}': {}", temp, GetLastError()));
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void File::overwriteFileWithRename(char const* data, size_t len, String const& filename, String const& newSuffix) {
|
|
|
|
String newFile = filename + newSuffix;
|
|
|
|
|
|
|
|
try {
|
|
|
|
auto file = File::open(newFile, IOMode::Write | IOMode::Truncate);
|
|
|
|
file->writeFull(data, len);
|
|
|
|
file->sync();
|
|
|
|
file->close();
|
|
|
|
|
|
|
|
File::rename(newFile, filename);
|
|
|
|
} catch (IOException const&) {
|
|
|
|
// HACK: Been having trouble on windows with the write / flush / move
|
|
|
|
// sequence, try super hard to just write the file non-atomically in case
|
|
|
|
// of weird file locking problems instead of erroring.
|
|
|
|
|
|
|
|
// Ignore any error on removal of the maybe existing newFile
|
|
|
|
::_wremove(stringToUtf16(newFile).get());
|
|
|
|
|
|
|
|
writeFile(data, len, filename);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void* File::fopen(char const* filename, IOMode mode) {
|
|
|
|
DWORD desiredAccess = 0;
|
|
|
|
if (mode & IOMode::Read)
|
|
|
|
desiredAccess |= GENERIC_READ;
|
|
|
|
if (mode & IOMode::Write)
|
|
|
|
desiredAccess |= GENERIC_WRITE;
|
|
|
|
|
|
|
|
DWORD creationDisposition = 0;
|
|
|
|
if (mode & IOMode::Write)
|
|
|
|
creationDisposition = OPEN_ALWAYS;
|
|
|
|
else
|
|
|
|
creationDisposition = OPEN_EXISTING;
|
|
|
|
|
|
|
|
HANDLE file = CreateFileW(stringToUtf16(String(filename)).get(),
|
|
|
|
desiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
|
|
|
|
creationDisposition, 0, NULL);
|
|
|
|
|
|
|
|
if (file == INVALID_HANDLE_VALUE)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("could not open file '{}' {}", filename, GetLastError());
|
2023-06-20 04:33:09 +00:00
|
|
|
|
|
|
|
LARGE_INTEGER szero;
|
|
|
|
szero.QuadPart = 0;
|
|
|
|
if (!SetFilePointerEx(file, szero, NULL, 0)) {
|
|
|
|
CloseHandle(file);
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("could not set file pointer in fopen '{}' {}", filename, GetLastError());
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mode & IOMode::Truncate) {
|
|
|
|
if (!SetEndOfFile(file)) {
|
|
|
|
CloseHandle(file);
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("could not set end of file in fopen '{}' {}", filename, GetLastError());
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode & IOMode::Append) {
|
|
|
|
LARGE_INTEGER size;
|
|
|
|
if (GetFileSizeEx(file, &size) == 0) {
|
|
|
|
CloseHandle(file);
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("could not get file size in fopen '{}' {}", filename, GetLastError());
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
if (!SetFilePointerEx(file, size, NULL, 0)) {
|
|
|
|
CloseHandle(file);
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("could not set file pointer in fopen '{}' {}", filename, GetLastError());
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (void*)file;
|
|
|
|
}
|
|
|
|
|
|
|
|
void File::fseek(void* f, StreamOffset offset, IOSeek seekMode) {
|
|
|
|
HANDLE file = (HANDLE)f;
|
|
|
|
|
|
|
|
LARGE_INTEGER loffset;
|
|
|
|
loffset.QuadPart = offset;
|
|
|
|
|
|
|
|
if (seekMode == IOSeek::Relative)
|
|
|
|
SetFilePointerEx(file, loffset, nullptr, FILE_CURRENT);
|
|
|
|
else if (seekMode == IOSeek::Absolute)
|
|
|
|
SetFilePointerEx(file, loffset, nullptr, FILE_BEGIN);
|
|
|
|
else
|
|
|
|
SetFilePointerEx(file, loffset, nullptr, FILE_END);
|
|
|
|
}
|
|
|
|
|
|
|
|
StreamOffset File::ftell(void* f) {
|
|
|
|
HANDLE file = (HANDLE)f;
|
|
|
|
LARGE_INTEGER pos;
|
|
|
|
LARGE_INTEGER szero;
|
|
|
|
szero.QuadPart = 0;
|
|
|
|
SetFilePointerEx(file, szero, &pos, FILE_CURRENT);
|
|
|
|
return pos.QuadPart;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t File::fread(void* f, char* data, size_t len) {
|
|
|
|
if (len == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
HANDLE file = (HANDLE)f;
|
|
|
|
|
|
|
|
DWORD numRead = 0;
|
|
|
|
int ret = ReadFile(file, data, len, &numRead, nullptr);
|
|
|
|
if (ret == 0) {
|
|
|
|
auto err = GetLastError();
|
|
|
|
if (err != ERROR_IO_PENDING)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("read error {}", err);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return numRead;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t File::fwrite(void* f, char const* data, size_t len) {
|
|
|
|
if (len == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
HANDLE file = (HANDLE)f;
|
|
|
|
|
|
|
|
DWORD numWritten = 0;
|
|
|
|
int ret = WriteFile(file, data, len, &numWritten, nullptr);
|
|
|
|
if (ret == 0) {
|
|
|
|
auto err = GetLastError();
|
|
|
|
if (err != ERROR_IO_PENDING)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("write error {}", err);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return numWritten;
|
|
|
|
}
|
|
|
|
|
|
|
|
void File::fsync(void* f) {
|
|
|
|
HANDLE file = (HANDLE)f;
|
|
|
|
if (FlushFileBuffers(file) == 0)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("fsync error {}", GetLastError());
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void File::fclose(void* f) {
|
|
|
|
HANDLE file = (HANDLE)f;
|
|
|
|
CloseHandle(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
StreamOffset File::fsize(void* f) {
|
|
|
|
HANDLE file = (HANDLE)f;
|
|
|
|
LARGE_INTEGER size;
|
|
|
|
if (GetFileSizeEx(file, &size) == 0)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("could not get file size in fsize {}", GetLastError());
|
2023-06-20 04:33:09 +00:00
|
|
|
return size.QuadPart;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t File::pread(void* f, char* data, size_t len, StreamOffset position) {
|
|
|
|
HANDLE file = (HANDLE)f;
|
|
|
|
DWORD numRead = 0;
|
|
|
|
OVERLAPPED overlapped = makeOverlapped(position);
|
|
|
|
int ret = ReadFile(file, data, len, &numRead, &overlapped);
|
|
|
|
if (ret == 0) {
|
|
|
|
auto err = GetLastError();
|
|
|
|
if (err != ERROR_IO_PENDING)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("pread error {}", err);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return numRead;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t File::pwrite(void* f, char const* data, size_t len, StreamOffset position) {
|
|
|
|
HANDLE file = (HANDLE)f;
|
|
|
|
DWORD numWritten = 0;
|
|
|
|
OVERLAPPED overlapped = makeOverlapped(position);
|
|
|
|
int ret = WriteFile(file, data, len, &numWritten, &overlapped);
|
|
|
|
if (ret == 0) {
|
|
|
|
auto err = GetLastError();
|
|
|
|
if (err != ERROR_IO_PENDING)
|
2023-06-27 10:23:44 +00:00
|
|
|
throw IOException::format("pwrite error {}", err);
|
2023-06-20 04:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return numWritten;
|
|
|
|
}
|
|
|
|
|
|
|
|
void File::resize(void* f, StreamOffset size) {
|
|
|
|
HANDLE file = (HANDLE)f;
|
|
|
|
LARGE_INTEGER s;
|
|
|
|
s.QuadPart = size;
|
|
|
|
SetFilePointerEx(file, s, NULL, 0);
|
|
|
|
SetEndOfFile(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|