FileSystem: Add LockedFile helper class

This commit is contained in:
Stenzek
2025-10-13 20:08:16 +10:00
parent 8f0c9dd171
commit e7f4034678
2 changed files with 125 additions and 0 deletions

View File

@@ -1464,6 +1464,98 @@ s64 FileSystem::GetPathFileSize(const char* path)
return sd.Size;
}
FileSystem::LockedFile FileSystem::OpenLockedFile(const char* path, bool for_write, Error* error /* = nullptr */)
{
static constexpr u32 DEFAULT_FILE_LOCK_TIMEOUT = 100;
return OpenLockedFile(path, for_write, DEFAULT_FILE_LOCK_TIMEOUT, error);
}
FileSystem::LockedFile FileSystem::OpenLockedFile(const char* path, bool for_write, u32 timeout_ms, Error* error)
{
const FileSystem::FileShareMode share_mode =
for_write ? FileSystem::FileShareMode::DenyReadWrite : FileSystem::FileShareMode::DenyWrite;
#ifdef _WIN32
const char* mode = for_write ? "r+b" : "rb";
#else
// Always open read/write on Linux, since we need it for flock().
const char* mode = "r+b";
#endif
std::FILE* fp = FileSystem::OpenSharedCFile(path, mode, share_mode, error);
if (!fp)
{
// Doesn't exist? Create it.
if (errno == ENOENT)
{
if (!for_write)
return {};
mode = "w+b";
fp = FileSystem::OpenSharedCFile(path, mode, share_mode, error);
}
}
if (!fp)
{
// If there's a sharing violation, try again for 100ms.
if (errno != EACCES)
return {};
Timer timer;
while (timer.GetTimeMilliseconds() <= static_cast<float>(timeout_ms))
{
fp = FileSystem::OpenSharedCFile(path, mode, share_mode, error);
if (fp)
break;
if (errno != EACCES)
return {};
}
if (!fp)
{
Error::SetStringFmt(error, "Timed out while trying to open file", Path::GetFileTitle(path));
return {};
}
}
Error lock_error;
LockedFile ret(fp, &lock_error);
if (!ret.IsLocked())
ERROR_LOG("Failed to lock file {}: {}", Path::GetFileTitle(path), lock_error.GetDescription());
return ret;
}
FileSystem::LockedFile::LockedFile(std::FILE* fp, Error* lock_error)
: ManagedCFilePtr(fp)
#ifdef HAS_POSIX_FILE_LOCK
,
m_lock(fp, true, lock_error)
#endif
{
}
std::FILE* FileSystem::LockedFile::release()
{
return nullptr;
}
#ifdef HAS_POSIX_FILE_LOCK
void FileSystem::LockedFile::reset()
{
// avoid race where the file isn't flushed before it's unlocked
if (*this)
std::fflush(get());
m_lock.Unlock();
ManagedCFilePtr::reset();
}
#endif
std::optional<DynamicHeapArray<u8>> FileSystem::ReadBinaryFile(const char* path, Error* error)
{
std::optional<DynamicHeapArray<u8>> ret;

View File

@@ -183,6 +183,39 @@ private:
#endif
/// Provides a simple RAII wrapper around a locked file, where only a single process can have it open at a time.
class LockedFile : public ManagedCFilePtr
{
public:
LockedFile() = default;
LockedFile(LockedFile&& move) = default;
LockedFile(const LockedFile&) = delete;
LockedFile(std::FILE* fp, Error* lock_error);
~LockedFile() = default;
LockedFile& operator=(LockedFile&& move) = default;
LockedFile& operator=(const LockedFile&) = delete;
#ifdef HAS_POSIX_FILE_LOCK
ALWAYS_INLINE bool IsLocked() const { return m_lock.IsLocked(); }
void reset();
#else
ALWAYS_INLINE bool IsLocked() const { return true; }
#endif
private:
// Hide release(), it doesn't make sense here.
std::FILE* release();
#ifdef HAS_POSIX_FILE_LOCK
POSIXLock m_lock;
#endif
};
LockedFile OpenLockedFile(const char* path, bool for_write, Error* error = nullptr);
LockedFile OpenLockedFile(const char* path, bool for_write, u32 timeout_ms, Error* error);
std::optional<DynamicHeapArray<u8>> ReadBinaryFile(const char* path, Error* error = nullptr);
std::optional<DynamicHeapArray<u8>> ReadBinaryFile(std::FILE* fp, Error* error = nullptr);
std::optional<std::string> ReadFileToString(const char* path, Error* error = nullptr);