blob: dfdecbe8bd5586b3d6c486c07b01c7e2e1dd1dce [file] [log] [blame]
// 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/smbprovider.h"
#include <algorithm>
#include <map>
#include <utility>
#include <base/check.h>
#include <base/check_op.h>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/memory/ptr_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#include <crypto/sha2.h>
#include <dbus/smbprovider/dbus-constants.h>
#include "smbprovider/constants.h"
#include "smbprovider/file_copy_progress.h"
#include "smbprovider/iterator/caching_iterator.h"
#include "smbprovider/iterator/directory_iterator.h"
#include "smbprovider/iterator/post_depth_first_iterator.h"
#include "smbprovider/mount_manager.h"
#include "smbprovider/netbios_packet_parser.h"
#include "smbprovider/proto.h"
#include "smbprovider/proto_bindings/directory_entry.pb.h"
#include "smbprovider/recursive_copy_progress.h"
#include "smbprovider/samba_interface.h"
#include "smbprovider/smbprovider_helper.h"
namespace smbprovider {
namespace {
constexpr mode_t kDirMode = 16877; // Dir entry
void PopulateMountRootStat(struct stat* mount_root_stat) {
mount_root_stat->st_size = 0;
mount_root_stat->st_mode = kDirMode;
mount_root_stat->st_mtime = 0;
}
base::FilePath MakePasswordFileName(const std::string& share_path,
const std::string& username,
const std::string& workgroup) {
const std::string raw_name = base::StringPrintf(
"%s@@%s@%s", share_path.c_str(), username.c_str(), workgroup.c_str());
const std::string raw_hash = crypto::SHA256HashString(raw_name);
CHECK_EQ(raw_hash.size(), crypto::kSHA256Length);
return base::FilePath(base::HexEncode(raw_hash.c_str(), raw_hash.size()));
}
} // namespace
bool GetEntries(const ReadDirectoryOptionsProto& options,
CachingIterator iterator,
int32_t* error_code,
ProtoBlob* out_entries) {
DCHECK(error_code);
DCHECK(out_entries);
DirectoryEntryListProto directory_entries;
int32_t result = iterator.Init();
while (result == 0) {
if (iterator.IsDone()) {
*error_code = static_cast<int32_t>(
SerializeProtoToBlob(directory_entries, out_entries));
return true;
}
AddDirectoryEntry(iterator.Get(), &directory_entries);
result = iterator.Next();
}
// The while-loop is only exited if there is an error. A full successful
// execution will return from inside the above while-loop.
*error_code = GetErrorFromErrno(result);
LogOperationError(GetMethodName(options), GetMountId(options),
static_cast<ErrorType>(*error_code));
return false;
}
bool GetShareEntries(const GetSharesOptionsProto& options,
ShareIterator iterator,
int32_t* error_code,
ProtoBlob* out_entries) {
DCHECK(error_code);
DCHECK(out_entries);
DirectoryEntryListProto directory_entries;
int32_t result = iterator.Init();
while (result == 0) {
if (iterator.IsDone()) {
*error_code = static_cast<int32_t>(
SerializeProtoToBlob(directory_entries, out_entries));
return true;
}
AddDirectoryEntry(iterator.Get(), &directory_entries);
result = iterator.Next();
}
// The while-loop is only exited if there is an error. A full successful
// execution will return from inside the above while-loop.
*error_code = GetErrorFromErrno(result);
LogOperationError(GetMethodName(options), GetMountId(options),
static_cast<ErrorType>(*error_code));
return false;
}
SmbProvider::SmbProvider(
std::unique_ptr<brillo::dbus_utils::DBusObject> dbus_object,
std::unique_ptr<MountManager> mount_manager,
std::unique_ptr<KerberosArtifactSynchronizer>
kerberos_artifact_synchronizer,
const base::FilePath& daemon_store_directory)
: org::chromium::SmbProviderAdaptor(this),
dbus_object_(std::move(dbus_object)),
mount_manager_(std::move(mount_manager)),
kerberos_artifact_synchronizer_(
std::move(kerberos_artifact_synchronizer)),
copy_tracker_(kInitialCopyProgressTrackerId),
read_dir_tracker_(kInitialReadDirProgressTrackerId),
daemon_store_directory_(daemon_store_directory) {}
void SmbProvider::RegisterAsync(
const AsyncEventSequencer::CompletionAction& completion_callback) {
RegisterWithDBusObject(dbus_object_.get());
dbus_object_->RegisterAsync(completion_callback);
}
base::FilePath SmbProvider::GetDaemonStoreDirectory(
const std::string& username_hash) const {
CHECK(!username_hash.empty());
return daemon_store_directory_.Append(username_hash);
}
void SmbProvider::Mount(const ProtoBlob& options_blob,
const base::ScopedFD& password_fd,
int32_t* error_code,
int32_t* mount_id) {
DCHECK(error_code);
DCHECK(mount_id);
*mount_id = -1;
// The functions below will set the error if they fail.
*error_code = static_cast<int32_t>(ERROR_OK);
MountOptionsProto options;
if (!ParseOptionsProto(options_blob, &options, error_code)) {
return; // Error with parsing proto.
}
if ((options.restore_password() || options.save_password()) &&
options.account_hash().empty()) {
// |account_hash| should always be set if the password is being saved or
// restored.
LOG(ERROR) << "Unknown profile for password save/restore";
*error_code = static_cast<int32_t>(ERROR_DBUS_PARSE_FAILED);
return;
}
std::string original_share_path = options.original_path();
if (original_share_path.empty()) {
original_share_path = options.path();
}
MountConfig mount_config = ConvertToMountConfig(options);
const base::ScopedFD* passed_password_fd = &password_fd;
base::ScopedFD saved_password_fd;
base::FilePath password_filepath;
if (options.restore_password() || options.save_password()) {
password_filepath =
GetDaemonStoreDirectory(options.account_hash())
.Append(MakePasswordFileName(
original_share_path, options.username(), options.workgroup()));
}
if (options.restore_password()) {
LOG(INFO) << "Restoring saved password";
base::File password_file(password_filepath,
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!password_file.IsValid()) {
LOG(ERROR) << "Unable to open password file for read with error: "
<< password_file.error_details();
} else {
saved_password_fd.reset(password_file.TakePlatformFile());
passed_password_fd = &saved_password_fd;
}
}
// AddMount() has to be called first since the credential has to be stored
// before calling CanReadMountRoot().
AddMount(options.path(), mount_config, options.workgroup(),
options.username(), *passed_password_fd, password_filepath,
mount_id);
if (options.skip_connect()) {
return;
}
if (!CanReadMountRoot(*mount_id, options.path(), error_code)) {
// If AddMount() was successful but the mount could not be accessed, remove
// the mount from mount_manager_.
RemoveMountIfMounted(*mount_id);
return;
}
if (options.save_password()) {
LOG(INFO) << "Saving password for mount [" << *mount_id << "]";
bool success = mount_manager_->SavePasswordToFile(*mount_id);
DCHECK(success);
}
}
int32_t SmbProvider::Unmount(const ProtoBlob& options_blob) {
int32_t error_code;
UnmountOptionsProto options;
if (!ParseOptionsProto(options_blob, &options, &error_code) ||
!IsMounted(options, &error_code)) {
return error_code;
}
if (options.remove_password()) {
bool success = mount_manager_->ErasePasswordFile(GetMountId(options));
DCHECK(success);
}
if (!RemoveMount(GetMountId(options), &error_code)) {
return error_code;
}
return ERROR_OK;
}
void SmbProvider::ReadDirectory(const ProtoBlob& options_blob,
int32_t* error_code,
ProtoBlob* out_entries) {
DCHECK(error_code);
DCHECK(out_entries);
ReadDirectoryEntries(options_blob, error_code, out_entries);
}
void SmbProvider::ReadDirectoryEntries(const ProtoBlob& options_blob,
int32_t* error_code,
ProtoBlob* out_entries) {
DCHECK(error_code);
DCHECK(out_entries);
out_entries->clear();
std::string full_path;
ReadDirectoryOptionsProto options;
if (ParseOptionsAndPath(options_blob, &options, &full_path, error_code)) {
MetadataCache* cache = nullptr;
bool got_cache =
mount_manager_->GetMetadataCache(GetMountId(options), &cache);
DCHECK(got_cache);
DCHECK(cache);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
// Purge the cache of expired entries before reading next directory.
cache->PurgeExpiredEntries();
GetEntries(options, CachingIterator(full_path, samba_interface, cache),
error_code, out_entries);
}
}
void SmbProvider::ReadShareEntries(const ProtoBlob& options_blob,
int32_t* error_code,
ProtoBlob* out_entries) {
DCHECK(error_code);
DCHECK(out_entries);
out_entries->clear();
std::string full_path;
GetSharesOptionsProto options;
if (ParseOptionsAndPath(options_blob, &options, &full_path, error_code)) {
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
GetShareEntries(options, ShareIterator(full_path, samba_interface),
error_code, out_entries);
}
}
void SmbProvider::GetMetadataEntry(const ProtoBlob& options_blob,
int32_t* error_code,
ProtoBlob* out_entry) {
DCHECK(error_code);
DCHECK(out_entry);
out_entry->clear();
std::string full_path;
GetMetadataEntryOptionsProto options;
if (!ParseOptionsAndPath(options_blob, &options, &full_path, error_code)) {
return;
}
// If we have the result cached, then return it.
if (GetCachedEntry(GetMountId(options), full_path, out_entry)) {
*error_code = static_cast<int32_t>(ERROR_OK);
return;
}
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
int32_t get_status_error = 0;
struct stat stat_info;
// Always return a successful response on the root. If the files app detects
// a failure here it will block any further requests to the mount.
if (options.entry_path() == "/") {
PopulateMountRootStat(&stat_info);
} else {
get_status_error =
samba_interface->GetEntryStatus(full_path.c_str(), &stat_info);
}
if (get_status_error != 0) {
LogAndSetError(options, GetErrorFromErrno(get_status_error), error_code);
return;
}
*error_code = GetDirectoryEntryProtoFromStat(full_path, stat_info, out_entry);
}
void SmbProvider::OpenFile(const ProtoBlob& options_blob,
int32_t* error_code,
int32_t* file_id) {
DCHECK(error_code);
DCHECK(file_id);
std::string full_path;
OpenFileOptionsProto options;
if (!ParseOptionsAndPath(options_blob, &options, &full_path, error_code)) {
return;
}
if (!OpenFile(options, full_path, error_code, file_id)) {
*file_id = -1;
return;
}
*error_code = static_cast<int32_t>(ERROR_OK);
}
int32_t SmbProvider::CloseFile(const ProtoBlob& options_blob) {
int32_t error_code;
CloseFileOptionsProto options;
if (!ParseOptionsProto(options_blob, &options, &error_code)) {
return error_code;
}
if (!IsMounted(options, &error_code)) {
return error_code;
}
if (!CloseFile(options, options.file_id(), &error_code)) {
return error_code;
}
return static_cast<int32_t>(ERROR_OK);
}
int32_t SmbProvider::DeleteEntry(const ProtoBlob& options_blob) {
int32_t error_code;
DeleteEntryOptionsProto options;
std::string full_path;
if (!ParseOptionsAndPath(options_blob, &options, &full_path, &error_code)) {
return error_code;
}
bool is_directory;
int32_t get_type_result;
if (!GetEntryType(GetMountId(options), full_path, &get_type_result,
&is_directory)) {
LogAndSetError(options, GetErrorFromErrno(get_type_result), &error_code);
return error_code;
}
int32_t result;
if (is_directory) {
if (options.recursive()) {
result = RecursiveDelete(GetMountId(options), full_path);
} else {
result = DeleteDirectory(GetMountId(options), full_path);
}
} else {
result = DeleteFile(GetMountId(options), full_path);
}
if (result != 0) {
LogAndSetError(options, GetErrorFromErrno(result), &error_code);
return error_code;
}
return static_cast<int32_t>(ERROR_OK);
}
void SmbProvider::ReadFile(const ProtoBlob& options_blob,
int32_t* error_code,
brillo::dbus_utils::FileDescriptor* temp_fd) {
DCHECK(error_code);
DCHECK(temp_fd);
ReadFileOptionsProto options;
// The functions below will set the error if they fail.
*error_code = static_cast<int32_t>(ERROR_OK);
bool success = ParseOptionsProto(options_blob, &options, error_code) &&
IsMounted(options, error_code) && Seek(options, error_code) &&
ReadFileIntoBuffer(options, error_code) &&
WriteTempFile(options, content_buffer_, error_code, temp_fd);
if (!success) {
*temp_fd = GenerateEmptyFile();
}
}
int32_t SmbProvider::CreateFile(const ProtoBlob& options_blob) {
int32_t error_code;
std::string full_path;
CreateFileOptionsProto options;
if (!ParseOptionsAndPath(options_blob, &options, &full_path, &error_code)) {
return error_code;
}
int32_t file_id;
// CreateFile() gives us back an open file descriptor to the newly created
// file.
if (!CreateFile(options, full_path, &file_id, &error_code)) {
return error_code;
}
// Close the file handle from CreateFile().
if (!CloseFile(options, file_id, &error_code)) {
// Attempt to delete the file since file will not be usable.
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
int32_t unlink_result = samba_interface->Unlink(full_path);
if (unlink_result != 0) {
// Log the unlink error but return the original error.
LOG(ERROR) << "Error unlinking after error closing file: "
<< GetErrorFromErrno(unlink_result);
}
return error_code;
}
return static_cast<int32_t>(ERROR_OK);
}
int32_t SmbProvider::Truncate(const ProtoBlob& options_blob) {
int32_t error_code;
std::string full_path;
TruncateOptionsProto options;
int32_t file_id;
const bool result =
ParseOptionsAndPath(options_blob, &options, &full_path, &error_code) &&
OpenFile(options, full_path, &error_code, &file_id) &&
TruncateAndCloseFile(options, file_id, options.length(), &error_code);
return result ? static_cast<int32_t>(ERROR_OK) : error_code;
}
int32_t SmbProvider::WriteFile(const ProtoBlob& options_blob,
const base::ScopedFD& temp_fd) {
int32_t error_code;
WriteFileOptionsProto options;
const bool result =
ParseOptionsProto(options_blob, &options, &error_code) &&
ReadFromFD(options, temp_fd, &error_code, &content_buffer_) &&
Seek(options, &error_code) &&
WriteFileFromBuffer(options, options.file_id(), &error_code);
return result ? static_cast<int32_t>(ERROR_OK) : error_code;
}
int32_t SmbProvider::CreateDirectory(const ProtoBlob& options_blob) {
int32_t error_code;
CreateDirectoryOptionsProto options;
std::string full_path;
const bool result =
ParseOptionsAndPath(options_blob, &options, &full_path, &error_code) &&
CreateParentsIfNecessary(options, &error_code) &&
CreateSingleDirectory(options, full_path, false /* ignore_existing */,
&error_code);
return result ? static_cast<int32_t>(ERROR_OK) : error_code;
}
int32_t SmbProvider::MoveEntry(const ProtoBlob& options_blob) {
int32_t error_code;
std::string source_path;
std::string target_path;
MoveEntryOptionsProto options;
const bool success =
ParseOptionsAndPaths(options_blob, &options, &source_path, &target_path,
&error_code) &&
MoveEntry(options, source_path, target_path, &error_code);
return success ? static_cast<int32_t>(ERROR_OK) : error_code;
}
int32_t SmbProvider::CopyEntry(const ProtoBlob& options_blob) {
int32_t error_code;
std::string source_path;
std::string target_path;
CopyEntryOptionsProto options;
const bool success =
ParseOptionsAndPaths(options_blob, &options, &source_path, &target_path,
&error_code) &&
CopyEntry(options, source_path, target_path, &error_code);
return success ? static_cast<int32_t>(ERROR_OK) : error_code;
}
void SmbProvider::GetShares(const ProtoBlob& options_blob,
int32_t* error_code,
ProtoBlob* shares) {
DCHECK(error_code);
DCHECK(shares);
ReadShareEntries(options_blob, error_code, shares);
}
void SmbProvider::SetupKerberos(SetupKerberosCallback callback,
const std::string& account_identifier) {
kerberos_artifact_synchronizer_->SetupKerberos(
account_identifier,
base::Bind(&SmbProvider::HandleSetupKerberosResponse,
base::Unretained(this), base::Passed(std::move(callback))));
}
void SmbProvider::HandleSetupKerberosResponse(SetupKerberosCallback callback,
bool result) {
callback->Return(result);
}
ProtoBlob SmbProvider::ParseNetBiosPacket(const std::vector<uint8_t>& packet,
uint16_t transaction_id) {
const std::vector<std::string> servers =
netbios::ParsePacket(packet, transaction_id);
const HostnamesProto hostnames_proto = BuildHostnamesProto(servers);
std::vector<uint8_t> out_blob;
if (SerializeProtoToBlob(hostnames_proto, &out_blob) != ERROR_OK) {
return ProtoBlob();
}
return out_blob;
}
void SmbProvider::StartCopy(const ProtoBlob& options_blob,
int32_t* error_code,
int32_t* copy_token) {
DCHECK(error_code);
DCHECK(copy_token);
std::string source_path;
std::string target_path;
CopyEntryOptionsProto options;
if (!ParseOptionsAndPaths(options_blob, &options, &source_path, &target_path,
error_code)) {
return;
}
ErrorType error = StartCopy(options, source_path, target_path, copy_token);
if (error != ERROR_OK && error != ERROR_COPY_PENDING) {
LogAndSetError(options, error, error_code);
return;
}
*error_code = static_cast<int32_t>(error);
}
int32_t SmbProvider::ContinueCopy(int32_t mount_id, int32_t copy_token) {
DCHECK_GE(mount_id, 0);
DCHECK_GE(copy_token, 0);
int32_t error_code;
if (!copy_tracker_.Contains(copy_token)) {
LogAndSetError(kContinueCopyMethod, mount_id, ERROR_COPY_FAILED,
&error_code);
return error_code;
}
if (!mount_manager_->IsAlreadyMounted(mount_id)) {
copy_tracker_.Remove(copy_token);
LogAndSetError(kContinueCopyMethod, mount_id, ERROR_COPY_FAILED,
&error_code);
return error_code;
}
ErrorType error = ContinueCopy(copy_token);
if (error != ERROR_OK && error != ERROR_COPY_PENDING) {
LogAndSetError(kContinueCopyMethod, mount_id, error, &error_code);
return error_code;
}
return static_cast<int32_t>(error);
}
void SmbProvider::StartReadDirectory(const ProtoBlob& options_blob,
int32_t* error_code,
ProtoBlob* out_entries,
int32_t* read_dir_token) {
DCHECK(error_code);
DCHECK(out_entries);
DCHECK(read_dir_token);
std::string directory_path;
ReadDirectoryOptionsProto options;
if (!ParseOptionsAndPath(options_blob, &options, &directory_path,
error_code)) {
return;
}
DirectoryEntryListProto entries;
ErrorType error =
StartReadDirectory(options, directory_path, &entries, read_dir_token);
if (error != ERROR_OK && error != ERROR_OPERATION_PENDING) {
LogAndSetError(options, error, error_code);
return;
}
ErrorType serialize_error = SerializeProtoToBlob(entries, out_entries);
if (serialize_error != ERROR_OK) {
LogAndSetError(options, serialize_error, error_code);
return;
}
*error_code = static_cast<int32_t>(error);
}
void SmbProvider::ContinueReadDirectory(int32_t mount_id,
int32_t read_dir_token,
int32_t* error_code,
ProtoBlob* out_entries) {
DCHECK_GE(mount_id, 0);
DCHECK_GE(read_dir_token, 0);
DCHECK(error_code);
DCHECK(out_entries);
if (!read_dir_tracker_.Contains(read_dir_token)) {
LogAndSetError(kContinueReadDirectoryMethod, mount_id,
ERROR_OPERATION_FAILED, error_code);
return;
}
if (!mount_manager_->IsAlreadyMounted(mount_id)) {
read_dir_tracker_.Remove(read_dir_token);
LogAndSetError(kContinueReadDirectoryMethod, mount_id,
ERROR_OPERATION_FAILED, error_code);
return;
}
DirectoryEntryListProto entries;
ErrorType error = ContinueReadDirectory(read_dir_token, &entries);
if (error != ERROR_OK && error != ERROR_OPERATION_PENDING) {
LogAndSetError(kContinueReadDirectoryMethod, mount_id, error, error_code);
return;
}
ErrorType serialize_error = SerializeProtoToBlob(entries, out_entries);
if (serialize_error != ERROR_OK) {
LogAndSetError(kContinueReadDirectoryMethod, mount_id, serialize_error,
error_code);
return;
}
*error_code = static_cast<int32_t>(error);
}
int32_t SmbProvider::UpdateMountCredentials(const ProtoBlob& options_blob,
const base::ScopedFD& password_fd) {
int32_t error_code;
UpdateMountCredentialsOptionsProto options;
const bool success =
ParseOptionsProto(options_blob, &options, &error_code) &&
mount_manager_->UpdateMountCredential(
options.mount_id(),
SmbCredential(options.workgroup(), options.username(),
GetPassword(password_fd), {}));
if (!success) {
LOG(ERROR) << "Failed to update credentials of mount id: "
<< options.mount_id();
return static_cast<int32_t>(ERROR_NOT_FOUND);
}
return static_cast<int32_t>(ERROR_OK);
}
int32_t SmbProvider::UpdateSharePath(const ProtoBlob& options_blob) {
int32_t error_code;
UpdateSharePathOptionsProto options;
const bool success =
ParseOptionsProto(options_blob, &options, &error_code) &&
mount_manager_->UpdateSharePath(options.mount_id(), options.path());
if (!success) {
LOG(ERROR) << "Failed to update share path of mount id: "
<< options.mount_id();
return static_cast<int32_t>(ERROR_NOT_FOUND);
}
return static_cast<int32_t>(ERROR_OK);
}
HostnamesProto SmbProvider::BuildHostnamesProto(
const std::vector<std::string>& hostnames) const {
HostnamesProto hostnames_proto;
for (const auto& hostname : hostnames) {
AddToHostnamesProto(hostname, &hostnames_proto);
}
return hostnames_proto;
}
SambaInterface* SmbProvider::GetSambaInterface(int32_t mount_id) const {
SambaInterface* samba_interface;
if (mount_id == kInternalMountId) {
samba_interface = mount_manager_->GetSystemSambaInterface();
} else {
bool success =
mount_manager_->GetSambaInterface(mount_id, &samba_interface);
DCHECK(success);
}
DCHECK(samba_interface);
return samba_interface;
}
template <typename Proto>
bool SmbProvider::GetFullPath(const Proto* options,
std::string* full_path) const {
DCHECK(options);
DCHECK(full_path);
const int32_t mount_id = GetMountId(*options);
const std::string entry_path = GetEntryPath(*options);
bool success = mount_manager_->GetFullPath(mount_id, entry_path, full_path);
if (!success) {
LOG(ERROR) << GetMethodName(*options) << " requested unknown mount_id "
<< mount_id;
}
return success;
}
template <>
bool SmbProvider::GetFullPath(const GetSharesOptionsProto* options,
std::string* full_path) const {
DCHECK(options);
DCHECK(full_path);
*full_path = GetEntryPath(*options);
return true;
}
template <typename Proto>
bool SmbProvider::GetFullPaths(const Proto* options,
std::string* source_full_path,
std::string* target_full_path) const {
DCHECK(options);
DCHECK(source_full_path);
DCHECK(target_full_path);
const int32_t mount_id = GetMountId(*options);
const std::string source_path = GetSourcePath(*options);
const std::string target_path = GetDestinationPath(*options);
const bool success =
mount_manager_->GetFullPath(mount_id, source_path, source_full_path) &&
mount_manager_->GetFullPath(mount_id, target_path, target_full_path);
if (!success) {
LOG(ERROR) << GetMethodName(*options) << " requested unknown mount_id "
<< mount_id;
}
return success;
}
template <typename Proto>
bool SmbProvider::ParseOptionsAndPath(const ProtoBlob& blob,
Proto* options,
std::string* full_path,
int32_t* error_code) {
if (!ParseOptionsProto(blob, options, error_code)) {
return false;
}
if (!GetFullPath(options, full_path)) {
*error_code = static_cast<int32_t>(ERROR_NOT_FOUND);
return false;
}
return true;
}
template <typename Proto>
bool SmbProvider::ParseOptionsAndPaths(const ProtoBlob& blob,
Proto* options,
std::string* source_path,
std::string* target_path,
int32_t* error_code) {
DCHECK(options);
DCHECK(source_path);
DCHECK(target_path);
DCHECK(error_code);
if (!ParseOptionsProto(blob, options, error_code)) {
return false;
}
if (!GetFullPaths(options, source_path, target_path)) {
*error_code = static_cast<int32_t>(ERROR_NOT_FOUND);
return false;
}
return true;
}
bool SmbProvider::GetEntryType(int32_t mount_id,
const std::string& full_path,
int32_t* error_code,
bool* is_directory) {
DCHECK(error_code);
DCHECK(is_directory);
SambaInterface* samba_interface = GetSambaInterface(mount_id);
struct stat stat_info;
*error_code = samba_interface->GetEntryStatus(full_path.c_str(), &stat_info);
if (*error_code != 0) {
return false;
}
if (IsDirectory(stat_info)) {
*is_directory = true;
return true;
}
if (IsFile(stat_info)) {
*is_directory = false;
return true;
}
*error_code = ENOENT;
return false;
}
template <typename Proto>
bool SmbProvider::Seek(const Proto& options, int32_t* error_code) {
DCHECK(error_code);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
int32_t result = samba_interface->Seek(options.file_id(), options.offset());
if (result != 0) {
LogAndSetError(options, GetErrorFromErrno(result), error_code);
return false;
}
return true;
}
bool SmbProvider::CanReadMountRoot(int32_t mount_id,
const std::string& mount_root,
int32_t* error_code) {
DCHECK(error_code);
SambaInterface* samba_interface = GetSambaInterface(mount_id);
DCHECK(samba_interface);
int32_t dir_id = -1;
int32_t result = samba_interface->OpenDirectory(mount_root, &dir_id);
if (result != 0) {
LogAndSetError(kMountMethod, -1, GetErrorFromErrno(result), error_code);
return false;
}
CloseDirectory(mount_id, dir_id);
return true;
}
void SmbProvider::CloseDirectory(int32_t mount_id, int32_t dir_id) {
SambaInterface* samba_interface = GetSambaInterface(mount_id);
int32_t result = samba_interface->CloseDirectory(dir_id);
if (result != 0) {
LOG(ERROR) << "Error closing directory " << dir_id;
}
}
bool SmbProvider::RemoveMount(int32_t mount_id, int32_t* error_code) {
bool removed = mount_manager_->RemoveMount(mount_id);
if (!removed) {
*error_code = static_cast<int32_t>(ERROR_NOT_FOUND);
}
return removed;
}
void SmbProvider::AddMount(const std::string& mount_root,
const MountConfig& mount_config,
const std::string& workgroup,
const std::string& username,
const base::ScopedFD& password_fd,
const base::FilePath& password_file,
int32_t* mount_id) {
SmbCredential credential(workgroup, username, GetPassword(password_fd),
password_file);
mount_manager_->AddMount(mount_root, std::move(credential), mount_config,
mount_id);
DCHECK_GE(*mount_id, 0);
}
void SmbProvider::RemoveMountIfMounted(int32_t mount_id) {
if (mount_id != -1) {
mount_manager_->RemoveMount(mount_id);
}
}
bool SmbProvider::ReadFileIntoBuffer(const ReadFileOptionsProto& options,
int32_t* error_code) {
DCHECK(error_code);
// Read the data in the member |content_buffer_|.
if (!ReadToContentBuffer(options, options.file_id(), error_code)) {
return false;
}
return true;
}
template <typename Proto>
bool SmbProvider::WriteTempFile(const Proto& options,
const std::vector<uint8_t>& buffer,
int32_t* error_code,
brillo::dbus_utils::FileDescriptor* temp_fd) {
base::ScopedFD scoped_fd = temp_file_manager_.CreateTempFile(buffer);
if (!scoped_fd.is_valid()) {
LogAndSetError(options, ERROR_IO, error_code);
return false;
}
// get() is called here instead of release() since FileDescriptor duplicates
// the FD when getting assigned, meaning the local FD still needs to be
// closed by ScopedFD when it goes out of scope.
*temp_fd = scoped_fd.get();
return true;
}
bool SmbProvider::WriteFileFromBuffer(const WriteFileOptionsProto& options,
int32_t file_id,
int32_t* error_code) {
DCHECK(error_code);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
int32_t result = samba_interface->WriteFile(file_id, content_buffer_.data(),
content_buffer_.size());
if (result != 0) {
LogAndSetError(options, GetErrorFromErrno(result), error_code);
return false;
}
return true;
}
int32_t SmbProvider::RecursiveDelete(int32_t mount_id,
const std::string& dir_path) {
PostDepthFirstIterator it = GetPostOrderIterator(mount_id, dir_path);
int32_t it_result = it.Init();
while (it_result == 0) {
if (it.IsDone()) {
return 0;
}
int32_t del_result = DeleteDirectoryEntry(mount_id, it.Get());
if (del_result != 0) {
return del_result;
}
it_result = it.Next();
}
// while-loop is only exited from if there's an iterator error.
DCHECK_NE(0, it_result);
return it_result;
}
int32_t SmbProvider::DeleteDirectoryEntry(int32_t mount_id,
const DirectoryEntry& entry) {
if (entry.is_directory) {
return DeleteDirectory(mount_id, entry.full_path);
}
return DeleteFile(mount_id, entry.full_path);
}
int32_t SmbProvider::DeleteFile(int32_t mount_id,
const std::string& file_path) {
// It's always safe to invalidate the cache regardless of whether the delete
// is successful or not.
InvalidateCacheEntry(mount_id, file_path);
SambaInterface* samba_interface = GetSambaInterface(mount_id);
return samba_interface->Unlink(file_path.c_str());
}
int32_t SmbProvider::DeleteDirectory(int32_t mount_id,
const std::string& dir_path) {
// It's always safe to invalidate the cache regardless of whether the delete
// is successful or not.
InvalidateCacheEntry(mount_id, dir_path);
SambaInterface* samba_interface = GetSambaInterface(mount_id);
return samba_interface->RemoveDirectory(dir_path.c_str());
}
void SmbProvider::InvalidateCacheEntry(int32_t mount_id,
const std::string& full_path) {
MetadataCache* cache = nullptr;
bool got_cache = mount_manager_->GetMetadataCache(mount_id, &cache);
DCHECK(got_cache);
DCHECK(cache);
// The result can be ignored since it may or may not be in the cache.
cache->RemoveEntry(full_path);
}
PostDepthFirstIterator SmbProvider::GetPostOrderIterator(
int32_t mount_id, const std::string& full_path) {
SambaInterface* samba_interface = GetSambaInterface(mount_id);
return PostDepthFirstIterator(full_path, samba_interface);
}
template <typename Proto>
bool SmbProvider::OpenFile(const Proto& options,
const std::string& full_path,
int32_t* error,
int32_t* file_id) {
DCHECK(error);
DCHECK(file_id);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
int32_t result = samba_interface->OpenFile(
full_path, GetOpenFilePermissions(options), file_id);
if (result != 0) {
LogAndSetError(options, GetErrorFromErrno(result), error);
return false;
}
return true;
}
template <typename Proto>
bool SmbProvider::CloseFile(const Proto& options,
int32_t file_id,
int32_t* error) {
DCHECK(error);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
int32_t result = samba_interface->CloseFile(file_id);
if (result != 0) {
LogAndSetError(options, GetErrorFromErrno(result), error);
return false;
}
return true;
}
template <typename Proto>
bool SmbProvider::TruncateAndCloseFile(const Proto& options,
const int32_t file_id,
int64_t length,
int32_t* error) {
DCHECK(error);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
int32_t truncate_result = samba_interface->Truncate(file_id, length);
if (truncate_result != 0) {
LogAndSetError(options, GetErrorFromErrno(truncate_result), error);
// Continue to close on error.
}
int32_t close_error;
if (!CloseFile(options, file_id, &close_error)) {
if (truncate_result == 0) {
// If Truncate was successful, set error to the close error, otherwise
// keep the truncate error.
*error = close_error;
}
return false;
}
// Return if the truncate was successful.
return truncate_result == 0;
}
bool SmbProvider::MoveEntry(const MoveEntryOptionsProto& options,
const std::string& source_path,
const std::string& target_path,
int32_t* error) {
DCHECK(error);
// Invalidate the cache for the source path. This is always safe even if the
// move fails because it will just cause a cache miss.
int32_t mount_id = GetMountId(options);
InvalidateCacheEntry(mount_id, source_path);
SambaInterface* samba_interface = GetSambaInterface(mount_id);
int32_t result = samba_interface->MoveEntry(source_path, target_path);
if (result != 0) {
LogAndSetError(options, GetErrorFromErrno(result), error);
return false;
}
return true;
}
bool SmbProvider::GenerateParentPaths(
const CreateDirectoryOptionsProto& options,
int32_t* error_code,
std::vector<std::string>* parent_paths) {
DCHECK(error_code);
DCHECK(parent_paths);
base::FilePath current_path(options.directory_path());
DCHECK(current_path.IsAbsolute());
// Skip the leaf path and start with the lowest parent.
current_path = current_path.DirName();
while (current_path.value() != "/") {
std::string full_path;
if (!mount_manager_->GetFullPath(
GetMountId(options), current_path.StripTrailingSeparators().value(),
&full_path)) {
*error_code = static_cast<int32_t>(ERROR_NOT_FOUND);
return false;
}
current_path = current_path.DirName();
parent_paths->push_back(std::move(full_path));
}
// Reverse the vector so the top parent will be first.
std::reverse(parent_paths->begin(), parent_paths->end());
return true;
}
bool SmbProvider::CreateNestedDirectories(
const CreateDirectoryOptionsProto& options,
const std::vector<std::string>& paths,
int32_t* error_code) {
DCHECK(error_code);
for (auto const& path : paths) {
if (!CreateSingleDirectory(options, path, true /* ignore_existing */,
error_code)) {
return false;
}
}
return true;
}
bool SmbProvider::CreateParentsIfNecessary(
const CreateDirectoryOptionsProto& options, int32_t* error_code) {
DCHECK(error_code);
if (!options.recursive()) {
// Return true in this case since no parents need to be created.
return true;
}
std::vector<std::string> paths;
return GenerateParentPaths(options, error_code, &paths) &&
CreateNestedDirectories(options, paths, error_code);
}
template <typename Proto>
bool SmbProvider::CreateSingleDirectory(const Proto& options,
const std::string& full_path,
bool ignore_existing,
int32_t* error_code) {
DCHECK(error_code);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
const int32_t result = samba_interface->CreateDirectory(full_path);
if (ShouldReportCreateDirError(result, ignore_existing)) {
LogAndSetError(options, GetErrorFromErrno(result), error_code);
return false;
}
return true;
}
template <typename Proto>
bool SmbProvider::CreateFile(const Proto& options,
const std::string& full_path,
int32_t* file_id,
int32_t* error) {
DCHECK(file_id);
DCHECK(error);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
int32_t result = samba_interface->CreateFile(full_path, file_id);
if (result != 0) {
LogAndSetError(options, GetErrorFromErrno(result), error);
return false;
}
return true;
}
bool SmbProvider::CopyEntry(const CopyEntryOptionsProto& options,
const std::string& source_path,
const std::string& target_path,
int32_t* error_code) {
DCHECK(error_code);
bool is_directory;
int32_t get_type_result;
if (!GetEntryType(GetMountId(options), source_path, &get_type_result,
&is_directory)) {
LogAndSetError(options, GetErrorFromErrno(get_type_result), error_code);
return false;
}
if (is_directory) {
return CreateSingleDirectory(options, target_path,
false /* ignore_existing */, error_code);
}
return CopyFile(options, source_path, target_path, error_code);
}
bool SmbProvider::CopyFile(const CopyEntryOptionsProto& options,
const std::string& source_path,
const std::string& target_path,
int32_t* error_code) {
DCHECK(error_code);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
int32_t result = samba_interface->CopyFile(source_path, target_path);
if (result != 0) {
LogAndSetError(options, GetErrorFromErrno(result), error_code);
return false;
}
return true;
}
ErrorType SmbProvider::StartCopy(const CopyEntryOptionsProto& options,
const std::string& source_path,
const std::string& target_path,
int32_t* copy_token) {
DCHECK(copy_token);
bool is_directory;
int32_t get_type_result;
if (!GetEntryType(GetMountId(options), source_path, &get_type_result,
&is_directory)) {
return GetErrorFromErrno(get_type_result);
}
if (is_directory) {
return StartDirectoryCopy(options, source_path, target_path, copy_token);
}
return StartFileCopy(options, source_path, target_path, copy_token);
}
template <typename CopyProgressType>
ErrorType SmbProvider::StartCopyProgress(const CopyEntryOptionsProto& options,
const std::string& source_path,
const std::string& target_path,
int32_t* copy_token) {
DCHECK(copy_token);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
auto copy_progress = std::make_unique<CopyProgressType>(samba_interface);
int32_t copy_result;
bool should_continue_copy =
copy_progress->StartCopy(source_path, target_path, &copy_result);
if (should_continue_copy) {
// The copy needs to be continued.
*copy_token = copy_tracker_.Insert(std::move(copy_progress));
return ERROR_COPY_PENDING;
}
if (copy_result == 0) {
// The copy is complete.
return ERROR_OK;
}
return GetErrorFromErrno(copy_result);
}
ErrorType SmbProvider::StartDirectoryCopy(const CopyEntryOptionsProto& options,
const std::string& source_path,
const std::string& target_path,
int32_t* copy_token) {
DCHECK(copy_token);
return StartCopyProgress<RecursiveCopyProgress>(options, source_path,
target_path, copy_token);
}
ErrorType SmbProvider::StartFileCopy(const CopyEntryOptionsProto& options,
const std::string& source_path,
const std::string& target_path,
int32_t* copy_token) {
DCHECK(copy_token);
return StartCopyProgress<FileCopyProgress>(options, source_path, target_path,
copy_token);
}
ErrorType SmbProvider::ContinueCopy(int32_t copy_token) {
DCHECK_GE(copy_token, 0);
DCHECK(copy_tracker_.Contains(copy_token));
int32_t copy_result;
const bool should_continue_copy =
copy_tracker_.Find(copy_token)->second->ContinueCopy(&copy_result);
if (should_continue_copy) {
// The copy needs to be continued.
return ERROR_COPY_PENDING;
}
// The copy no longer needs to be continued as it has either completed
// successfully or it failed so remove it from |copy_tracker_|.
copy_tracker_.Remove(copy_token);
if (copy_result == 0) {
// The copy is complete.
return ERROR_OK;
}
return GetErrorFromErrno(copy_result);
}
bool SmbProvider::ReadToContentBuffer(const ReadFileOptionsProto& options,
int32_t file_id,
int32_t* error_code) {
DCHECK(error_code);
// Make sure the buffer is at least large enough for the content.
content_buffer_.resize(options.length());
size_t bytes_read;
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
int32_t result = samba_interface->ReadFile(
file_id, content_buffer_.data(), content_buffer_.size(), &bytes_read);
if (result != 0) {
LogAndSetError(options, GetErrorFromErrno(result), error_code);
return false;
}
DCHECK_GE(bytes_read, 0);
DCHECK_LE(bytes_read, content_buffer_.size());
// Make sure buffer is only as big as bytes_read.
content_buffer_.resize(bytes_read);
return true;
}
void SmbProvider::GetDeleteList(const ProtoBlob& options_blob,
int32_t* error_code,
brillo::dbus_utils::FileDescriptor* temp_fd,
int32_t* bytes_written) {
DCHECK(error_code);
DCHECK(temp_fd);
DCHECK(bytes_written);
std::string full_path;
GetDeleteListOptionsProto options;
if (!ParseOptionsAndPath(options_blob, &options, &full_path, error_code)) {
*temp_fd = GenerateEmptyFile();
return;
}
bool is_directory;
int32_t get_type_result;
if (!GetEntryType(GetMountId(options), full_path, &get_type_result,
&is_directory)) {
LogAndSetError(options, GetErrorFromErrno(get_type_result), error_code);
*temp_fd = GenerateEmptyFile();
return;
}
DeleteListProto delete_list;
int32_t result =
GenerateDeleteList(options, full_path, is_directory, &delete_list);
if (result != 0) {
LogAndSetError(options, GetErrorFromErrno(result), error_code);
*temp_fd = GenerateEmptyFile();
return;
}
WriteDeleteListToTempFile(options, delete_list, error_code, temp_fd,
bytes_written);
}
bool SmbProvider::WriteDeleteListToTempFile(
const GetDeleteListOptionsProto& options,
const DeleteListProto& delete_list,
int32_t* error_code,
brillo::dbus_utils::FileDescriptor* temp_fd,
int32_t* bytes_written) {
DCHECK(error_code);
DCHECK(temp_fd);
DCHECK(bytes_written);
std::vector<uint8_t> buffer;
*error_code =
static_cast<int32_t>(SerializeProtoToBlob(delete_list, &buffer));
if (*error_code != ERROR_OK) {
*temp_fd = GenerateEmptyFile();
return false;
}
bool success = WriteTempFile(options, buffer, error_code, temp_fd);
*bytes_written = success ? buffer.size() : -1;
return success;
}
int32_t SmbProvider::GenerateDeleteList(
const GetDeleteListOptionsProto& options,
const std::string& full_path,
bool is_directory,
DeleteListProto* delete_list) {
DCHECK(delete_list);
if (!is_directory) {
// |delete_list| will only contain the relative path to the file.
AddToDeleteList(GetRelativePath(GetMountId(options), full_path),
delete_list);
return 0;
}
PostDepthFirstIterator it =
GetPostOrderIterator(GetMountId(options), full_path);
int32_t it_result = it.Init();
while (it_result == 0) {
if (it.IsDone()) {
return 0;
}
AddToDeleteList(GetRelativePath(GetMountId(options), it.Get().full_path),
delete_list);
it_result = it.Next();
}
// while-loop is only exited from if there's an iterator error.
DCHECK_NE(0, it_result);
return it_result;
}
bool SmbProvider::GetCachedEntry(int32_t mount_id,
const std::string full_path,
ProtoBlob* out_entry) {
MetadataCache* cache = nullptr;
if (!mount_manager_->GetMetadataCache(mount_id, &cache)) {
return false;
}
DCHECK(cache);
DirectoryEntry entry;
if (!cache->FindEntry(full_path, &entry)) {
return false;
}
DirectoryEntryProto proto;
ConvertToProto(entry, &proto);
if (SerializeProtoToBlob(proto, out_entry) != ERROR_OK) {
return false;
}
return true;
}
brillo::dbus_utils::FileDescriptor SmbProvider::GenerateEmptyFile() {
base::ScopedFD temp_fd = temp_file_manager_.CreateTempFile();
DCHECK(temp_fd.is_valid());
// get() is called here instead of release() since FileDescriptor duplicates
// the FD when getting assigned, meaning the local FD still needs to be
// closed by ScopedFD when it goes out of scope.
return brillo::dbus_utils::FileDescriptor(temp_fd.get());
}
std::string SmbProvider::GetRelativePath(int32_t mount_id,
const std::string& entry_path) {
return mount_manager_->GetRelativePath(mount_id, entry_path);
}
template <typename Proto>
bool SmbProvider::IsMounted(const Proto& options, int32_t* error_code) const {
const bool is_valid = mount_manager_->IsAlreadyMounted(GetMountId(options));
if (!is_valid) {
LogAndSetError(options, ERROR_NOT_FOUND, error_code);
}
return is_valid;
}
ErrorType SmbProvider::StartReadDirectory(
const ReadDirectoryOptionsProto& options,
const std::string& directory_path,
DirectoryEntryListProto* entries,
int32_t* read_dir_token) {
DCHECK(entries);
DCHECK(read_dir_token);
SambaInterface* samba_interface = GetSambaInterface(GetMountId(options));
auto read_dir_progress = std::make_unique<ReadDirProgress>(samba_interface);
MetadataCache* cache = nullptr;
bool got_cache =
mount_manager_->GetMetadataCache(GetMountId(options), &cache);
DCHECK(got_cache);
DCHECK(cache);
int32_t read_dir_result;
const bool should_continue_read_dir = read_dir_progress->StartReadDir(
directory_path, cache, &read_dir_result, entries);
if (should_continue_read_dir) {
DCHECK_EQ(0, read_dir_result);
// The read directory needs to be continued.
*read_dir_token = read_dir_tracker_.Insert(std::move(read_dir_progress));
return ERROR_OPERATION_PENDING;
}
if (read_dir_result == 0) {
// The readdir is complete.
return ERROR_OK;
}
return GetErrorFromErrnoForReadDir(read_dir_result);
}
ErrorType SmbProvider::ContinueReadDirectory(int32_t read_dir_token,
DirectoryEntryListProto* entries) {
DCHECK(entries);
DCHECK_GE(read_dir_token, 0);
DCHECK(read_dir_tracker_.Contains(read_dir_token));
int32_t read_dir_result;
const bool should_continue_read_dir =
read_dir_tracker_.Find(read_dir_token)
->second->ContinueReadDir(&read_dir_result, entries);
if (should_continue_read_dir) {
DCHECK_EQ(0, read_dir_result);
// The read directory needs to be continued.
return ERROR_OPERATION_PENDING;
}
// The read directory no longer needs to be continued as it has either
// completed successfully or it failed so remove it for |read_dir_tracker_|.
read_dir_tracker_.Remove(read_dir_token);
if (read_dir_result == 0) {
// The readdir is complete.
return ERROR_OK;
}
return GetErrorFromErrno(read_dir_result);
}
// These are required to explicitly instantiate the template functions.
template ErrorType SmbProvider::StartCopyProgress<FileCopyProgress>(
const CopyEntryOptionsProto&,
const std::string&,
const std::string&,
int32_t*);
template ErrorType SmbProvider::StartCopyProgress<RecursiveCopyProgress>(
const CopyEntryOptionsProto&,
const std::string&,
const std::string&,
int32_t*);
} // namespace smbprovider