| // Copyright 2017 The Chromium 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 "chrome/browser/chromeos/smb_client/smb_file_system.h" |
| |
| #include <algorithm> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "chrome/browser/chromeos/file_system_provider/service.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/smb_provider_client.h" |
| #include "net/base/io_buffer.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| // This is a bogus data URI. |
| // The Files app will attempt to download a whole image to create a thumbnail |
| // any time you visit a folder. A bug (crbug.com/548050) tracks not doing that |
| // for NETWORK providers. This work around is to supply an icon but make it |
| // bogus so it falls back to the generic icon. |
| constexpr char kUnknownImageDataUri[] = "data:image/png;base64,X"; |
| |
| // Initial number of entries to send during read directory. This number is |
| // smaller than kReadDirectoryMaxBatchSize since we want the initial page to |
| // load as quickly as possible. |
| constexpr uint32_t kReadDirectoryInitialBatchSize = 64; |
| |
| // Maximum number of entries to send at a time for read directory, |
| constexpr uint32_t kReadDirectoryMaxBatchSize = 2048; |
| |
| using file_system_provider::ProvidedFileSystemInterface; |
| |
| bool RequestedIsDirectory( |
| ProvidedFileSystemInterface::MetadataFieldMask fields) { |
| return fields & ProvidedFileSystemInterface::MetadataField:: |
| METADATA_FIELD_IS_DIRECTORY; |
| } |
| |
| bool RequestedName(ProvidedFileSystemInterface::MetadataFieldMask fields) { |
| return fields & |
| ProvidedFileSystemInterface::MetadataField::METADATA_FIELD_NAME; |
| } |
| |
| bool RequestedSize(ProvidedFileSystemInterface::MetadataFieldMask fields) { |
| return fields & |
| ProvidedFileSystemInterface::MetadataField::METADATA_FIELD_SIZE; |
| } |
| |
| bool RequestedModificationTime( |
| ProvidedFileSystemInterface::MetadataFieldMask fields) { |
| return fields & ProvidedFileSystemInterface::MetadataField:: |
| METADATA_FIELD_MODIFICATION_TIME; |
| } |
| |
| bool RequestedThumbnail(ProvidedFileSystemInterface::MetadataFieldMask fields) { |
| return fields & |
| ProvidedFileSystemInterface::MetadataField::METADATA_FIELD_THUMBNAIL; |
| } |
| |
| } // namespace |
| |
| namespace smb_client { |
| |
| namespace { |
| |
| storage::DirectoryEntry::DirectoryEntryType MapEntryType(bool is_directory) { |
| return is_directory ? storage::DirectoryEntry::DIRECTORY |
| : storage::DirectoryEntry::FILE; |
| } |
| |
| } // namespace |
| |
| using file_system_provider::AbortCallback; |
| |
| SmbFileSystem::SmbFileSystem( |
| const file_system_provider::ProvidedFileSystemInfo& file_system_info, |
| UnmountCallback unmount_callback) |
| : file_system_info_(file_system_info), |
| unmount_callback_(std::move(unmount_callback)) {} |
| |
| SmbFileSystem::~SmbFileSystem() {} |
| |
| // static |
| base::File::Error SmbFileSystem::TranslateError(smbprovider::ErrorType error) { |
| DCHECK_NE(smbprovider::ERROR_NONE, error); |
| |
| switch (error) { |
| case smbprovider::ERROR_OK: |
| return base::File::FILE_OK; |
| case smbprovider::ERROR_FAILED: |
| return base::File::FILE_ERROR_FAILED; |
| case smbprovider::ERROR_IN_USE: |
| return base::File::FILE_ERROR_IN_USE; |
| case smbprovider::ERROR_EXISTS: |
| return base::File::FILE_ERROR_EXISTS; |
| case smbprovider::ERROR_NOT_FOUND: |
| return base::File::FILE_ERROR_NOT_FOUND; |
| case smbprovider::ERROR_ACCESS_DENIED: |
| return base::File::FILE_ERROR_ACCESS_DENIED; |
| case smbprovider::ERROR_TOO_MANY_OPENED: |
| return base::File::FILE_ERROR_TOO_MANY_OPENED; |
| case smbprovider::ERROR_NO_MEMORY: |
| return base::File::FILE_ERROR_NO_MEMORY; |
| case smbprovider::ERROR_NO_SPACE: |
| return base::File::FILE_ERROR_NO_SPACE; |
| case smbprovider::ERROR_NOT_A_DIRECTORY: |
| return base::File::FILE_ERROR_NOT_A_DIRECTORY; |
| case smbprovider::ERROR_INVALID_OPERATION: |
| return base::File::FILE_ERROR_INVALID_OPERATION; |
| case smbprovider::ERROR_SECURITY: |
| return base::File::FILE_ERROR_SECURITY; |
| case smbprovider::ERROR_ABORT: |
| return base::File::FILE_ERROR_ABORT; |
| case smbprovider::ERROR_NOT_A_FILE: |
| return base::File::FILE_ERROR_NOT_A_FILE; |
| case smbprovider::ERROR_NOT_EMPTY: |
| return base::File::FILE_ERROR_NOT_EMPTY; |
| case smbprovider::ERROR_INVALID_URL: |
| return base::File::FILE_ERROR_INVALID_URL; |
| case smbprovider::ERROR_IO: |
| return base::File::FILE_ERROR_IO; |
| case smbprovider::ERROR_DBUS_PARSE_FAILED: |
| // DBUS_PARSE_FAILED maps to generic ERROR_FAILED |
| LOG(ERROR) << "DBUS PARSE FAILED"; |
| return base::File::FILE_ERROR_FAILED; |
| default: |
| break; |
| } |
| |
| NOTREACHED(); |
| return base::File::FILE_ERROR_FAILED; |
| } |
| |
| int32_t SmbFileSystem::GetMountId() const { |
| int32_t mount_id; |
| bool result = |
| base::StringToInt(file_system_info_.file_system_id(), &mount_id); |
| DCHECK(result); |
| return mount_id; |
| } |
| |
| SmbProviderClient* SmbFileSystem::GetSmbProviderClient() const { |
| return chromeos::DBusThreadManager::Get()->GetSmbProviderClient(); |
| } |
| |
| void SmbFileSystem::Abort() { |
| // TODO(zentaro): To implement Abort() fully will require storing a |
| // request id unique to each method call and also passing it to the daemon. |
| // However none of current operations on the daemon are cancelable, so |
| // until there are operations that can actually be cancelled this will |
| // be a no-op. |
| } |
| |
| AbortCallback SmbFileSystem::CreateAbortCallback() { |
| return base::BindRepeating(&SmbFileSystem::Abort, AsWeakPtr()); |
| } |
| |
| AbortCallback SmbFileSystem::RequestUnmount( |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| GetSmbProviderClient()->Unmount( |
| GetMountId(), base::BindOnce(&SmbFileSystem::HandleRequestUnmountCallback, |
| AsWeakPtr(), callback)); |
| return CreateAbortCallback(); |
| } |
| |
| void SmbFileSystem::HandleRequestUnmountCallback( |
| const storage::AsyncFileUtil::StatusCallback& callback, |
| smbprovider::ErrorType error) { |
| base::File::Error result = TranslateError(error); |
| if (result == base::File::FILE_OK) { |
| result = RunUnmountCallback( |
| file_system_info_.provider_id(), file_system_info_.file_system_id(), |
| file_system_provider::Service::UNMOUNT_REASON_USER); |
| } |
| callback.Run(result); |
| } |
| |
| AbortCallback SmbFileSystem::GetMetadata( |
| const base::FilePath& entry_path, |
| ProvidedFileSystemInterface::MetadataFieldMask fields, |
| const ProvidedFileSystemInterface::GetMetadataCallback& callback) { |
| GetSmbProviderClient()->GetMetadataEntry( |
| GetMountId(), entry_path, |
| base::BindOnce(&SmbFileSystem::HandleRequestGetMetadataEntryCallback, |
| AsWeakPtr(), fields, callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::GetActions( |
| const std::vector<base::FilePath>& entry_paths, |
| const GetActionsCallback& callback) { |
| const std::vector<file_system_provider::Action> actions; |
| // No actions are currently supported. |
| callback.Run(actions, base::File::FILE_OK); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::ExecuteAction( |
| const std::vector<base::FilePath>& entry_paths, |
| const std::string& action_id, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| NOTIMPLEMENTED(); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::ReadDirectory( |
| const base::FilePath& directory_path, |
| const storage::AsyncFileUtil::ReadDirectoryCallback& callback) { |
| GetSmbProviderClient()->ReadDirectory( |
| GetMountId(), directory_path, |
| base::BindOnce(&SmbFileSystem::HandleRequestReadDirectoryCallback, |
| AsWeakPtr(), callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::OpenFile(const base::FilePath& file_path, |
| file_system_provider::OpenFileMode mode, |
| const OpenFileCallback& callback) { |
| bool writeable = |
| mode == file_system_provider::OPEN_FILE_MODE_WRITE ? true : false; |
| GetSmbProviderClient()->OpenFile( |
| GetMountId(), file_path, writeable, |
| base::BindOnce(&SmbFileSystem::HandleRequestOpenFileCallback, AsWeakPtr(), |
| callback)); |
| return CreateAbortCallback(); |
| } |
| |
| void SmbFileSystem::HandleRequestOpenFileCallback( |
| const OpenFileCallback& callback, |
| smbprovider::ErrorType error, |
| int32_t file_id) const { |
| callback.Run(file_id, TranslateError(error)); |
| } |
| |
| AbortCallback SmbFileSystem::CloseFile( |
| int file_handle, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| GetSmbProviderClient()->CloseFile( |
| GetMountId(), file_handle, |
| base::BindOnce(&SmbFileSystem::HandleStatusCallback, AsWeakPtr(), |
| callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::ReadFile( |
| int file_handle, |
| net::IOBuffer* buffer, |
| int64_t offset, |
| int length, |
| const ReadChunkReceivedCallback& callback) { |
| GetSmbProviderClient()->ReadFile( |
| GetMountId(), file_handle, offset, length, |
| base::BindOnce(&SmbFileSystem::HandleRequestReadFileCallback, AsWeakPtr(), |
| length, scoped_refptr<net::IOBuffer>(buffer), callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::CreateDirectory( |
| const base::FilePath& directory_path, |
| bool recursive, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| GetSmbProviderClient()->CreateDirectory( |
| GetMountId(), directory_path, recursive, |
| base::BindOnce(&SmbFileSystem::HandleStatusCallback, AsWeakPtr(), |
| callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::CreateFile( |
| const base::FilePath& file_path, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| GetSmbProviderClient()->CreateFile( |
| GetMountId(), file_path, |
| base::BindOnce(&SmbFileSystem::HandleStatusCallback, AsWeakPtr(), |
| callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::DeleteEntry( |
| const base::FilePath& entry_path, |
| bool recursive, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| GetSmbProviderClient()->GetDeleteList( |
| GetMountId(), entry_path, |
| base::BindOnce(&SmbFileSystem::HandleGetDeleteListCallback, AsWeakPtr(), |
| callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::CopyEntry( |
| const base::FilePath& source_path, |
| const base::FilePath& target_path, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| GetSmbProviderClient()->CopyEntry( |
| GetMountId(), source_path, target_path, |
| base::BindOnce(&SmbFileSystem::HandleStatusCallback, AsWeakPtr(), |
| callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::MoveEntry( |
| const base::FilePath& source_path, |
| const base::FilePath& target_path, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| GetSmbProviderClient()->MoveEntry( |
| GetMountId(), source_path, target_path, |
| base::BindOnce(&SmbFileSystem::HandleStatusCallback, AsWeakPtr(), |
| callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::Truncate( |
| const base::FilePath& file_path, |
| int64_t length, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| GetSmbProviderClient()->Truncate( |
| GetMountId(), file_path, length, |
| base::BindOnce(&SmbFileSystem::HandleStatusCallback, AsWeakPtr(), |
| callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::WriteFile( |
| int file_handle, |
| net::IOBuffer* buffer, |
| int64_t offset, |
| int length, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| const std::vector<uint8_t> data(buffer->data(), buffer->data() + length); |
| base::ScopedFD temp_fd = temp_file_manager_.CreateTempFile(data); |
| |
| GetSmbProviderClient()->WriteFile( |
| GetMountId(), file_handle, offset, length, std::move(temp_fd), |
| base::BindOnce(&SmbFileSystem::HandleStatusCallback, AsWeakPtr(), |
| callback)); |
| return CreateAbortCallback(); |
| } |
| |
| AbortCallback SmbFileSystem::AddWatcher( |
| const GURL& origin, |
| const base::FilePath& entry_path, |
| bool recursive, |
| bool persistent, |
| const storage::AsyncFileUtil::StatusCallback& callback, |
| const storage::WatcherManager::NotificationCallback& |
| notification_callback) { |
| // Watchers are not supported. |
| // This method should not be getting called since watchable is set to false. |
| // crbug.com/796334. |
| callback.Run(base::File::FILE_ERROR_INVALID_OPERATION); |
| return CreateAbortCallback(); |
| } |
| |
| void SmbFileSystem::RemoveWatcher( |
| const GURL& origin, |
| const base::FilePath& entry_path, |
| bool recursive, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| // Watchers are not supported. |
| // This method should not be getting called since watchable is set to false. |
| // http://www.crbug.com/796334. |
| callback.Run(base::File::FILE_ERROR_INVALID_OPERATION); |
| } |
| |
| const file_system_provider::ProvidedFileSystemInfo& |
| SmbFileSystem::GetFileSystemInfo() const { |
| return file_system_info_; |
| } |
| |
| file_system_provider::RequestManager* SmbFileSystem::GetRequestManager() { |
| NOTIMPLEMENTED(); |
| return NULL; |
| } |
| |
| file_system_provider::Watchers* SmbFileSystem::GetWatchers() { |
| // Watchers are not supported. |
| // This method should not be getting called since watchable is set to false. |
| // http://www.crbug.com/796334. |
| return &watchers_; |
| } |
| |
| const file_system_provider::OpenedFiles& SmbFileSystem::GetOpenedFiles() const { |
| NOTIMPLEMENTED(); |
| return opened_files_; |
| } |
| |
| void SmbFileSystem::AddObserver( |
| file_system_provider::ProvidedFileSystemObserver* observer) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void SmbFileSystem::RemoveObserver( |
| file_system_provider::ProvidedFileSystemObserver* observer) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void SmbFileSystem::SmbFileSystem::Notify( |
| const base::FilePath& entry_path, |
| bool recursive, |
| storage::WatcherManager::ChangeType change_type, |
| std::unique_ptr<file_system_provider::ProvidedFileSystemObserver::Changes> |
| changes, |
| const std::string& tag, |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void SmbFileSystem::Configure( |
| const storage::AsyncFileUtil::StatusCallback& callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void SmbFileSystem::HandleRequestReadDirectoryCallback( |
| const storage::AsyncFileUtil::ReadDirectoryCallback& callback, |
| smbprovider::ErrorType error, |
| const smbprovider::DirectoryEntryListProto& entries) const { |
| uint32_t batch_size = kReadDirectoryInitialBatchSize; |
| storage::AsyncFileUtil::EntryList entry_list; |
| |
| // Loop through the entries and send when the desired batch size is hit. |
| for (const smbprovider::DirectoryEntryProto& entry : entries.entries()) { |
| entry_list.emplace_back(entry.name(), MapEntryType(entry.is_directory())); |
| |
| if (entry_list.size() == batch_size) { |
| callback.Run(base::File::FILE_OK, entry_list, true /* has_more */); |
| entry_list.clear(); |
| |
| // Double the batch size until it gets to the maximum size. |
| batch_size = std::min(batch_size * 2, kReadDirectoryMaxBatchSize); |
| } |
| } |
| |
| callback.Run(TranslateError(error), entry_list, false /* has_more */); |
| } |
| |
| void SmbFileSystem::HandleGetDeleteListCallback( |
| storage::AsyncFileUtil::StatusCallback callback, |
| smbprovider::ErrorType list_error, |
| const smbprovider::DeleteListProto& delete_list) { |
| if (delete_list.entries_size() == 0) { |
| // There are no entries to delete. |
| DCHECK_NE(smbprovider::ERROR_OK, list_error); |
| callback.Run(TranslateError(list_error)); |
| return; |
| } |
| |
| for (int i = 0; i < delete_list.entries_size(); ++i) { |
| const base::FilePath entry_path(delete_list.entries(i)); |
| bool is_last_entry = (i == delete_list.entries_size() - 1); |
| |
| GetSmbProviderClient()->DeleteEntry( |
| GetMountId(), entry_path, false /* recursive */, |
| base::BindOnce(&SmbFileSystem::HandleDeleteEntryCallback, AsWeakPtr(), |
| callback, list_error, is_last_entry)); |
| } |
| } |
| |
| void SmbFileSystem::HandleDeleteEntryCallback( |
| storage::AsyncFileUtil::StatusCallback callback, |
| smbprovider::ErrorType list_error, |
| bool is_last_entry, |
| smbprovider::ErrorType delete_error) const { |
| if (is_last_entry) { |
| // Only run the callback once. |
| if (list_error != smbprovider::ERROR_OK) { |
| delete_error = list_error; |
| } |
| callback.Run(TranslateError(delete_error)); |
| } |
| } |
| |
| void SmbFileSystem::HandleRequestGetMetadataEntryCallback( |
| ProvidedFileSystemInterface::MetadataFieldMask fields, |
| const ProvidedFileSystemInterface::GetMetadataCallback& callback, |
| smbprovider::ErrorType error, |
| const smbprovider::DirectoryEntryProto& entry) const { |
| if (error != smbprovider::ERROR_OK) { |
| callback.Run(std::unique_ptr<file_system_provider::EntryMetadata>(), |
| TranslateError(error)); |
| return; |
| } |
| std::unique_ptr<file_system_provider::EntryMetadata> metadata = |
| std::make_unique<file_system_provider::EntryMetadata>(); |
| if (RequestedIsDirectory(fields)) { |
| metadata->is_directory = std::make_unique<bool>(entry.is_directory()); |
| } |
| if (RequestedName(fields)) { |
| metadata->name = std::make_unique<std::string>(entry.name()); |
| } |
| if (RequestedSize(fields)) { |
| metadata->size = std::make_unique<int64_t>(entry.size()); |
| } |
| if (RequestedModificationTime(fields)) { |
| metadata->modification_time = std::make_unique<base::Time>( |
| base::Time::FromTimeT(entry.last_modified_time())); |
| } |
| if (RequestedThumbnail(fields)) { |
| metadata->thumbnail = std::make_unique<std::string>(kUnknownImageDataUri); |
| } |
| // Mime types are not supported. |
| callback.Run(std::move(metadata), base::File::FILE_OK); |
| } |
| |
| base::File::Error SmbFileSystem::RunUnmountCallback( |
| const ProviderId& provider_id, |
| const std::string& file_system_id, |
| file_system_provider::Service::UnmountReason reason) { |
| base::File::Error error = |
| std::move(unmount_callback_).Run(provider_id, file_system_id, reason); |
| return error; |
| } |
| |
| void SmbFileSystem::HandleRequestReadFileCallback( |
| int32_t length, |
| scoped_refptr<net::IOBuffer> buffer, |
| const ReadChunkReceivedCallback& callback, |
| smbprovider::ErrorType error, |
| const base::ScopedFD& fd) const { |
| if (error != smbprovider::ERROR_OK) { |
| callback.Run(0 /* chunk_length */, false /* has_more */, |
| TranslateError(error)); |
| return; |
| } |
| |
| int32_t total_read = 0; |
| while (total_read < length) { |
| // read() may return less than the requested amount of bytes. If this |
| // happens, try to read the remaining bytes. |
| const int32_t bytes_read = HANDLE_EINTR( |
| read(fd.get(), buffer->data() + total_read, length - total_read)); |
| if (bytes_read < 0) { |
| // This is an error case, return an error immediately. |
| callback.Run(0 /* chunk_length */, false /* has_more */, |
| base::File::FILE_ERROR_IO); |
| return; |
| } |
| if (bytes_read == 0) { |
| break; |
| } |
| total_read += bytes_read; |
| } |
| callback.Run(total_read, false /* has_more */, base::File::FILE_OK); |
| } |
| |
| void SmbFileSystem::HandleStatusCallback( |
| const storage::AsyncFileUtil::StatusCallback& callback, |
| smbprovider::ErrorType error) const { |
| callback.Run(TranslateError(error)); |
| } |
| |
| base::WeakPtr<file_system_provider::ProvidedFileSystemInterface> |
| SmbFileSystem::GetWeakPtr() { |
| return AsWeakPtr(); |
| } |
| |
| } // namespace smb_client |
| } // namespace chromeos |