Commit e06663d0 authored by Anand K. Mistry's avatar Anand K. Mistry Committed by Commit Bot

Add Mojo interface to connect and mount smbfs.

Also add a host encapsulation class, and a helper class for performing
the actual mount operation.

Bug: 939235
Change-Id: I2e8af1c88fe59d190d41b31d8b7060be6fdb0e08
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1923868
Commit-Queue: Anand Mistry <amistry@chromium.org>
Reviewed-by: default avatarSergei Datsenko <dats@chromium.org>
Reviewed-by: default avatarSam McNally <sammc@chromium.org>
Reviewed-by: default avatarRyo Hashimoto <hashimoto@chromium.org>
Reviewed-by: default avatarKen Rockot <rockot@google.com>
Cr-Commit-Position: refs/heads/master@{#722378}
parent 6c89c946
......@@ -25,6 +25,7 @@ test("chromeos_components_unittests") {
"//chromeos/components/power:unit_tests",
"//chromeos/components/proximity_auth:unit_tests",
"//chromeos/components/quick_answers:unit_tests",
"//chromeos/components/smbfs:unit_tests",
"//chromeos/components/sync_wifi:unit_tests",
"//chromeos/components/tether:unit_tests",
"//mojo/core/embedder",
......
# Copyright 2019 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.
assert(is_chromeos, "Non-ChromeOS builds cannot depend on //chromeos")
component("smbfs") {
sources = [
"smbfs_host.cc",
"smbfs_host.h",
"smbfs_mounter.cc",
"smbfs_mounter.h",
]
deps = [
"//base",
"//chromeos/components/mojo_bootstrap",
"//chromeos/components/smbfs/mojom",
"//chromeos/disks",
"//mojo/public/cpp/bindings",
"//net",
]
defines = [ "IS_SMBFS_IMPL" ]
}
source_set("unit_tests") {
testonly = true
sources = [
"smbfs_host_unittest.cc",
"smbfs_mounter_unittest.cc",
]
deps = [
":smbfs",
"//base",
"//base/test:test_support",
"//chromeos/components/mojo_bootstrap",
"//chromeos/components/smbfs/mojom",
"//chromeos/disks:test_support",
"//mojo/core/embedder",
"//mojo/public/cpp/bindings",
"//mojo/public/cpp/system",
"//testing/gmock",
"//testing/gtest",
]
}
specific_include_rules = {
"smbfs_mounter_unittest\.cc": [
"+mojo/core/embedder",
],
}
amistry@chromium.org
dats@chromium.org
slangley@chromium.org
# COMPONENT: Platform>Apps>FileManager
chromeos/components/smbfs
=========================
smbfs is a FUSE-based filesystem for accessing SMB file shares on Chrome OS.
This directory contains classes that are used to mount, connect and communicate
with an instance of smbfs.
# Copyright 2019 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.
import("//mojo/public/tools/bindings/mojom.gni")
mojom_component("mojom") {
sources = [
"smbfs.mojom",
]
public_deps = [
"//mojo/public/mojom/base",
]
output_prefix = "smbfs_mojom"
macro_prefix = "SMBFS_MOJOM"
}
per-file *.mojom=set noparent
per-file *.mojom=file://ipc/SECURITY_OWNERS
// Copyright 2019 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.
module smbfs.mojom;
// This file is shared between Chrome and Chrome OS.
// In Chrome, this file is located at:
// //chromeos/components/smbfs/mojom/smbfs.mojom
// In Chrome OS, this file is located at:
// //platform2/smbfs/mojom/smbfs.mojom
// Implemented by SmbFs, used from Chrome.
interface SmbFsBootstrap {
// Connect to an SMB share. This method must only be called once.
MountShare(MountOptions options, pending_remote<SmbFsDelegate> delegate) =>
(MountError error, pending_remote<SmbFs>? smbfs);
};
// Implemented by SmbFs, used from Chrome.
interface SmbFs {
};
// Implemented by Chrome, used from SmbFs.
interface SmbFsDelegate {
};
enum MountError {
// Success.
kOk = 0,
// Generic code for uncategorized errors.
kUnknown = 1,
// Mount timeout.
kTimeout = 2,
// Share URL is invalid.
kInvalidUrl = 3,
// An invalid combination of mount options was specified, or required
// options were missing.
kInvalidOptions = 4,
// Share not found.
kNotFound = 5,
// Share access denied (i.e. username/password error).
kAccessDenied = 6,
// Invalid protocol (i.e. SMB1).
kInvalidProtocol = 7,
};
struct MountOptions {
// Full share path. Must be in the form "smb://hostname/sharename", and must
// have the hostname as entered by the user and NOT resolved to an IP address
// (unless the user entered an IP address as the hostname).
string share_path;
// Authentication parameters.
string username;
string workgroup;
string password;
// Allow NTLM authentication.
bool allow_ntlm = false;
};
// Copyright 2019 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 "chromeos/components/smbfs/smbfs_host.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/macros.h"
#include "chromeos/disks/disk_mount_manager.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace smbfs {
namespace {
class SmbFsDelegateImpl : public mojom::SmbFsDelegate {
public:
SmbFsDelegateImpl(
mojo::PendingReceiver<mojom::SmbFsDelegate> pending_receiver,
base::OnceClosure disconnect_callback)
: receiver_(this, std::move(pending_receiver)) {
receiver_.set_disconnect_handler(std::move(disconnect_callback));
}
~SmbFsDelegateImpl() override = default;
private:
mojo::Receiver<mojom::SmbFsDelegate> receiver_;
DISALLOW_COPY_AND_ASSIGN(SmbFsDelegateImpl);
};
} // namespace
SmbFsHost::Delegate::~Delegate() = default;
SmbFsHost::SmbFsHost(
const base::FilePath& mount_path,
Delegate* delegate,
chromeos::disks::DiskMountManager* disk_mount_manager,
mojo::Remote<mojom::SmbFs> smbfs_remote,
mojo::PendingReceiver<mojom::SmbFsDelegate> delegate_receiver)
: mount_path_(mount_path),
delegate_(delegate),
disk_mount_manager_(disk_mount_manager),
smbfs_(std::move(smbfs_remote)),
delegate_impl_(std::make_unique<SmbFsDelegateImpl>(
std::move(delegate_receiver),
base::BindOnce(&SmbFsHost::OnDisconnect, base::Unretained(this)))) {
DCHECK(!mount_path.empty());
DCHECK(delegate);
smbfs_.set_disconnect_handler(
base::BindOnce(&SmbFsHost::OnDisconnect, base::Unretained(this)));
}
SmbFsHost::~SmbFsHost() {
disk_mount_manager_->UnmountPath(mount_path_.value(), base::DoNothing());
}
void SmbFsHost::OnDisconnect() {
// Ensure only one disconnection event occurs.
smbfs_.reset();
delegate_impl_.reset();
// This may delete us.
delegate_->OnDisconnected();
}
} // namespace smbfs
// Copyright 2019 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.
#ifndef CHROMEOS_COMPONENTS_SMBFS_SMBFS_HOST_H_
#define CHROMEOS_COMPONENTS_SMBFS_SMBFS_HOST_H_
#include <memory>
#include "base/component_export.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "chromeos/components/smbfs/mojom/smbfs.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace chromeos {
namespace disks {
class DiskMountManager;
} // namespace disks
} // namespace chromeos
namespace smbfs {
// SmbFsHost is a connection to a running instance of smbfs. It exposes methods
// provided by smbfs over Mojo (eg. server-side copy), and provides access to
// the host from smbfs using the Delegate interface. Destroying SmbFsHost will
// unmount and clean up the smbfs instance.
class COMPONENT_EXPORT(SMBFS) SmbFsHost {
public:
class Delegate {
public:
virtual ~Delegate();
// Notification that the smbfs process is no longer connected via Mojo.
virtual void OnDisconnected() = 0;
};
SmbFsHost(const base::FilePath& mount_path,
Delegate* delegate,
chromeos::disks::DiskMountManager* disk_mount_manager,
mojo::Remote<mojom::SmbFs> smbfs_remote,
mojo::PendingReceiver<mojom::SmbFsDelegate> delegate_receiver);
~SmbFsHost();
// Returns the path where SmbFS is mounted.
const base::FilePath& mount_path() const { return mount_path_; }
private:
// Mojo disconnection handler.
void OnDisconnect();
const base::FilePath mount_path_;
Delegate* const delegate_;
chromeos::disks::DiskMountManager* const disk_mount_manager_;
mojo::Remote<mojom::SmbFs> smbfs_;
std::unique_ptr<mojom::SmbFsDelegate> delegate_impl_;
DISALLOW_COPY_AND_ASSIGN(SmbFsHost);
};
} // namespace smbfs
#endif // CHROMEOS_COMPONENTS_SMBFS_SMBFS_HOST_H_
// Copyright 2019 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 "chromeos/components/smbfs/smbfs_host.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "chromeos/disks/mock_disk_mount_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
namespace smbfs {
namespace {
constexpr base::FilePath::CharType kMountPath[] = FILE_PATH_LITERAL("/foo/bar");
class MockDelegate : public SmbFsHost::Delegate {
public:
MOCK_METHOD(void, OnDisconnected, (), (override));
};
class SmbFsHostTest : public testing::Test {
protected:
void SetUp() override {
smbfs_pending_receiver_ = smbfs_remote_.BindNewPipeAndPassReceiver();
delegate_pending_receiver_ = delegate_remote_.BindNewPipeAndPassReceiver();
}
base::test::TaskEnvironment task_environment_;
MockDelegate mock_delegate_;
chromeos::disks::MockDiskMountManager mock_disk_mount_manager_;
mojo::Remote<mojom::SmbFs> smbfs_remote_;
mojo::PendingReceiver<mojom::SmbFs> smbfs_pending_receiver_;
mojo::Remote<mojom::SmbFsDelegate> delegate_remote_;
mojo::PendingReceiver<mojom::SmbFsDelegate> delegate_pending_receiver_;
};
TEST_F(SmbFsHostTest, DisconnectDelegate) {
base::RunLoop run_loop;
EXPECT_CALL(mock_delegate_, OnDisconnected())
.WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
EXPECT_CALL(mock_disk_mount_manager_, UnmountPath(kMountPath, _))
.WillOnce(base::test::RunOnceCallback<1>(chromeos::MOUNT_ERROR_NONE));
std::unique_ptr<SmbFsHost> host = std::make_unique<SmbFsHost>(
base::FilePath(kMountPath), &mock_delegate_, &mock_disk_mount_manager_,
std::move(smbfs_remote_), std::move(delegate_pending_receiver_));
delegate_remote_.reset();
run_loop.Run();
}
TEST_F(SmbFsHostTest, DisconnectSmbFs) {
base::RunLoop run_loop;
EXPECT_CALL(mock_delegate_, OnDisconnected())
.WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
EXPECT_CALL(mock_disk_mount_manager_, UnmountPath(kMountPath, _))
.WillOnce(base::test::RunOnceCallback<1>(chromeos::MOUNT_ERROR_NONE));
std::unique_ptr<SmbFsHost> host = std::make_unique<SmbFsHost>(
base::FilePath(kMountPath), &mock_delegate_, &mock_disk_mount_manager_,
std::move(smbfs_remote_), std::move(delegate_pending_receiver_));
smbfs_pending_receiver_.reset();
run_loop.Run();
}
TEST_F(SmbFsHostTest, UnmountOnDestruction) {
EXPECT_CALL(mock_delegate_, OnDisconnected()).Times(0);
EXPECT_CALL(mock_disk_mount_manager_, UnmountPath(kMountPath, _))
.WillOnce(base::test::RunOnceCallback<1>(chromeos::MOUNT_ERROR_NONE));
base::RunLoop run_loop;
std::unique_ptr<SmbFsHost> host = std::make_unique<SmbFsHost>(
base::FilePath(kMountPath), &mock_delegate_, &mock_disk_mount_manager_,
std::move(smbfs_remote_), std::move(delegate_pending_receiver_));
run_loop.RunUntilIdle();
host.reset();
}
} // namespace
} // namespace smbfs
// Copyright 2019 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 "chromeos/components/smbfs/smbfs_mounter.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "chromeos/components/mojo_bootstrap/pending_connection_manager.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
namespace smbfs {
namespace {
constexpr char kMessagePipeName[] = "smbfs-bootstrap";
constexpr char kMountUrlPrefix[] = "smbfs://";
constexpr base::TimeDelta kMountTimeout = base::TimeDelta::FromSeconds(20);
} // namespace
SmbFsMounter::MountOptions::MountOptions() = default;
SmbFsMounter::MountOptions::MountOptions(const MountOptions&) = default;
SmbFsMounter::MountOptions::~MountOptions() = default;
SmbFsMounter::SmbFsMounter(
const std::string& share_path,
const std::string& mount_dir_name,
const MountOptions& options,
SmbFsHost::Delegate* delegate,
chromeos::disks::DiskMountManager* disk_mount_manager)
: share_path_(share_path),
mount_dir_name_(mount_dir_name),
options_(options),
delegate_(delegate),
disk_mount_manager_(disk_mount_manager),
token_(base::UnguessableToken::Create()),
mount_url_(base::StrCat({kMountUrlPrefix, token_.ToString()})) {
DCHECK(delegate_);
DCHECK(disk_mount_manager_);
}
SmbFsMounter::~SmbFsMounter() {
if (mojo_fd_pending_) {
mojo_bootstrap::PendingConnectionManager::Get()
.CancelExpectedOpenIpcChannel(token_);
}
disk_mount_manager_->RemoveObserver(this);
}
void SmbFsMounter::Mount(SmbFsMounter::DoneCallback callback) {
DCHECK(!callback_);
DCHECK(callback);
CHECK(!mojo_fd_pending_);
callback_ = std::move(callback);
mojo_bootstrap::PendingConnectionManager::Get().ExpectOpenIpcChannel(
token_,
base::BindOnce(&SmbFsMounter::OnIpcChannel, base::Unretained(this)));
mojo_fd_pending_ = true;
bootstrap_.Bind(mojo::PendingRemote<mojom::SmbFsBootstrap>(
bootstrap_invitation_.AttachMessagePipe(kMessagePipeName),
mojom::SmbFsBootstrap::Version_));
bootstrap_.set_disconnect_handler(
base::BindOnce(&SmbFsMounter::OnMojoDisconnect, base::Unretained(this)));
disk_mount_manager_->AddObserver(this);
disk_mount_manager_->MountPath(mount_url_, "", mount_dir_name_, {},
chromeos::MOUNT_TYPE_NETWORK_STORAGE,
chromeos::MOUNT_ACCESS_MODE_READ_WRITE);
mount_timer_.Start(
FROM_HERE, kMountTimeout,
base::BindOnce(&SmbFsMounter::OnMountTimeout, base::Unretained(this)));
}
void SmbFsMounter::OnMountEvent(
chromeos::disks::DiskMountManager::MountEvent event,
chromeos::MountError error_code,
const chromeos::disks::DiskMountManager::MountPointInfo& mount_info) {
if (!callback_) {
// This can happen if the mount timeout expires and the callback is already
// run with a timeout error.
return;
}
if (mount_url_.empty() ||
mount_info.mount_type != chromeos::MOUNT_TYPE_NETWORK_STORAGE ||
mount_info.source_path != mount_url_ ||
event != chromeos::disks::DiskMountManager::MOUNTING) {
return;
}
disk_mount_manager_->RemoveObserver(this);
if (error_code != chromeos::MOUNT_ERROR_NONE) {
LOG(WARNING) << "smbfs mount error: " << error_code;
ProcessMountError(mojom::MountError::kUnknown);
return;
}
DCHECK(!mount_info.mount_path.empty());
mount_path_ = mount_info.mount_path;
mojom::MountOptionsPtr mount_options = mojom::MountOptions::New();
mount_options->share_path = share_path_;
mount_options->username = options_.username;
mount_options->workgroup = options_.workgroup;
mount_options->password = options_.password;
mount_options->allow_ntlm = options_.allow_ntlm;
mojo::PendingRemote<mojom::SmbFsDelegate> delegate_remote;
mojo::PendingReceiver<mojom::SmbFsDelegate> delegate_receiver =
delegate_remote.InitWithNewPipeAndPassReceiver();
bootstrap_->MountShare(
std::move(mount_options), std::move(delegate_remote),
base::BindOnce(&SmbFsMounter::OnMountShare, base::Unretained(this),
std::move(delegate_receiver)));
}
void SmbFsMounter::OnIpcChannel(base::ScopedFD mojo_fd) {
DCHECK(mojo_fd.is_valid());
mojo::OutgoingInvitation::Send(
std::move(bootstrap_invitation_), base::kNullProcessHandle,
mojo::PlatformChannelEndpoint(mojo::PlatformHandle(std::move(mojo_fd))));
mojo_fd_pending_ = false;
}
void SmbFsMounter::OnMountShare(
mojo::PendingReceiver<mojom::SmbFsDelegate> delegate_receiver,
mojom::MountError mount_error,
mojo::PendingRemote<mojom::SmbFs> smbfs) {
if (!callback_) {
return;
}
if (mount_error != mojom::MountError::kOk) {
LOG(WARNING) << "smbfs mount share error: " << mount_error;
ProcessMountError(mount_error);
return;
}
std::unique_ptr<SmbFsHost> host = std::make_unique<SmbFsHost>(
base::FilePath(mount_path_), delegate_, disk_mount_manager_,
mojo::Remote<mojom::SmbFs>(std::move(smbfs)),
std::move(delegate_receiver));
std::move(callback_).Run(mojom::MountError::kOk, std::move(host));
}
void SmbFsMounter::OnMojoDisconnect() {
if (!callback_) {
return;
}
LOG(WARNING) << "smbfs bootstrap disconnection";
ProcessMountError(mojom::MountError::kUnknown);
}
void SmbFsMounter::OnMountTimeout() {
if (!callback_) {
return;
}
LOG(ERROR) << "smbfs mount timeout";
ProcessMountError(mojom::MountError::kTimeout);
}
void SmbFsMounter::ProcessMountError(mojom::MountError mount_error) {
if (!mount_path_.empty()) {
disk_mount_manager_->UnmountPath(
mount_path_, base::BindOnce([](chromeos::MountError error_code) {
LOG_IF(WARNING, error_code != chromeos::MOUNT_ERROR_NONE)
<< "Error unmounting smbfs on setup failure: " << error_code;
}));
mount_path_ = {};
}
std::move(callback_).Run(mount_error, nullptr);
}
} // namespace smbfs
// Copyright 2019 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.
#ifndef CHROMEOS_COMPONENTS_SMBFS_SMBFS_MOUNTER_H_
#define CHROMEOS_COMPONENTS_SMBFS_SMBFS_MOUNTER_H_
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/component_export.h"
#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "base/timer/timer.h"
#include "base/unguessable_token.h"
#include "chromeos/components/smbfs/mojom/smbfs.mojom.h"
#include "chromeos/components/smbfs/smbfs_host.h"
#include "chromeos/disks/disk_mount_manager.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/invitation.h"
namespace smbfs {
// SmbFsMounter is a helper class that is used to mount an instance of smbfs. It
// performs all the actions necessary to start smbfs and initiate a connection
// to the SMB server.
class COMPONENT_EXPORT(SMBFS) SmbFsMounter
: public chromeos::disks::DiskMountManager::Observer {
public:
using DoneCallback =
base::OnceCallback<void(mojom::MountError, std::unique_ptr<SmbFsHost>)>;
struct MountOptions {
MountOptions();
MountOptions(const MountOptions&);
~MountOptions();
// Authentication options.
std::string username;
std::string workgroup;
std::string password;
// Allow NTLM authentication to be used.
bool allow_ntlm = false;
};
SmbFsMounter(const std::string& share_path,
const std::string& mount_dir_name,
const MountOptions& options,
SmbFsHost::Delegate* delegate,
chromeos::disks::DiskMountManager* disk_mount_manager);
~SmbFsMounter() override;
// Initiate the filesystem mount request, and run |callback| when completed.
// Must only be called once.
void Mount(DoneCallback callback);
private:
// DiskMountManager::Observer overrides.
void OnMountEvent(chromeos::disks::DiskMountManager::MountEvent event,
chromeos::MountError error_code,
const chromeos::disks::DiskMountManager::MountPointInfo&
mount_info) override;
// Callback for receiving a Mojo bootstrap channel.
void OnIpcChannel(base::ScopedFD mojo_fd);
// Callback for bootstrap Mojo MountShare() method.
void OnMountShare(
mojo::PendingReceiver<mojom::SmbFsDelegate> delegate_receiver,
mojom::MountError mount_error,
mojo::PendingRemote<mojom::SmbFs> smbfs);
// Mojo disconnection handler.
void OnMojoDisconnect();
// Mount timeout handler.
void OnMountTimeout();
// Perform cleanup and run |callback_| with |mount_error|.
void ProcessMountError(mojom::MountError mount_error);
const std::string share_path_;
const std::string mount_dir_name_;
const MountOptions options_;
SmbFsHost::Delegate* const delegate_;
chromeos::disks::DiskMountManager* const disk_mount_manager_;
const base::UnguessableToken token_;
const std::string mount_url_;
bool mojo_fd_pending_ = false;
base::OneShotTimer mount_timer_;
DoneCallback callback_;
std::string mount_path_;
mojo::OutgoingInvitation bootstrap_invitation_;
mojo::Remote<mojom::SmbFsBootstrap> bootstrap_;
DISALLOW_COPY_AND_ASSIGN(SmbFsMounter);
};
} // namespace smbfs
#endif // CHROMEOS_COMPONENTS_SMBFS_SMBFS_MOUNTER_H_
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment