#include "seafile/fs/seafile_file_system.hpp" #include "seafile/fs/file_context.hpp" #include "util/string_util.hpp" #include #include namespace seafile::fs { namespace { constexpr int64_t UNIX_EPOCH_AS_FILETIME = 116444736000000000LL; constexpr int64_t HUNDREDS_OF_NANOSECONDS_PER_SECOND = 10000000LL; int64_t UnixTimeToFileTime(const int64_t unix_time) { return UNIX_EPOCH_AS_FILETIME + unix_time * HUNDREDS_OF_NANOSECONDS_PER_SECOND; } std::string normalize_path(const std::string& path) { std::string result = path; std::ranges::replace(result, '\\', '/'); return result; } int64_t NowFileTime() { FILETIME ft; GetSystemTimeAsFileTime(&ft); return (static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; } void SetFileInfo(FSP_FSCTL_FILE_INFO* FileInfo, const uint32_t attributes, const uint64_t size, const int64_t mtime) { FileInfo->FileAttributes = attributes; FileInfo->AllocationSize = size; FileInfo->FileSize = size; const auto ft = mtime ? UnixTimeToFileTime(mtime) : NowFileTime(); FileInfo->CreationTime = ft; FileInfo->LastAccessTime = ft; FileInfo->LastWriteTime = ft; FileInfo->ChangeTime = ft; FileInfo->IndexNumber = 0; } } tl::expected SeafileFileSystem::initialize(const std::string& username, const std::string& password) { const auto authToken = api->authToken(username, password); if (authToken.has_value()) { this->token = authToken.value().token; if (const auto repo = api->defaultRepo(this->token, this->token_type); repo.has_value()) { this->repo_id = repo.value().repo_id.value_or(""); return {}; } spdlog::error("no default repo found"); return tl::make_unexpected(SeafileException("No default repo found")); } spdlog::error(std::format("user login failed:{}", authToken.error().what())); return tl::make_unexpected(authToken.error()); } void SeafileFileSystem::Close(PVOID FileContext) { delete static_cast(FileContext); } NTSTATUS SeafileFileSystem::GetSecurityByName(PWSTR FileName, PUINT32 PFileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T* PSecurityDescriptorSize) { if (PFileAttributes) { if (wcscmp(FileName, L"\\") == 0) { *PFileAttributes = FILE_ATTRIBUTE_DIRECTORY; } else { *PFileAttributes = FILE_ATTRIBUTE_NORMAL; } } if (PSecurityDescriptorSize) *PSecurityDescriptorSize = 0; return STATUS_SUCCESS; } NTSTATUS SeafileFileSystem::Overwrite(PVOID FileContext, UINT32 FileAttributes, BOOLEAN ReplaceFileAttributes, UINT64 AllocationSize, FSP_FSCTL_FILE_INFO* FileInfo) { return STATUS_MEDIA_WRITE_PROTECTED; } NTSTATUS SeafileFileSystem::GetVolumeInfo(FSP_FSCTL_VOLUME_INFO* VolumeInfo) const { if (const auto userInfo = api->accountInfo(this->token, this->token_type); userInfo.has_value()) { const auto& info = userInfo.value(); VolumeInfo->FreeSize = info.total - info.usage; VolumeInfo->TotalSize = info.total; if (!this->volume_label.empty()) { wcscpy_s(VolumeInfo->VolumeLabel, this->volume_label.data()); VolumeInfo->VolumeLabelLength = static_cast((wcslen(VolumeInfo->VolumeLabel) * sizeof(WCHAR))); } spdlog::info(std::format("volume total size: {}, free size: {}", VolumeInfo->TotalSize, VolumeInfo->FreeSize)); return STATUS_SUCCESS; } spdlog::error("failed to get volume info"); return STATUS_END_OF_MEDIA; } void SeafileFileSystem::Cleanup(PVOID FileContext, PWSTR FileName, ULONG Flags) { } NTSTATUS SeafileFileSystem::Open(PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess, PVOID* PFileContext, FSP_FSCTL_FILE_INFO* FileInfo) { const auto path = normalize_path(shell::util::StringUtil::utf16le_to_utf8(FileName)); if (repo_id.empty()) { spdlog::error(std::format("can't open file: {}, repo id is empty", path)); return STATUS_OBJECT_NAME_NOT_FOUND; } if (path == "/") { auto repo = api->repoInfo(repo_id, token, token_type); if (!repo.has_value()) return STATUS_OBJECT_NAME_NOT_FOUND; const auto ctx = new SeaFileContext{repo->id, path, SeaFileContext::Type::DIRECTORY, 0, ""}; *PFileContext = ctx; SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, repo->size, repo->mtime); spdlog::info(std::format("open directory {} success", path)); return STATUS_SUCCESS; } if (const auto dirInfo = api->dirDetail(repo_id, path, token, token_type); dirInfo.has_value()) { const auto& info = dirInfo.value(); const auto ctx = new SeaFileContext{info.repo_id, info.path, SeaFileContext::Type::DIRECTORY, 0, ""}; *PFileContext = ctx; SetFileInfo(FileInfo,FILE_ATTRIBUTE_DIRECTORY, 0, 0); spdlog::info(std::format("open directory {} success", path)); return STATUS_SUCCESS; } if (const auto fileInfo = api->fileDetail(repo_id, path, token, token_type); fileInfo.has_value()) { const auto& info = fileInfo.value(); const auto ctx = new SeaFileContext(repo_id, path, SeaFileContext::Type::FILE, info.size, ""); *PFileContext = ctx; SetFileInfo(FileInfo,FILE_ATTRIBUTE_ARCHIVE, info.size, 0); spdlog::info(std::format("open file {} success", path)); return STATUS_SUCCESS; } return STATUS_OBJECT_NAME_NOT_FOUND; } NTSTATUS SeafileFileSystem::Read(PVOID FileContext, PVOID Buffer, UINT64 Offset, ULONG Length, PULONG PBytesTransferred) { const auto fc = static_cast(FileContext); auto result = api->downloadFile( fc->repo_id, fc->path, Offset, Offset + Length - 1, token, token_type); if (result.has_value()) { const auto& data = result.value(); const auto copy_len = (std::min)(data.size(), static_cast(Length)); memcpy(Buffer, data.data(), copy_len); *PBytesTransferred = static_cast(copy_len); spdlog::info(std::format("read file {} bytes offset {} length {} success", fc->path, Offset, Length)); return STATUS_SUCCESS; } return STATUS_UNSUCCESSFUL; } NTSTATUS SeafileFileSystem::ReadDirectory(PVOID FileContext, PWSTR Pattern, PWSTR Marker, PVOID Buffer, ULONG BufferLength, PULONG PBytesTransferred) { const auto fc = static_cast(FileContext); *PBytesTransferred = 0; if (auto entries_result = api->repoDir(fc->repo_id, fc->path, token, token_type); entries_result.has_value()) { const auto& entries = entries_result.value(); size_t start = 0; if (Marker) { const auto marker = shell::util::StringUtil::utf16le_to_utf8(Marker); for (auto i = 0; i < entries.size(); ++i) { if (entries[i].name == marker) { start = i + 1; break; } } } for (auto i = start; i < entries.size(); ++i) { const auto& entry = entries[i]; const auto entry_name = shell::util::StringUtil::utf8_to_utf16le(entry.name); const auto size = sizeof(FSP_FSCTL_DIR_INFO) + ((entry_name.size() + 1) * sizeof(WCHAR)); const auto dir_info = static_cast(std::malloc(size)); std::memset(dir_info, 0, size); dir_info->Size = size; dir_info->FileInfo.FileSize = entry.size.value_or(0); if (entry.type == "file") { dir_info->FileInfo.FileAttributes = FILE_ATTRIBUTE_ARCHIVE; } else { dir_info->FileInfo.FileAttributes = FILE_ATTRIBUTE_DIRECTORY; } wcscpy_s(dir_info->FileNameBuf, entry_name.size() + 1, entry_name.data()); if (*PBytesTransferred + size < BufferLength) { if (!FspFileSystemAddDirInfo(dir_info, Buffer, BufferLength, PBytesTransferred)) { break; } } else { break; } } return STATUS_SUCCESS; } else { spdlog::error(std::format("read directory {} failed: {}", fc->path, entries_result.error().what())); } return STATUS_UNSUCCESSFUL; } NTSTATUS SeafileFileSystem::GetFileInfo(HANDLE Handle, FSP_FSCTL_FILE_INFO* FileInfo) { auto fc = static_cast(Handle); if (fc->repo_id.empty() || fc->path == "/" || fc->path.back() == '/') { if (fc->repo_id.empty()) { SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, 0, 0); return STATUS_SUCCESS; } auto repo = api->repoInfo(fc->repo_id, token, token_type); if (!repo.has_value()) return STATUS_UNSUCCESSFUL; SetFileInfo(FileInfo, FILE_ATTRIBUTE_DIRECTORY, repo->size, repo->mtime); return STATUS_SUCCESS; } auto fdetail = api->fileDetail(fc->repo_id, fc->path, token, token_type); if (!fdetail.has_value()) return STATUS_UNSUCCESSFUL; SetFileInfo(FileInfo, FILE_ATTRIBUTE_NORMAL, fdetail->size, fdetail->mtime); spdlog::info(std::format("get file {} info success", fc->path)); return STATUS_SUCCESS; } NTSTATUS SeafileFileSystem::Create(PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess, UINT32 FileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor, UINT64 AllocationSize, PVOID* PFileContext, FSP_FSCTL_FILE_INFO* FileInfo) { return Open(FileName, CreateOptions, GrantedAccess, PFileContext, FileInfo); } NTSTATUS SeafileFileSystem::GetDirInfoByName(PVOID FileContext, PWSTR FileName, FSP_FSCTL_DIR_INFO* DirInfo) { auto fc = static_cast(FileContext); std::wstring name(FileName); if (name == L"." || name == L"..") { memset(DirInfo, 0, sizeof(FSP_FSCTL_DIR_INFO)); DirInfo->Size = sizeof(FSP_FSCTL_DIR_INFO) + (wcslen(FileName) + 1) * sizeof(WCHAR); SetFileInfo(&DirInfo->FileInfo, FILE_ATTRIBUTE_DIRECTORY, 0, 0); wcscpy_s(DirInfo->FileNameBuf, wcslen(FileName) + 1, FileName); return STATUS_SUCCESS; } std::string utf8_name; for (const auto c : name) utf8_name += static_cast(c); if (fc->repo_id.empty()) { return STATUS_OBJECT_NAME_NOT_FOUND; } auto entries = api->repoDir(fc->repo_id, fc->path, token, token_type); if (!entries.has_value()) return STATUS_OBJECT_NAME_NOT_FOUND; for (const auto& entry : entries.value()) { if (entry.name != utf8_name) continue; memset(DirInfo, 0, sizeof(FSP_FSCTL_DIR_INFO)); const auto name_bytes = (name.size() + 1) * sizeof(WCHAR); DirInfo->Size = offsetof(FSP_FSCTL_DIR_INFO, FileNameBuf) + name_bytes; const auto attrs = (entry.type == "dir") ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL; SetFileInfo(&DirInfo->FileInfo, attrs, entry.size.value_or(0), entry.mtime); wcscpy_s(DirInfo->FileNameBuf, name.size() + 1, FileName); return STATUS_SUCCESS; } return STATUS_OBJECT_NAME_NOT_FOUND; } NTSTATUS SeafileFileSystem::SetVolumeLabel_(PWSTR VolumeLabel, FSP_FSCTL_VOLUME_INFO* VolumeInfo) { return STATUS_ACCESS_DENIED; } }