| // Copyright 2017 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "smbprovider/fake_samba_interface.h" |
| |
| #include <errno.h> |
| |
| #include <algorithm> |
| #include <iomanip> |
| |
| #include <base/bits.h> |
| #include <base/macros.h> |
| #include <base/files/file_path.h> |
| #include <brillo/any.h> |
| |
| #include "smbprovider/constants.h" |
| #include "smbprovider/smbprovider.h" |
| #include "smbprovider/smbprovider_helper.h" |
| |
| namespace smbprovider { |
| namespace { |
| |
| constexpr mode_t kFileMode = 33188; // File entry |
| constexpr mode_t kDirMode = 16877; // Dir entry |
| |
| // Returns if |flag| is set in |flags|. |
| bool IsFlagSet(int32_t flags, int32_t flag) { |
| return (flags & flag) == flag; |
| } |
| |
| // Returns true if |target| is inside of |source|. |
| bool IsTargetInsideSource(const std::string& target, |
| const std::string& source) { |
| base::FilePath target_path(target); |
| base::FilePath source_path(source); |
| |
| return source_path.IsParent(target_path); |
| } |
| |
| size_t CalculateEntrySize(const std::string& entry_name) { |
| return base::bits::Align(sizeof(smbc_dirent) + entry_name.size(), |
| alignof(smbc_dirent)); |
| } |
| |
| bool WriteEntry(const std::string& entry_name, |
| int32_t entry_type, |
| size_t buffer_size, |
| smbc_dirent* dirp) { |
| DCHECK(dirp); |
| size_t entry_size = CalculateEntrySize(entry_name); |
| if (entry_size > buffer_size) { |
| return false; |
| } |
| dirp->smbc_type = entry_type; |
| dirp->dirlen = entry_size; |
| memcpy(dirp->name, entry_name.c_str(), entry_name.size() + 1); |
| return true; |
| } |
| |
| } // namespace |
| |
| FakeSambaInterface::FakeSambaInterface() { |
| // Initialize the |file_info_| struct. |
| memset(&file_info_, 0, sizeof(file_info_)); |
| } |
| |
| int32_t FakeSambaInterface::OpenDirectory(const std::string& directory_path, |
| int32_t* dir_id) { |
| DCHECK(dir_id); |
| *dir_id = -1; |
| |
| int32_t error; |
| if (!GetDirectory(RemoveURLScheme(directory_path), &error)) { |
| return error; |
| } |
| |
| *dir_id = AddOpenDirectory(directory_path); |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::CloseDirectory(int32_t dir_id) { |
| if (!IsDirectoryFDOpen(dir_id)) { |
| LOG(ERROR) << "Cannot close directory [" << dir_id |
| << "]: Bad file descriptor"; |
| return EBADF; |
| } |
| |
| auto open_info_iter = FindOpenFD(dir_id); |
| LOG(INFO) << "Closed directory [" << dir_id << "] " |
| << std::quoted(open_info_iter->second.full_path); |
| open_fds.erase(open_info_iter); |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::GetDirectoryEntry(int32_t dir_id, |
| const smbc_dirent** dirent) { |
| DCHECK(dirent); |
| *dirent = nullptr; |
| if (!IsDirectoryFDOpen(dir_id)) { |
| return EBADF; |
| } |
| |
| OpenInfo& open_info = FindOpenFD(dir_id)->second; |
| FakeDirectory* directory = GetDirectory(RemoveURLScheme(open_info.full_path)); |
| DCHECK(directory); |
| |
| if (open_info.current_index >= directory->entries.size()) { |
| // Reached the end of directory. |
| return 0; |
| } |
| |
| FakeEntry* entry = directory->entries[open_info.current_index].get(); |
| *dirent = PopulateDirEnt(*entry); |
| open_info.current_index++; |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::GetDirectoryEntryWithMetadata( |
| int32_t dir_id, const libsmb_file_info** file_info) { |
| DCHECK(file_info); |
| *file_info = nullptr; |
| if (!IsDirectoryFDOpen(dir_id)) { |
| return EBADF; |
| } |
| |
| OpenInfo& open_info = FindOpenFD(dir_id)->second; |
| FakeDirectory* directory = GetDirectory(RemoveURLScheme(open_info.full_path)); |
| DCHECK(directory); |
| |
| if (open_info.current_index >= directory->entries.size()) { |
| // Reached the end of directory. |
| return 0; |
| } |
| |
| FakeEntry* entry = directory->entries[open_info.current_index].get(); |
| *file_info = PopulateFileInfo(*entry); |
| open_info.current_index++; |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::GetEntryStatus(const std::string& entry_path, |
| struct stat* stat) { |
| DCHECK(stat); |
| |
| FakeEntry* entry = GetEntry(entry_path); |
| if (!entry || !entry->IsValidEntryType()) { |
| return ENOENT; |
| } |
| |
| if (entry->locked) { |
| return EACCES; |
| } |
| |
| stat->st_size = entry->size; |
| stat->st_mode = entry->IsFile() ? kFileMode : kDirMode; |
| stat->st_mtime = entry->date; |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::OpenFile(const std::string& file_path, |
| int32_t flags, |
| int32_t* file_id) { |
| DCHECK(file_id); |
| *file_id = -1; |
| |
| FakeFile* file = GetFile(file_path); |
| if (!file) { |
| return ENOENT; |
| } |
| |
| if (file->locked) { |
| return EACCES; |
| } |
| |
| DCHECK(IsValidOpenFileFlags(flags)); |
| bool readable = IsFlagSet(flags, O_RDONLY) || IsFlagSet(flags, O_RDWR); |
| bool writeable = IsFlagSet(flags, O_WRONLY) || IsFlagSet(flags, O_RDWR); |
| DCHECK(readable || writeable); |
| |
| *file_id = AddOpenFile(file_path, readable, writeable); |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::CloseFile(int32_t file_id) { |
| if (close_file_error_ != 0) { |
| LOG(ERROR) << "Cannot close file [" << file_id << "]: Error " |
| << close_file_error_; |
| return close_file_error_; |
| } |
| |
| DCHECK_GT(file_id, 0); |
| if (!IsFileFDOpen(file_id)) { |
| LOG(ERROR) << "Cannot close file [" << file_id << "]: Bad file descriptor"; |
| return EBADF; |
| } |
| |
| auto open_info_iter = FindOpenFD(file_id); |
| LOG(INFO) << "Closed file [" << file_id << "] " |
| << std::quoted(open_info_iter->second.full_path); |
| open_fds.erase(open_info_iter); |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::ReadFile(int32_t file_id, |
| uint8_t* buffer, |
| size_t buffer_size, |
| size_t* bytes_read) { |
| DCHECK(buffer); |
| DCHECK(bytes_read); |
| if (!IsFileFDOpen(file_id)) { |
| return EBADF; |
| } |
| |
| OpenInfo& open_info = FindOpenFD(file_id)->second; |
| FakeFile* file = GetFile(open_info.full_path); |
| DCHECK(file); |
| DCHECK(file->has_data); |
| DCHECK(file->size == file->data.size()); |
| DCHECK(open_info.current_index <= file->data.size()); |
| |
| // Only read up to the end of the file. |
| *bytes_read = |
| std::min(buffer_size, file->data.size() - open_info.current_index); |
| if (*bytes_read == 0) { |
| // No need for copy or seek when bytes_read is zero. |
| return 0; |
| } |
| |
| // Copy the buffer and update the offset. |
| memcpy(buffer, file->data.data() + open_info.current_index, *bytes_read); |
| open_info.current_index += *bytes_read; |
| DCHECK(open_info.current_index <= file->data.size()); |
| |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::Seek(int32_t file_id, int64_t offset) { |
| if (!IsFileFDOpen(file_id)) { |
| return EBADF; |
| } |
| |
| OpenInfo& open_info = FindOpenFD(file_id)->second; |
| if (offset > GetFile(open_info.full_path)->data.size()) { |
| // Returning an error when offset is outside the bounds of the file. |
| return EINVAL; |
| } |
| |
| open_info.current_index = offset; |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::Unlink(const std::string& file_path) { |
| FakeFile* file = GetFile(file_path); |
| if (!file) { |
| return ENOENT; |
| } |
| |
| if (file->locked) { |
| return EACCES; |
| } |
| |
| RemoveEntryAndResetIndicies(file_path); |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::RemoveDirectory(const std::string& dir_path) { |
| int32_t error; |
| FakeDirectory* directory = GetDirectory(RemoveURLScheme(dir_path), &error); |
| if (!directory) { |
| return error; |
| } |
| if (!directory->entries.empty()) { |
| return ENOTEMPTY; |
| } |
| |
| RemoveEntryAndResetIndicies(dir_path); |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::CreateFile(const std::string& file_path, |
| int32_t* file_id) { |
| if (EntryExists(file_path)) { |
| return EEXIST; |
| } |
| |
| int32_t error; |
| FakeDirectory* parent = GetDirectory(GetDirPath(file_path), &error); |
| if (!parent) { |
| return error; |
| } |
| |
| AddFile(file_path); |
| *file_id = AddOpenFile(file_path, false /* readable */, true /* writeable */); |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::Truncate(int32_t file_id, size_t size) { |
| if (truncate_error_ != 0) { |
| return truncate_error_; |
| } |
| |
| if (!IsFileFDOpen(file_id)) { |
| return EBADFD; |
| } |
| OpenInfo& open_info = FindOpenFD(file_id)->second; |
| FakeFile* file = GetFile(open_info.full_path); |
| DCHECK(file); |
| file->size = size; |
| if (file->has_data) { |
| file->data.resize(size, 0); |
| } |
| // Adjust offset to end of file if the previous offset was larger than size. |
| open_info.current_index = std::min(open_info.current_index, size); |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::WriteFile(int32_t file_id, |
| const uint8_t* buffer, |
| size_t buffer_size) { |
| DCHECK(buffer); |
| OpenInfo& open_info = FindOpenFD(file_id)->second; |
| DCHECK(open_info.smbc_type == SMBC_DIR || open_info.smbc_type == SMBC_FILE); |
| if (open_info.smbc_type != SMBC_FILE) { |
| return EISDIR; |
| } |
| |
| if (!open_info.writeable) { |
| return EINVAL; |
| } |
| |
| FakeFile* file = GetFile(open_info.full_path); |
| DCHECK(file); |
| |
| // Write the data into the file. |
| file->WriteData(open_info.current_index, buffer, buffer_size); |
| |
| // Adjust to the new offset. |
| open_info.current_index += buffer_size; |
| |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::CreateDirectory(const std::string& directory_path) { |
| if (EntryExists(directory_path)) { |
| return EEXIST; |
| } |
| |
| FakeDirectory* parent = GetDirectory(GetDirPath(directory_path)); |
| if (!parent) { |
| return ENOENT; |
| } |
| |
| AddDirectory(directory_path); |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::MoveEntry(const std::string& source_path, |
| const std::string& target_path) { |
| if (IsTargetInsideSource(target_path, source_path)) { |
| // MoveEntry fails if |target_path| is a child of source_path. |
| return EINVAL; |
| } |
| |
| if (!EntryExists(source_path)) { |
| // MoveEntry fails if |source_path| does not exist. |
| return ENOENT; |
| } |
| |
| FakeEntry* src_entry = GetEntry(source_path); |
| if (EntryExists(target_path)) { |
| // If |target_path| exists, check that we can continue with the move. |
| FakeEntry* target_entry = GetEntry(target_path); |
| int32_t result = CheckEntriesValidForMove(src_entry, target_entry); |
| if (result != 0) { |
| return result; |
| } |
| } |
| |
| if (src_entry->IsDir() && src_entry->locked) { |
| // MoveEntry fails to move a locked directory. |
| return EACCES; |
| } |
| |
| return MoveEntryFromSourceToTarget(source_path, target_path); |
| } |
| |
| int32_t FakeSambaInterface::SpliceFile(int32_t source_fd, |
| int32_t target_fd, |
| off_t length, |
| off_t* bytes_written) { |
| DCHECK(bytes_written); |
| |
| if (!IsFDOpen(source_fd) || !IsFDOpen(target_fd)) { |
| return EBADF; |
| } |
| |
| // Verify the source is a readable file. |
| OpenInfo& source_info = FindOpenFD(source_fd)->second; |
| if (source_info.smbc_type != SMBC_FILE) { |
| return EISDIR; |
| } |
| if (!source_info.readable) { |
| return EINVAL; |
| } |
| |
| // Verify the target is a writable file. |
| OpenInfo& target_info = FindOpenFD(target_fd)->second; |
| if (target_info.smbc_type != SMBC_FILE) { |
| return EISDIR; |
| } |
| if (!target_info.writeable) { |
| return EINVAL; |
| } |
| |
| // Get the file structs. |
| FakeFile* source_file = GetFile(source_info.full_path); |
| DCHECK(source_file); |
| FakeFile* target_file = GetFile(target_info.full_path); |
| DCHECK(target_file); |
| |
| // Verify there is at least |length| bytes remaining in source_info. |
| DCHECK_GE(source_file->data.size(), source_info.current_index); |
| int32_t max_bytes_remaining = |
| source_file->data.size() - source_info.current_index; |
| DCHECK_GE(max_bytes_remaining, length); |
| |
| auto begin = source_file->data.begin() + source_info.current_index; |
| auto end = source_file->data.begin() + source_info.current_index + length; |
| std::vector<uint8_t> buffer(begin, end); |
| |
| // Write the data into the file. |
| target_file->WriteData(target_info.current_index, buffer.data(), |
| buffer.size()); |
| |
| // Adjust to the new offsets of the files. |
| source_info.current_index += length; |
| target_info.current_index += length; |
| |
| *bytes_written = length; |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::CopyFile(const std::string& source_path, |
| const std::string& target_path) { |
| if (!EntryExists(source_path)) { |
| // CopyFile fails if |source_path| does not exist. |
| return ENOENT; |
| } |
| |
| // CopyFile should only be called on files. A higher layer should have |
| // already translated a copy of a directory to a series of file copies and |
| // directory creations. |
| FakeFile* source_entry = GetFile(source_path); |
| if (!source_entry) { |
| return EISDIR; |
| } |
| |
| if (EntryExists(target_path)) { |
| // |target_path| is the full path to the intended target file and must |
| // not exist. |target_path| also cannot be a directory. |
| return EEXIST; |
| } |
| |
| // The parent of |target_path| must exist. |
| int32_t error; |
| FakeDirectory* target_parent = GetDirectory(GetDirPath(target_path), &error); |
| if (!target_parent) { |
| return error; |
| } |
| |
| std::unique_ptr<FakeFile> new_target_file; |
| if (source_entry->has_data) { |
| new_target_file = std::make_unique<FakeFile>( |
| target_path, source_entry->date, source_entry->data); |
| |
| } else { |
| new_target_file = std::make_unique<FakeFile>( |
| target_path, source_entry->size, source_entry->date, false /*locked */); |
| } |
| |
| target_parent->entries.push_back(std::move(new_target_file)); |
| |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::CheckEntriesValidForMove( |
| FakeEntry* src_entry, FakeEntry* target_entry) const { |
| DCHECK(src_entry); |
| DCHECK(target_entry); |
| |
| if (target_entry->IsFile()) { |
| if (src_entry->IsDir()) { |
| return ENOTDIR; |
| } |
| return EEXIST; |
| } else { |
| if (src_entry->IsFile()) { |
| return EISDIR; |
| } |
| DCHECK(target_entry->IsDir()); |
| FakeDirectory* target_dir = static_cast<FakeDirectory*>(target_entry); |
| if (!target_dir->IsEmpty()) { |
| return EEXIST; |
| } |
| return 0; |
| } |
| } |
| |
| int32_t FakeSambaInterface::MoveEntryFromSourceToTarget( |
| const std::string& source_path, const std::string& target_path) { |
| FakeDirectory* source_dir; |
| FakeDirectory* target_dir; |
| int32_t result = GetSourceAndTargetParentDirectories( |
| source_path, target_path, &source_dir, &target_dir); |
| if (result != 0) { |
| return result; |
| } |
| |
| FakeSambaInterface::FakeDirectory::EntriesIterator source_it = |
| source_dir->GetEntryIt(GetFileName(source_path)); |
| (*source_it)->name = GetFileName(target_path); |
| |
| if (source_dir != target_dir) { |
| // Must perform move in addition to rename. |
| target_dir->entries.push_back(std::move(*source_it)); |
| source_dir->entries.erase(source_it); |
| } |
| |
| return 0; |
| } |
| |
| int32_t FakeSambaInterface::GetSourceAndTargetParentDirectories( |
| const std::string& source_path, |
| const std::string& target_path, |
| FakeDirectory** source_parent, |
| FakeDirectory** target_parent) const { |
| DCHECK(source_parent); |
| DCHECK(target_parent); |
| |
| int32_t error; |
| *source_parent = GetDirectory(GetDirPath(source_path), &error); |
| if (!(*source_parent)) { |
| return error; |
| } |
| |
| *target_parent = GetDirectory(GetDirPath(target_path), &error); |
| if (!(*target_parent)) { |
| return error; |
| } |
| |
| // FakeSambaInterface does not support moving open entries/parents. |
| DCHECK(!IsOpen(GetDirPath(source_path))); |
| DCHECK(!IsOpen(GetDirPath(target_path))); |
| DCHECK(!IsOpen(source_path)); |
| DCHECK(!IsOpen(target_path)); |
| |
| return 0; |
| } |
| |
| bool FakeSambaInterface::FakeDirectory::IsEmpty() const { |
| return entries.empty(); |
| } |
| |
| FakeSambaInterface::FakeEntry* FakeSambaInterface::FakeDirectory::FindEntry( |
| const std::string& name) { |
| for (auto&& entry : entries) { |
| if (entry->name == name) { |
| return entry.get(); |
| } |
| } |
| return nullptr; |
| } |
| |
| FakeSambaInterface::FakeDirectory::EntriesIterator |
| FakeSambaInterface::FakeDirectory::GetEntryIt(const std::string& name) { |
| return std::find_if( |
| entries.begin(), entries.end(), |
| [&name](const std::unique_ptr<FakeEntry>& p) { return p->name == name; }); |
| } |
| |
| int32_t FakeSambaInterface::FakeDirectory::RemoveEntry( |
| const std::string& name) { |
| const EntriesIterator it = GetEntryIt(name); |
| if (it == entries.end()) { |
| return -1; |
| } |
| |
| DCHECK((*it)->IsFileOrEmptyDir()); |
| |
| const size_t i = it - entries.begin(); |
| entries.erase(it); |
| return i; |
| } |
| |
| void FakeSambaInterface::FakeFile::WriteData(size_t offset, |
| const uint8_t* buffer, |
| size_t buffer_size) { |
| // Ensure that the current size of the file greater than or equal to the |
| // offset. |
| DCHECK(this->data.size() >= offset); |
| |
| // Resize the data to the new length if necessary. |
| const size_t new_length = std::max(offset + buffer_size, this->data.size()); |
| this->data.resize(new_length, 0); |
| this->size = new_length; |
| |
| // Copy the data from buffer into the vector starting from the offset. |
| memcpy(this->data.data() + offset, buffer, buffer_size); |
| |
| this->has_data = true; |
| } |
| |
| FakeSambaInterface::FakeEntry::FakeEntry(const std::string& full_path, |
| uint32_t smbc_type, |
| size_t size, |
| time_t date, |
| bool locked) |
| : name(GetFileName(full_path)), |
| smbc_type(smbc_type), |
| size(size), |
| date(date), |
| locked(locked) {} |
| |
| void FakeSambaInterface::AddDirectory(const std::string& path) { |
| AddDirectory(path, false /* locked */, SMBC_DIR); |
| } |
| |
| void FakeSambaInterface::AddServer(const std::string& server_url) { |
| AddDirectory(server_url, false /* locked */, SMBC_SERVER); |
| } |
| |
| void FakeSambaInterface::AddShare(const std::string& path) { |
| AddDirectory(path, false /* locked */, SMBC_FILE_SHARE); |
| } |
| |
| void FakeSambaInterface::AddDirectory(const std::string& path, |
| bool locked, |
| uint32_t smbc_type) { |
| AddDirectory(path, locked, smbc_type, 0 /* date */); |
| } |
| |
| void FakeSambaInterface::AddDirectory(const std::string& path, |
| bool locked, |
| uint32_t smbc_type, |
| time_t date) { |
| // Make sure that no entry exists in that path. |
| DCHECK(!EntryExists(path)); |
| DCHECK(!IsOpen(path)); |
| FakeDirectory* directory = GetDirectory(GetDirPath(path)); |
| DCHECK(directory); |
| directory->entries.emplace_back( |
| std::make_unique<FakeDirectory>(path, locked, smbc_type, date)); |
| } |
| |
| void FakeSambaInterface::AddLockedDirectory(const std::string& path) { |
| AddDirectory(path, true, SMBC_DIR); |
| } |
| |
| void FakeSambaInterface::AddFile(const std::string& path) { |
| AddFile(path, 0 /* size */); |
| } |
| |
| void FakeSambaInterface::AddFile(const std::string& path, size_t size) { |
| AddFile(path, size, 0 /* date */); |
| } |
| |
| void FakeSambaInterface::AddFile(const std::string& path, |
| size_t size, |
| time_t date) { |
| AddFile(path, size, date, false /* locked */); |
| } |
| |
| void FakeSambaInterface::AddFile(const std::string& path, |
| size_t size, |
| time_t date, |
| bool locked) { |
| // Make sure that no entry exists in that path. |
| DCHECK(!EntryExists(path)); |
| DCHECK(!IsOpen(path)); |
| FakeDirectory* directory = GetDirectory(GetDirPath(path)); |
| DCHECK(directory); |
| directory->entries.emplace_back( |
| std::make_unique<FakeFile>(path, size, date, locked)); |
| } |
| |
| void FakeSambaInterface::AddFile(const std::string& path, |
| time_t date, |
| std::vector<uint8_t> file_data) { |
| // Make sure that no entry exists in that path. |
| DCHECK(!EntryExists(path)); |
| DCHECK(!IsOpen(path)); |
| FakeDirectory* directory = GetDirectory(GetDirPath(path)); |
| DCHECK(directory); |
| directory->entries.emplace_back( |
| std::make_unique<FakeFile>(path, date, std::move(file_data))); |
| } |
| |
| void FakeSambaInterface::AddLockedFile(const std::string& path) { |
| AddFile(path, 0 /* size */, 0 /* date */, true /* locked */); |
| } |
| |
| void FakeSambaInterface::AddEntry(const std::string& path, uint32_t smbc_type) { |
| // Make sure that no entry exists in that path. |
| DCHECK(!EntryExists(path)); |
| DCHECK(!IsOpen(path)); |
| FakeDirectory* directory = GetDirectory(GetDirPath(path)); |
| DCHECK(directory); |
| directory->entries.emplace_back(std::make_unique<FakeEntry>( |
| path, smbc_type, 0 /* size */, 0 /* date */, false /* locked */)); |
| } |
| |
| FakeSambaInterface::FakeDirectory* FakeSambaInterface::GetDirectory( |
| const std::string& full_path) const { |
| int32_t error; |
| return GetDirectory(full_path, &error); |
| } |
| |
| FakeSambaInterface::FakeDirectory* FakeSambaInterface::GetDirectory( |
| const std::string& full_path, int32_t* error) const { |
| if (get_directory_error_ != 0) { |
| *error = get_directory_error_; |
| return nullptr; |
| } |
| |
| PathParts split_path = SplitPath(full_path); |
| FakeDirectory* current = &root; |
| |
| // i = 0 represents the root directory which we already have. |
| DCHECK_EQ("/", split_path[0]); |
| for (int i = 1; i < split_path.size(); ++i) { |
| FakeEntry* entry = current->FindEntry(split_path[i]); |
| if (!entry) { |
| *error = ENOENT; |
| return nullptr; |
| } |
| if (!entry->IsDir()) { |
| *error = ENOTDIR; |
| return nullptr; |
| } |
| if (entry->locked) { |
| *error = EACCES; |
| return nullptr; |
| } |
| current = static_cast<FakeDirectory*>(entry); |
| } |
| return current; |
| } |
| |
| FakeSambaInterface::FakeFile* FakeSambaInterface::GetFile( |
| const std::string& file_path) const { |
| FakeEntry* entry = GetEntry(file_path); |
| if (!entry || !entry->IsFile()) { |
| return nullptr; |
| } |
| return static_cast<FakeFile*>(entry); |
| } |
| |
| FakeSambaInterface::FakeEntry* FakeSambaInterface::GetEntry( |
| const std::string& entry_path) const { |
| FakeDirectory* directory = GetDirectory(GetDirPath(entry_path)); |
| if (!directory) { |
| return nullptr; |
| } |
| return directory->FindEntry(GetFileName(entry_path)); |
| } |
| |
| int32_t FakeSambaInterface::AddOpenDirectory(const std::string& path) { |
| DCHECK(!IsFDOpen(next_fd)); |
| open_fds.emplace(next_fd, OpenInfo(path, SMBC_DIR)); |
| LOG(INFO) << "Opened directory [" << next_fd << "] " << std::quoted(path); |
| return next_fd++; |
| } |
| |
| int32_t FakeSambaInterface::AddOpenFile(const std::string& path, |
| bool readable, |
| bool writeable) { |
| DCHECK(!IsFDOpen(next_fd)); |
| open_fds.emplace(next_fd, OpenInfo(path, SMBC_FILE, readable, writeable)); |
| LOG(INFO) << "Opened file [" << next_fd << "] " << std::quoted(path); |
| return next_fd++; |
| } |
| |
| bool FakeSambaInterface::IsOpen(const std::string& full_path) const { |
| for (auto const& open_it : open_fds) { |
| if (open_it.second.full_path == full_path) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool FakeSambaInterface::HasOpenEntries() const { |
| return !open_fds.empty(); |
| } |
| |
| bool FakeSambaInterface::IsFileFDOpen(uint32_t fd) const { |
| auto open_iter = open_fds.find(fd); |
| return open_iter != open_fds.end() && |
| open_iter->second.smbc_type == SMBC_FILE; |
| } |
| |
| bool FakeSambaInterface::IsDirectoryFDOpen(uint32_t fd) const { |
| auto open_iter = open_fds.find(fd); |
| return open_iter != open_fds.end() && open_iter->second.smbc_type == SMBC_DIR; |
| } |
| |
| bool FakeSambaInterface::IsFDOpen(uint32_t fd) const { |
| return open_fds.count(fd) != 0; |
| } |
| |
| size_t FakeSambaInterface::GetFileOffset(int32_t fd) const { |
| const OpenInfo& open_info = FindOpenFD(fd)->second; |
| DCHECK_EQ(open_info.smbc_type, SMBC_FILE); |
| return open_info.current_index; |
| } |
| |
| size_t FakeSambaInterface::GetFileSize(const std::string& path) const { |
| FakeFile* file = GetFile(path); |
| DCHECK(file); |
| return file->size; |
| } |
| |
| FakeSambaInterface::OpenEntriesIterator FakeSambaInterface::FindOpenFD( |
| uint32_t fd) { |
| return open_fds.find(fd); |
| } |
| |
| FakeSambaInterface::OpenEntriesConstIterator FakeSambaInterface::FindOpenFD( |
| uint32_t fd) const { |
| return open_fds.find(fd); |
| } |
| |
| bool FakeSambaInterface::FakeEntry::IsValidEntryType() const { |
| return IsFile() || IsDir(); |
| } |
| |
| bool FakeSambaInterface::FakeEntry::IsFile() const { |
| return smbc_type == SMBC_FILE; |
| } |
| |
| bool FakeSambaInterface::FakeEntry::IsDir() const { |
| return smbc_type == SMBC_DIR || smbc_type == SMBC_SERVER || |
| smbc_type == SMBC_FILE_SHARE; |
| } |
| |
| bool FakeSambaInterface::FakeEntry::IsFileOrEmptyDir() const { |
| DCHECK(IsValidEntryType()); |
| return IsFile() || static_cast<const FakeDirectory*>(this)->IsEmpty(); |
| } |
| |
| bool FakeSambaInterface::HasReadSet(int32_t fd) const { |
| DCHECK(IsFDOpen(fd)); |
| return open_fds.at(fd).readable; |
| } |
| |
| bool FakeSambaInterface::HasWriteSet(int32_t fd) const { |
| DCHECK(IsFDOpen(fd)); |
| return open_fds.at(fd).writeable; |
| } |
| |
| bool FakeSambaInterface::EntryExists(const std::string& path) const { |
| return GetEntry(path); |
| } |
| |
| void FakeSambaInterface::SetCloseFileError(int32_t error) { |
| close_file_error_ = error; |
| } |
| |
| void FakeSambaInterface::SetTruncateError(int32_t error) { |
| truncate_error_ = error; |
| } |
| |
| void FakeSambaInterface::SetGetDirectoryError(int32_t error) { |
| get_directory_error_ = error; |
| } |
| |
| void FakeSambaInterface::SetCurrentEntry(int32_t dir_id, size_t index) { |
| DCHECK(IsFDOpen(dir_id)); |
| OpenInfo& info = FindOpenFD(dir_id)->second; |
| |
| FakeDirectory* directory = GetDirectory(RemoveURLScheme(info.full_path)); |
| DCHECK(directory); |
| |
| DCHECK_LE(index, directory->entries.size()); |
| info.current_index = index; |
| } |
| |
| std::string FakeSambaInterface::GetCurrentEntry(int32_t dir_id) { |
| DCHECK(IsFDOpen(dir_id)); |
| OpenInfo& info = FindOpenFD(dir_id)->second; |
| const size_t index = info.current_index; |
| |
| FakeDirectory* directory = GetDirectory(RemoveURLScheme(info.full_path)); |
| DCHECK(directory); |
| |
| if (index == directory->entries.size()) { |
| return ""; |
| } |
| |
| return directory->entries[index]->name; |
| } |
| |
| bool FakeSambaInterface::IsFileDataEqual( |
| const std::string& path, const std::vector<uint8_t>& expected) const { |
| FakeFile* file = GetFile(path); |
| if (!file || !file->has_data) { |
| return false; |
| } |
| |
| if (file->size != expected.size()) { |
| return false; |
| } |
| |
| return expected == file->data; |
| } |
| |
| void FakeSambaInterface::RewindOpenInfoIndicesIfNeccessary( |
| const std::string& dir_path, size_t deleted_index) { |
| for (auto& it : open_fds) { |
| OpenInfo& info = it.second; |
| if (info.IsForDir(dir_path)) { |
| if (deleted_index < info.current_index) { |
| // By removing an entry from the directory that has already been read, |
| // current_index will have been inadvertantly advanced. |
| --info.current_index; |
| } |
| } |
| } |
| } |
| |
| bool FakeSambaInterface::OpenInfo::IsForDir(const std::string& dir_path) { |
| return RemoveURLScheme(full_path) == dir_path; |
| } |
| |
| void FakeSambaInterface::RemoveEntryAndResetIndicies( |
| const std::string& full_path) { |
| FakeDirectory* parent = GetDirectory(GetDirPath(full_path)); |
| const int32_t deleted_index = parent->RemoveEntry(GetFileName(full_path)); |
| DCHECK_GE(deleted_index, 0); |
| RewindOpenInfoIndicesIfNeccessary(GetDirPath(full_path), deleted_index); |
| } |
| |
| const libsmb_file_info* FakeSambaInterface::PopulateFileInfo( |
| const FakeEntry& entry) { |
| file_info_.size = entry.size; |
| file_info_.mtime_ts.tv_sec = entry.date; |
| file_info_.attrs = |
| (entry.smbc_type == SMBC_FILE) ? 0 : kFileAttributeDirectory; |
| |
| // The libsmb_file_info struct has a non-const char* so even through only |
| // a const pointer is ever returned, the member variable backing it is |
| // not const. |
| file_info_.name = const_cast<char*>(entry.name.c_str()); |
| |
| return &file_info_; |
| } |
| |
| const smbc_dirent* FakeSambaInterface::PopulateDirEnt(const FakeEntry& entry) { |
| smbc_dirent* const dirent = reinterpret_cast<smbc_dirent*>(&dirent_buf_); |
| const bool result = |
| WriteEntry(entry.name, entry.smbc_type, kDirEntBufSize, dirent); |
| DCHECK(result); |
| |
| return dirent; |
| } |
| |
| SambaInterface::SambaInterfaceId FakeSambaInterface::GetSambaInterfaceId() { |
| // GetSambaInterfaceId() should never be called directly on |
| // FakeSambaInterface. All tests should call FakeSambaProxy which provides a |
| // unique SambaInterfaceId for each instance. |
| NOTREACHED(); |
| return 0; |
| } |
| |
| SambaInterface::WeakPtr FakeSambaInterface::AsWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| } // namespace smbprovider |