Files
2026-11-01 18:38:49 +01:00

337 lines
8.6 KiB
C++

/*
Copyright 2025 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "directory.h"
#include "types.h"
#include <string>
#ifdef NOWIDE_WINDOWS
#include "nowide/stackstring.hpp"
#include <cstring>
#include <windows.h>
#ifdef TARGET_UWP
// Define dirent type constants if not already defined
#ifndef DT_DIR
#define DT_DIR 4
#endif
#ifndef DT_REG
#define DT_REG 8
#endif
#endif // UWP
namespace flycast
{
#ifdef TARGET_UWP
struct DIR_UWP {
HANDLE hFind;
WIN32_FIND_DATAW findData;
bool firstRead;
bool hasNext;
};
DIR *opendir(char const *dirname)
{
std::string searchPath(dirname);
if (!searchPath.empty() && searchPath.back() != '\\' && searchPath.back() != '/')
searchPath += "\\";
searchPath += "*";
nowide::wstackstring wsearchPath;
if (!wsearchPath.convert(searchPath.c_str())) {
errno = EINVAL;
return nullptr;
}
DIR_UWP* dir = new DIR_UWP;
dir->hFind = FindFirstFileExFromAppW(wsearchPath.get(), FindExInfoBasic, &dir->findData,
FindExSearchNameMatch, nullptr, 0);
if (dir->hFind == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
delete dir;
if (error == ERROR_ACCESS_DENIED)
errno = EACCES;
else if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND)
errno = ENOENT;
else
errno = error;
return nullptr;
}
dir->firstRead = true;
dir->hasNext = true;
return (DIR*)dir;
}
#else
DIR *opendir(char const *dirname)
{
nowide::wstackstring wname;
if (!wname.convert(dirname)) {
errno = EINVAL;
return nullptr;
}
return (DIR *)::_wopendir(wname.get());
}
#endif
#ifdef TARGET_UWP
dirent *readdir(DIR *dirstream)
{
DIR_UWP* dir = (DIR_UWP*)dirstream;
if (!dir->hasNext)
return nullptr;
static dirent d;
if (dir->firstRead) {
dir->firstRead = false;
} else {
if (!FindNextFileW(dir->hFind, &dir->findData)) {
dir->hasNext = false;
return nullptr;
}
}
// Skip hidden files/directories on UWP
// if (dir->findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
// return readdir(dirstream); // Skip to next entry
nowide::stackstring name;
if (!name.convert(dir->findData.cFileName)) {
errno = EINVAL;
return nullptr;
}
d.d_ino = 0;
d.d_off = 0;
// Set d_type based on file attributes
if (dir->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
d.d_type = DT_DIR;
else
d.d_type = DT_REG;
d.d_reclen = sizeof(dirent);
strncpy(d.d_name, name.get(), sizeof(d.d_name) - 1);
d.d_name[sizeof(d.d_name) - 1] = '\0';
d.d_namlen = strlen(d.d_name);
return &d;
}
int closedir(DIR *dirstream)
{
DIR_UWP* dir = (DIR_UWP*)dirstream;
if (dir->hFind != INVALID_HANDLE_VALUE)
FindClose(dir->hFind);
delete dir;
return 0;
}
#else
dirent *readdir(DIR *dirstream)
{
_WDIR *wdir = (_WDIR *)dirstream;
_wdirent *wdirent = ::_wreaddir(wdir);
if (wdirent == nullptr)
return nullptr;
nowide::stackstring name;
if (!name.convert(wdirent->d_name)) {
errno = EINVAL;
return nullptr;
}
static dirent d;
d.d_ino = wdirent->d_ino;
d.d_off = wdirent->d_off;
d.d_type = wdirent->d_type;
d.d_reclen = sizeof(dirent);
strncpy(d.d_name, name.get(), sizeof(d.d_name) - 1);
d.d_name[sizeof(d.d_name) - 1] = '\0';
d.d_namlen = strlen(d.d_name);
return &d;
}
int closedir(DIR *dirstream)
{
return ::_wclosedir((_WDIR *)dirstream);
}
#endif
inline static void _set_errno(int error)
{
#ifdef _MSC_VER
::_set_errno (error);
#else
errno = error;
#endif
}
int stat(const char *filename, struct stat *buf)
{
nowide::wstackstring wname;
if (!wname.convert(filename)) {
_set_errno(EINVAL);
return -1;
}
#ifdef TARGET_UWP
WIN32_FILE_ATTRIBUTE_DATA attrs;
bool rc = GetFileAttributesExFromAppW(wname.get(), GetFileExInfoStandard, &attrs);
if (!rc)
{
_set_errno(GetLastError());
return -1;
}
memset(buf, 0, sizeof(struct stat));
if (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
buf->st_mode = S_IFDIR;
else
buf->st_mode = S_IFREG;
buf->st_size = attrs.nFileSizeLow;
constexpr UINT64 WINDOWS_TICK = 10000000u;
constexpr UINT64 SEC_TO_UNIX_EPOCH = 11644473600llu;
buf->st_ctime = (unsigned)(((UINT64)attrs.ftCreationTime.dwLowDateTime | ((UINT64)attrs.ftCreationTime.dwHighDateTime << 32)) / WINDOWS_TICK - SEC_TO_UNIX_EPOCH);
buf->st_mtime = (unsigned)(((UINT64)attrs.ftLastWriteTime.dwLowDateTime | ((UINT64)attrs.ftLastWriteTime.dwHighDateTime << 32)) / WINDOWS_TICK - SEC_TO_UNIX_EPOCH);
buf->st_atime =(unsigned)(((UINT64)attrs.ftLastAccessTime.dwLowDateTime | ((UINT64)attrs.ftLastAccessTime.dwHighDateTime << 32)) / WINDOWS_TICK - SEC_TO_UNIX_EPOCH);
return 0;
#else
struct _stat _st;
int rc = _wstat(wname.get(), &_st);
buf->st_ctime = _st.st_ctime;
buf->st_mtime = _st.st_mtime;
buf->st_atime = _st.st_atime;
buf->st_dev = _st.st_dev;
buf->st_ino = _st.st_ino;
buf->st_uid = _st.st_uid;
buf->st_gid = _st.st_gid;
buf->st_mode = _st.st_mode;
buf->st_nlink = _st.st_nlink;
buf->st_rdev = _st.st_rdev;
buf->st_size = _st.st_size;
return rc;
#endif
}
int access(const char *filename, int how)
{
nowide::wstackstring wname;
if (!wname.convert(filename)) {
_set_errno(EINVAL);
return -1;
}
#ifdef TARGET_UWP
// For UWP just check if the file exists
WIN32_FILE_ATTRIBUTE_DATA attrs;
bool rc = GetFileAttributesExFromAppW(wname.get(), GetFileExInfoStandard, &attrs);
if (!rc)
{
DWORD error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND)
_set_errno(ENOENT);
else if (error == ERROR_ACCESS_DENIED)
_set_errno(EACCES);
else
_set_errno(error);
return -1;
}
// Only fail on write access check for readonly files
// With broadFileSystemAccess, we generally have read access
if (how == W_OK && (attrs.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
{
_set_errno(EACCES);
return -1;
}
return 0;
#else
return ::_waccess(wname.get(), how);
#endif
}
int mkdir(const char *path, mode_t mode)
{
nowide::wstackstring wpath;
if (!wpath.convert(path)) {
errno = EINVAL;
return -1;
}
return ::_wmkdir(wpath.get());
}
int rename(const char *oldpath, const char *newpath)
{
nowide::wstackstring woldpath, wnewpath;
if (!woldpath.convert(oldpath) || !wnewpath.convert(newpath)) {
errno = EINVAL;
return -1;
}
// _wrename() fails when newpath exists. Replace it, as POSIX rename() does.
if (!MoveFileExW(woldpath.get(), wnewpath.get(), MOVEFILE_REPLACE_EXISTING)) {
errno = GetLastError() == ERROR_FILE_NOT_FOUND ? ENOENT : EACCES;
return -1;
}
return 0;
}
int unlink(const char *path)
{
nowide::wstackstring wpath;
if (!wpath.convert(path)) {
errno = EINVAL;
return -1;
}
return ::_wunlink(wpath.get());
}
} // namespace flycast
#endif // NOWIDE_WINDOWS
bool safeFullSave(const std::string& path, const void *data, size_t len)
{
// Write to a temp file next to the real one, then rename it over the real
// path, so the file on disk is always either the previous image or the new
// one: a crash or kill during the write leaves the previous save intact.
const std::string tmpFile = path + ".tmp";
FILE *f = nowide::fopen(tmpFile.c_str(), "wb");
if (f == nullptr)
{
ERROR_LOG(COMMON, "Cannot open temp save file '%s'", tmpFile.c_str());
return false;
}
bool ok = std::fwrite(data, 1, len, f) == len;
std::fclose(f);
if (!ok)
{
ERROR_LOG(COMMON, "Failed or truncated write to file '%s'", tmpFile.c_str());
flycast::unlink(tmpFile.c_str());
return false;
}
// Commit. On failure the previous file is still there.
if (flycast::rename(tmpFile.c_str(), path.c_str()) != 0)
{
ERROR_LOG(COMMON, "Failed to commit file (rename '%s' -> '%s')", tmpFile.c_str(), path.c_str());
flycast::unlink(tmpFile.c_str());
return false;
}
return true;
}