#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2021 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library 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 IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_STORAGE_SOURCE 1 #include "WindowsFile.h" #if defined(NUCLEX_STORAGE_WIN32) #include "WindowsFileApi.h" #include "WindowsFileAccessApi.h" #include "DateHelper.h" #include "PathHelper.h" #include "Nuclex/Storage/FileSystem/Path.h" #include namespace { // ------------------------------------------------------------------------------------------- // /// Scoped unlocker for a shared lock on a mutex class SharedMutexLockScope { /// Initializes the mutex unlocking scope on the specified mutex public: SharedMutexLockScope(std::shared_timed_mutex &mutex) : mutex(mutex) {} /// Releases the shared lock on the scope's mutex public: ~SharedMutexLockScope() { this->mutex.unlock_shared(); } /// Mutex that will be unlocked when the scope is destroyed private: std::shared_timed_mutex &mutex; }; // ------------------------------------------------------------------------------------------- // /// Scoped unlocker for an exclusive lock on a mutex class ExclusiveMutexLockScope { /// Initializes the mutex unlocking scope on the specified mutex public: ExclusiveMutexLockScope(std::shared_timed_mutex &mutex) : mutex(mutex) {} /// Releases the exclusive lock on the scope's mutex public: ~ExclusiveMutexLockScope() { this->mutex.unlock(); } /// Mutex that will be unlocked when the scope is destroyed private: std::shared_timed_mutex &mutex; }; // ------------------------------------------------------------------------------------------- // /// Value that indicates an invalid position of the file cursor const std::uint64_t InvalidPosition = static_cast(-1); // ------------------------------------------------------------------------------------------- // } // anonymous namespace namespace Nuclex { namespace Storage { namespace FileSystem { namespace Windows { // ------------------------------------------------------------------------------------------- // WindowsFile::WindowsFile( std::shared_ptr backend, const std::string &absolutePath, bool create /* = false */ ) : backend(backend), absolutePath(absolutePath), name(Path::GetFilename(absolutePath)), readerWriterLock(), fileHandle(INVALID_HANDLE_VALUE), isOpenedWritable(false), fileCursor(InvalidPosition) { // TODO: Implement eager creation of the file is 'create' is true } // ------------------------------------------------------------------------------------------- // WindowsFile::~WindowsFile() { // When the destructor runs, all references to the file have been dropped, // thus there can be no other access. The mutex is not needed. HANDLE fileHandle = this->fileHandle.load(std::memory_order_relaxed); if(fileHandle != INVALID_HANDLE_VALUE) { BOOL wasClosed = ::CloseHandle(INVALID_HANDLE_VALUE); assert(wasClosed && u8"Handle has been closed"); } } // ------------------------------------------------------------------------------------------- // std::uint64_t WindowsFile::GetSize() const { DWORD lastErrorCode; // Enter a shared lock on the file descriptor so we can access it // without having to fear a race condition this->readerWriterLock.lock_shared(); { SharedMutexLockScope fileDescriptorAccessScope(this->readerWriterLock); // Try to do a 'stat()' on the file (either by handle or by name) to get information HANDLE fileHandle = this->fileHandle.load(std::memory_order_relaxed); if(fileHandle == INVALID_HANDLE_VALUE) { std::wstring utf16Path = PathHelper::PrefixedUtf16FromUtf8Path(this->absolutePath); ::WIN32_FILE_ATTRIBUTE_DATA fileAttributes; BOOL result =::GetFileAttributesExW( utf16Path.c_str(), ::GetFileExInfoStandard, &fileAttributes ); if(likely(result == 0)) { return ( (static_cast(fileAttributes.nFileSizeHigh) << 32) | static_cast(fileAttributes.nFileSizeLow) ); } else { lastErrorCode = ::GetLastError(); } } else { LARGE_INTEGER fileSize; BOOL result = ::GetFileSizeEx(fileHandle, &fileSize); if(likely(result == 0)) { return static_cast(fileSize.QuadPart); } else { lastErrorCode = ::GetLastError(); } } } // If this place is reached, the stat() call failed and the error number has been // saved in 'result'. Throw a descriptive exception to indicate the problem. std::string errorMessage(u8"Could not obtain file size of '"); errorMessage.append(this->absolutePath); errorMessage.append(u8"'"); Helpers::WindowsApi::ThrowExceptionForSystemError(errorMessage, lastErrorCode); } // ------------------------------------------------------------------------------------------- // const std::string &WindowsFile::GetName() const { return this->name; } // ------------------------------------------------------------------------------------------- // void WindowsFile::Rename(const std::string &name) { throw - 1; } // ------------------------------------------------------------------------------------------- // std::time_t WindowsFile::GetLastModificationTime() const { DWORD lastErrorCode; // Enter a shared lock on the file descriptor so we can access it // without having to fear a race condition this->readerWriterLock.lock_shared(); { SharedMutexLockScope fileDescriptorAccessScope(this->readerWriterLock); // Try to do a 'stat()' on the file (either by handle or by name) to get information HANDLE fileHandle = this->fileHandle.load(std::memory_order_relaxed); if(fileHandle == INVALID_HANDLE_VALUE) { std::wstring utf16Path = PathHelper::PrefixedUtf16FromUtf8Path(this->absolutePath); ::WIN32_FILE_ATTRIBUTE_DATA fileAttributes; BOOL result = ::GetFileAttributesExW( utf16Path.c_str(), ::GetFileExInfoStandard, &fileAttributes ); if(likely(result == 0)) { return DateHelper::PosixTimeFromFileTime(fileAttributes.ftLastWriteTime); } else { lastErrorCode = ::GetLastError(); } } else { BY_HANDLE_FILE_INFORMATION fileInformation; BOOL result = ::GetFileInformationByHandle(fileHandle, &fileInformation); if(likely(result == 0)) { return DateHelper::PosixTimeFromFileTime(fileInformation.ftLastWriteTime); } else { lastErrorCode = ::GetLastError(); } } } // If this place is reached, the stat() call failed and the error number has been // saved in 'result'. Throw a descriptive exception to indicate the problem. std::string errorMessage(u8"Could not obtain last modification time of '"); errorMessage.append(this->absolutePath); errorMessage.append(u8"'"); Helpers::WindowsApi::ThrowExceptionForSystemError(errorMessage, lastErrorCode); } // ------------------------------------------------------------------------------------------- // void WindowsFile::ReadAt( std::uint64_t location, void *buffer, std::size_t count ) const { this->readerWriterLock.lock_shared(); // Fetch the file handle and check if it's already open. HANDLE fileHandle = this->fileHandle.load(std::memory_order_relaxed); while(unlikely(fileHandle == INVALID_HANDLE_VALUE)) { // File wasn't opened. Upgrade the lock to an exclusive one so we can // attempt to open the file for reading this->readerWriterLock.unlock_shared(); openFileForReading(); this->readerWriterLock.lock_shared(); // Reload the file descriptor. There a small but non-zero chance that // something happened to it between releasing the exclusive lock and // re-acquiring a shared lock, so we re-check and loop. fileHandle = this->fileHandle.load(std::memory_order_relaxed); } /* if(location != this->fileCursor) { this->fileCursor = WindowsFileAccessApi::SetFilePointer(this->fileHandle, location); } std::size_t readByteCount = WindowsFileAccessApi::Read(this->fileHandle, buffer, count); this->fileCursor += readByteCount; if(readByteCount != count) { } //this->~WindowsFile */ throw -1; } // ------------------------------------------------------------------------------------------- // void WindowsFile::WriteAt( std::uint64_t location, const void *buffer, std::size_t count ) { throw -1; } // ------------------------------------------------------------------------------------------- // void WindowsFile::Flush() { throw -1; } // ------------------------------------------------------------------------------------------- // void WindowsFile::openFileForReading() const { this->readerWriterLock.lock(); { ExclusiveMutexLockScope fileDescriptorAccessScope(this->readerWriterLock); // Of course, another thread may have done the same, so re-check // the file descriptor HANDLE fileHandle = this->fileHandle.load(std::memory_order_relaxed); if(likely(fileHandle == INVALID_HANDLE_VALUE)) { this->fileHandle.store( WindowsFileAccessApi::OpenFileForReading(this->absolutePath), std::memory_order_relaxed ); } } } // ------------------------------------------------------------------------------------------- // void WindowsFile::openFileForWriting() const { this->readerWriterLock.lock(); { ExclusiveMutexLockScope fileDescriptorAccessScope(this->readerWriterLock); // Of course, another thread may have done the same, so re-check // the file descriptor HANDLE fileHandle = this->fileHandle.load(std::memory_order_relaxed); if(likely(fileHandle == INVALID_HANDLE_VALUE)) { this->fileHandle.store( WindowsFileAccessApi::OpenFileForWriting(this->absolutePath), std::memory_order_relaxed ); } else { bool isOpenedWritable = this->isOpenedWritable.load(std::memory_order_relaxed); } } } // ------------------------------------------------------------------------------------------- // }}}} // namespace Nuclex::Storage::FileSystem::Windows #endif // defined(NUCLEX_STORAGE_WIN32)