// ====================================================================== // \title Os/FileSystem.cpp // \brief common function implementation for Os::FileSystem // ====================================================================== #include #include namespace Os { FileSystem::FileSystem() : m_handle_storage(), m_delegate(*FileSystemInterface::getDelegate(m_handle_storage)) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); } FileSystem::~FileSystem() { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); m_delegate.~FileSystemInterface(); } FileSystemHandle* FileSystem::getHandle() { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); return this->m_delegate.getHandle(); } FileSystem::Status FileSystem::_removeDirectory(const char* path) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); FW_ASSERT(path != nullptr); return this->m_delegate._removeDirectory(path); } FileSystem::Status FileSystem::_removeFile(const char* path) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); FW_ASSERT(path != nullptr); return this->m_delegate._removeFile(path); } FileSystem::Status FileSystem::_rename(const char* sourcePath, const char* destPath) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); FW_ASSERT(sourcePath != nullptr); FW_ASSERT(destPath != nullptr); return this->m_delegate._rename(sourcePath, destPath); } FileSystem::Status FileSystem::_getPathType(const char* path, PathType& pathType) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); FW_ASSERT(path != nullptr); return this->m_delegate._getPathType(path, pathType); } FileSystem::Status FileSystem::_getWorkingDirectory(char* path, FwSizeType bufferSize) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); FW_ASSERT(path != nullptr); FW_ASSERT(bufferSize > 0); // because bufferSize=0 would trigger a malloc in some implementations (e.g. Posix) return this->m_delegate._getWorkingDirectory(path, bufferSize); } FileSystem::Status FileSystem::_changeWorkingDirectory(const char* path) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); FW_ASSERT(path != nullptr); return this->m_delegate._changeWorkingDirectory(path); } FileSystem::Status FileSystem::_getFreeSpace(const char* path, FwSizeType& totalBytes, FwSizeType& freeBytes) { FW_ASSERT(&this->m_delegate == reinterpret_cast(&this->m_handle_storage[0])); FW_ASSERT(path != nullptr); return this->m_delegate._getFreeSpace(path, totalBytes, freeBytes); } void FileSystem::init() { // Force trigger on the fly singleton setup (void)FileSystem::getSingleton(); } FileSystem& FileSystem::getSingleton() { static FileSystem s_singleton; return s_singleton; } // ------------------------------------------------------------ // Static functions calling implementation-specific operations // ------------------------------------------------------------ FileSystem::Status FileSystem::removeDirectory(const char* path) { return FileSystem::getSingleton()._removeDirectory(path); } FileSystem::Status FileSystem::removeFile(const char* path) { return FileSystem::getSingleton()._removeFile(path); } FileSystem::Status FileSystem::rename(const char* sourcePath, const char* destPath) { return FileSystem::getSingleton()._rename(sourcePath, destPath); } FileSystem::Status FileSystem::getWorkingDirectory(char* path, FwSizeType bufferSize) { return FileSystem::getSingleton()._getWorkingDirectory(path, bufferSize); } FileSystem::Status FileSystem::changeWorkingDirectory(const char* path) { return FileSystem::getSingleton()._changeWorkingDirectory(path); } FileSystem::Status FileSystem::getFreeSpace(const char* path, FwSizeType& totalBytes, FwSizeType& freeBytes) { return FileSystem::getSingleton()._getFreeSpace(path, totalBytes, freeBytes); } // ------------------------------------------------------------ // Additional functions built on top of OS-specific operations // ------------------------------------------------------------ FileSystem::Status FileSystem::createDirectory(const char* path, bool errorIfAlreadyExists) { FW_ASSERT(path != nullptr); Status status = Status::OP_OK; Os::Directory dir; // If errorIfAlreadyExists is true, use CREATE_EXCLUSIVE mode, otherwise use CREATE_IF_MISSING Directory::OpenMode mode = errorIfAlreadyExists ? Directory::OpenMode::CREATE_EXCLUSIVE : Directory::OpenMode::CREATE_IF_MISSING; Directory::Status dirStatus = dir.open(path, mode); dir.close(); if (dirStatus != Directory::OP_OK) { return FileSystem::handleDirectoryError(dirStatus); } return status; } FileSystem::Status FileSystem::touch(const char* path) { FW_ASSERT(path != nullptr); Status status = Status::OP_OK; Os::File file; File::Status file_status = file.open(path, Os::File::OPEN_WRITE); file.close(); if (file_status != File::OP_OK) { status = FileSystem::handleFileError(file_status); } return status; } FileSystem::PathType FileSystem::getPathType(const char* path) { FW_ASSERT(path != nullptr); PathType pathType; Status status = getSingleton()._getPathType(path, pathType); if (status != Status::OP_OK) { return PathType::NOT_EXIST; } return pathType; } // end getPathType bool FileSystem::exists(const char* path) { return FileSystem::getPathType(path) != PathType::NOT_EXIST; } // end exists FileSystem::Status FileSystem::copyFile(const char* sourcePath, const char* destPath) { FW_ASSERT(sourcePath != nullptr); FW_ASSERT(destPath != nullptr); Os::File source; Os::File destination; Os::File::Status fileStatus = source.open(sourcePath, Os::File::OPEN_READ); if (fileStatus != Os::File::OP_OK) { return FileSystem::handleFileError(fileStatus); } fileStatus = destination.open(destPath, Os::File::OPEN_WRITE); if (fileStatus != Os::File::OP_OK) { return FileSystem::handleFileError(fileStatus); } FwSizeType sourceFileSize = 0; FileSystem::Status fs_status = FileSystem::getFileSize(sourcePath, sourceFileSize); if (fs_status != FileSystem::Status::OP_OK) { return fs_status; } fs_status = FileSystem::copyFileData(source, destination, sourceFileSize); return fs_status; } // end copyFile FileSystem::Status FileSystem::appendFile(const char* sourcePath, const char* destPath, bool createMissingDest) { Os::File source; Os::File destination; // If requested, check if destination file exists and exit if does not exist if (not createMissingDest and not FileSystem::exists(destPath)) { return Status::DOESNT_EXIST; } Os::File::Status fileStatus = source.open(sourcePath, Os::File::OPEN_READ); if (fileStatus != Os::File::OP_OK) { return FileSystem::handleFileError(fileStatus); } fileStatus = destination.open(destPath, Os::File::OPEN_APPEND); if (fileStatus != Os::File::OP_OK) { return FileSystem::handleFileError(fileStatus); } FileSystem::Status fs_status = FileSystem::OP_OK; FwSizeType sourceFileSize = 0; fs_status = FileSystem::getFileSize(sourcePath, sourceFileSize); if (fs_status != FileSystem::Status::OP_OK) { return fs_status; } fs_status = FileSystem::copyFileData(source, destination, sourceFileSize); return fs_status; } // end appendFile FileSystem::Status FileSystem::moveFile(const char* source, const char* destination) { Status status = Status::OP_OK; // Try to rename the file status = FileSystem::rename(source, destination); // If rename fails because of cross-device rename, attempt to copy and remove instead if (status == Status::EXDEV_ERROR) { status = FileSystem::copyFile(source, destination); if (status != Status::OP_OK) { return status; } status = FileSystem::removeFile(source); } return status; } FileSystem::Status FileSystem::getFileSize(const char* path, FwSizeType& size) { Os::File file; Os::File::Status status = file.open(path, Os::File::OPEN_READ); if (status != File::Status::OP_OK) { return FileSystem::handleFileError(status); } status = file.size(size); if (status != File::Status::OP_OK) { return FileSystem::handleFileError(status); } return FileSystem::OP_OK; } // ------------------------------------------------------------ // Internal helper functions // ------------------------------------------------------------ FileSystem::Status FileSystem::handleFileError(File::Status fileStatus) { FileSystem::Status status = FileSystem::OTHER_ERROR; switch (fileStatus) { case File::NO_SPACE: status = FileSystem::NO_SPACE; break; case File::NO_PERMISSION: status = FileSystem::NO_PERMISSION; break; case File::DOESNT_EXIST: status = FileSystem::DOESNT_EXIST; break; default: status = FileSystem::OTHER_ERROR; } return status; } // end handleFileError FileSystem::Status FileSystem::handleDirectoryError(Directory::Status dirStatus) { FileSystem::Status status = FileSystem::OTHER_ERROR; switch (dirStatus) { case Directory::DOESNT_EXIST: status = FileSystem::DOESNT_EXIST; break; case Directory::NO_PERMISSION: status = FileSystem::NO_PERMISSION; break; case Directory::ALREADY_EXISTS: status = FileSystem::ALREADY_EXISTS; break; case Directory::NOT_SUPPORTED: status = FileSystem::NOT_SUPPORTED; break; default: status = FileSystem::OTHER_ERROR; } return status; } // end handleFileError FileSystem::Status FileSystem::copyFileData(File& source, File& destination, FwSizeType size) { static_assert(FILE_SYSTEM_FILE_CHUNK_SIZE != 0, "FILE_SYSTEM_FILE_CHUNK_SIZE must be >0"); U8 fileBuffer[FILE_SYSTEM_FILE_CHUNK_SIZE]; File::Status file_status; FwSizeType copiedSize = 0; FwSizeType chunkSize = FILE_SYSTEM_FILE_CHUNK_SIZE; // Loop up to 2 times for each by, bounded to prevent infinite loop const FwSizeType maximum = (size > (std::numeric_limits::max() / 2)) ? std::numeric_limits::max() : size * 2; // Copy the file in chunks - loop until all data is copied FwSizeType i = 0; for (copiedSize = 0; (copiedSize < size) && (i < maximum); copiedSize += chunkSize, i++) { // chunkSize is FILE_SYSTEM_FILE_CHUNK_SIZE unless size-copiedSize is less than that // in which case chunkSize is size-copiedSize, ensuring the last chunk reads the remaining data chunkSize = FW_MIN(FILE_SYSTEM_FILE_CHUNK_SIZE, size - copiedSize); file_status = source.read(fileBuffer, chunkSize, Os::File::WaitType::WAIT); if (file_status != File::OP_OK) { return FileSystem::handleFileError(file_status); } file_status = destination.write(fileBuffer, chunkSize, Os::File::WaitType::WAIT); if (file_status != File::OP_OK) { return FileSystem::handleFileError(file_status); } } return FileSystem::OP_OK; } // end copyFileData } // namespace Os