// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/components/arc/volume_mounter/arc_volume_mounter_bridge.h"
#include <string>
#include <vector>
#include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/upstart/upstart_client.h"
#include "chromeos/ash/components/disks/disk.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"
#include "chromeos/components/disks/disks_prefs.h"
#include "components/prefs/pref_service.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_context.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/strings/grit/ui_chromeos_strings.h"
namespace arc {
namespace {
using ::ash::disks::DiskMountManager;
// TODO(crbug.com/929031): Move MyFiles constants to a common place.
constexpr char kMyFilesPath[] = "/home/chronos/user/MyFiles";
// Prefix of the removable media mount paths.
// TODO(crbug.com/1274481): Move ash-wide FileManager constants to a common
// place.
constexpr char kRemovableMediaMountPathPrefix[] = "/media/removable/";
// Dummy UUID for MyFiles volume.
constexpr char kMyFilesUuid[] = "0000000000000000000000000000CAFEF00D2019";
// Dummy UUID for testing.
constexpr char kDummyUuid[] = "00000000000000000000000000000000DEADBEEF";
// The minimum and maximum values of app UID in Android. Defined in Android's
// system/core/libcutils/include/private/android_filesystem_config.h.
constexpr uint32_t kAndroidAppUidStart = 10000;
constexpr uint32_t kAndroidAppUidEnd = 19999;
// Singleton factory for ArcVolumeMounterBridge.
class ArcVolumeMounterBridgeFactory
: public internal::ArcBrowserContextKeyedServiceFactoryBase<
ArcVolumeMounterBridge,
ArcVolumeMounterBridgeFactory> {
public:
// Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
static constexpr const char* kName = "ArcVolumeMounterBridgeFactory";
static ArcVolumeMounterBridgeFactory* GetInstance() {
return base::Singleton<ArcVolumeMounterBridgeFactory>::get();
}
private:
friend base::DefaultSingletonTraits<ArcVolumeMounterBridgeFactory>;
ArcVolumeMounterBridgeFactory() = default;
~ArcVolumeMounterBridgeFactory() override = default;
};
std::string GetChromeOsUserId() {
auto* arc_service_manager = ArcServiceManager::Get();
DCHECK(arc_service_manager);
// Return the string representation of AccountId.
return cryptohome::CreateAccountIdentifierFromAccountId(
arc_service_manager->account_id())
.account_id();
}
} // namespace
// static
ArcVolumeMounterBridge* ArcVolumeMounterBridge::GetForBrowserContext(
content::BrowserContext* context) {
return ArcVolumeMounterBridgeFactory::GetForBrowserContext(context);
}
// static
KeyedServiceBaseFactory* ArcVolumeMounterBridge::GetFactory() {
return ArcVolumeMounterBridgeFactory::GetInstance();
}
ArcVolumeMounterBridge::ArcVolumeMounterBridge(content::BrowserContext* context,
ArcBridgeService* bridge_service)
: arc_bridge_service_(bridge_service),
pref_service_(user_prefs::UserPrefs::Get(context)) {
DCHECK(pref_service_);
arc_bridge_service_->volume_mounter()->AddObserver(this);
arc_bridge_service_->volume_mounter()->SetHost(this);
DCHECK(DiskMountManager::GetInstance());
DiskMountManager::GetInstance()->AddObserver(this);
change_registerar_.Init(pref_service_);
// Start monitoring |kArcVisibleExternalStorages| changes. Note that the
// registerar automatically stops monitoring the pref in its dtor.
change_registerar_.Add(
prefs::kArcVisibleExternalStorages,
base::BindRepeating(&ArcVolumeMounterBridge::OnVisibleStoragesChanged,
weak_ptr_factory_.GetWeakPtr()));
}
ArcVolumeMounterBridge::~ArcVolumeMounterBridge() {
DCHECK(DiskMountManager::GetInstance());
DiskMountManager::GetInstance()->RemoveObserver(this);
arc_bridge_service_->volume_mounter()->SetHost(nullptr);
arc_bridge_service_->volume_mounter()->RemoveObserver(this);
}
void ArcVolumeMounterBridge::Initialize(Delegate* delegate) {
delegate_ = delegate;
DCHECK(delegate_);
}
// Sends MountEvents of all existing MountPoints in cros-disks.
void ArcVolumeMounterBridge::SendAllMountEvents() {
if (!IsReadyToSendMountingEvents()) {
DVLOG(1) << "Skipping SendAllMountEvents because it is not ready to send "
<< "mounting events to Android";
return;
}
SendMountEventForMyFiles();
for (const auto& mount_point :
DiskMountManager::GetInstance()->mount_points()) {
OnMountEvent(DiskMountManager::MountEvent::MOUNTING,
ash::MountError::kSuccess, mount_point);
}
}
// Notifies ARC of MyFiles volume by sending a mount event.
void ArcVolumeMounterBridge::SendMountEventForMyFiles() {
mojom::VolumeMounterInstance* volume_mounter_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->volume_mounter(),
OnMountEvent);
if (!volume_mounter_instance)
return;
std::string device_label =
l10n_util::GetStringUTF8(IDS_FILE_BROWSER_MY_FILES_ROOT_LABEL);
// TODO(niwa): Add a new DeviceType enum value for MyFiles.
ash::DeviceType device_type = ash::DeviceType::kSD;
// Conditionally set MyFiles to be visible for P and invisible for R. In R, we
// use IsVisibleRead so this is not needed.
const bool is_p = arc::GetArcAndroidSdkVersionAsInt() == arc::kArcVersionP;
volume_mounter_instance->OnMountEvent(mojom::MountPointInfo::New(
DiskMountManager::MOUNTING, kMyFilesPath, kMyFilesPath, kMyFilesUuid,
device_label, device_type, is_p));
}
bool ArcVolumeMounterBridge::IsVisibleToAndroidApps(
const std::string& uuid) const {
const base::Value::List& uuid_list =
pref_service_->GetList(prefs::kArcVisibleExternalStorages);
for (auto& value : uuid_list) {
if (value.is_string() && value.GetString() == uuid)
return true;
}
return false;
}
void ArcVolumeMounterBridge::OnVisibleStoragesChanged() {
// Remount all external mount points when the list of visible storage changes.
for (const auto& mount_point :
DiskMountManager::GetInstance()->mount_points()) {
OnMountEvent(DiskMountManager::MountEvent::UNMOUNTING,
ash::MountError::kSuccess, mount_point);
}
for (const auto& mount_point :
DiskMountManager::GetInstance()->mount_points()) {
OnMountEvent(DiskMountManager::MountEvent::MOUNTING,
ash::MountError::kSuccess, mount_point);
}
}
void ArcVolumeMounterBridge::OnMountEvent(
DiskMountManager::MountEvent event,
ash::MountError error_code,
const DiskMountManager::MountPoint& mount_info) {
DCHECK(delegate_);
// Skip mount events for volumes that are not shared with ARC (e.g., those
// mounted on /media/archive) by allowlisting the removable media mount paths.
if (!base::StartsWith(mount_info.mount_path, kRemovableMediaMountPathPrefix,
base::CompareCase::SENSITIVE)) {
DVLOG(1) << "Ignoring mount event for mount_path: "
<< mount_info.mount_path;
return;
}
if (error_code != ash::MountError::kSuccess) {
DVLOG(1) << "Error " << error_code << "occurs during MountEvent " << event;
return;
}
// Skip mount events if removable media is forbidden by the policy.
if (event == DiskMountManager::MountEvent::MOUNTING &&
pref_service_->GetBoolean(disks::prefs::kExternalStorageDisabled)) {
DVLOG(1) << "Ignoring mount event since policy disallows removable media";
return;
}
// Skip mount events if removable media access is disabled by a feature.
if (event == DiskMountManager::MountEvent::MOUNTING &&
!base::FeatureList::IsEnabled(kExternalStorageAccess)) {
DVLOG(1) << "Ignoring mount event since removable media is disabled by "
"feature";
return;
}
if (event == DiskMountManager::MountEvent::MOUNTING &&
!IsReadyToSendMountingEvents()) {
DVLOG(1) << "Skipping OnMountEvent because it is not ready to send "
<< "mounting events to Android";
return;
}
// Get disks information that are needed by Android MountService.
const ash::disks::Disk* disk =
DiskMountManager::GetInstance()->FindDiskBySourcePath(
mount_info.source_path);
std::string fs_uuid, device_label;
ash::DeviceType device_type = ash::DeviceType::kUnknown;
// There are several cases where disk can be null:
// 1. The disk is removed physically before being ejected/unmounted.
// 2. The disk is inserted, but then immediately removed physically. The
// disk removal will race with mount event in this case.
if (disk) {
fs_uuid = disk->fs_uuid();
device_label = disk->device_label();
device_type = disk->device_type();
} else {
// This is needed by ChromeOS tast test (arc.RemovableMedia) because it
// creates a diskless volume (hence, no uuid) and Android expects the volume
// to have a uuid.
fs_uuid = kDummyUuid;
DVLOG(1) << "Disk at " << mount_info.source_path
<< " is null during MountEvent " << event;
}
if (device_label.empty()) {
// To make volume labels consistent with Files app, we follow how Files
// generates a volume label when the volume doesn't have specific label.
// That is, we use the base name of mount path instead in such cases.
// TODO: b/255485048 - Share the implementation with Files app and Settings.
device_label =
base::FilePath(mount_info.mount_path).BaseName().AsUTF8Unsafe();
}
const bool visible = IsVisibleToAndroidApps(fs_uuid);
switch (event) {
case DiskMountManager::MountEvent::MOUNTING:
// Attach watcher to the directories. This is the best place to add the
// watcher, because if the watcher is attached after Android mounts (and
// performs full scan) the removable media, there might be a small time
// interval that has undetectable changes.
delegate_->StartWatchingRemovableMedia(
fs_uuid, mount_info.mount_path,
base::BindOnce(
&ArcVolumeMounterBridge::SendMountEventForRemovableMedia,
weak_ptr_factory_.GetWeakPtr(), event, mount_info.source_path,
mount_info.mount_path, fs_uuid, device_label, device_type,
visible));
break;
case DiskMountManager::MountEvent::UNMOUNTING:
// The actual ordering for the unmount event is not very important because
// during unmount, we don't care about accidentally ignoring changes.
// Hence, no synchronization is needed as we only care about cleaning up
// memory usage for watchers which is ok to be done at any time as long as
// it is done.
SendMountEventForRemovableMedia(event, mount_info.source_path,
mount_info.mount_path, fs_uuid,
device_label, device_type, visible);
delegate_->StopWatchingRemovableMedia(mount_info.mount_path);
break;
}
}
void ArcVolumeMounterBridge::SendMountEventForRemovableMedia(
DiskMountManager::MountEvent event,
const std::string& source_path,
const std::string& mount_path,
const std::string& fs_uuid,
const std::string& device_label,
ash::DeviceType device_type,
bool visible) {
mojom::VolumeMounterInstance* volume_mounter_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->volume_mounter(),
OnMountEvent);
if (!volume_mounter_instance)
return;
volume_mounter_instance->OnMountEvent(
mojom::MountPointInfo::New(event, source_path, mount_path, fs_uuid,
device_label, device_type, visible));
}
void ArcVolumeMounterBridge::OnConnectionClosed() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
external_storage_mount_points_are_ready_ = false;
}
void ArcVolumeMounterBridge::RequestAllMountPoints() {
// Deferring the SendAllMountEvents as a task to current thread to not
// block the mojo request since SendAllMountEvents might take non trivial
// amount of time.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&ArcVolumeMounterBridge::SendAllMountEvents,
weak_ptr_factory_.GetWeakPtr()));
}
bool ArcVolumeMounterBridge::IsReadyToSendMountingEvents() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(delegate_);
// Check whether external storage mount points are set up and file system
// watchers are watching file system changes. In ARC P, we can assume that the
// mount points are set up in an earlier boot stage, whereas in ARC R+ they
// need to be set up by SetUpExternalStorageMountPoints().
return (GetArcAndroidSdkVersionAsInt() < arc::kArcVersionR ||
external_storage_mount_points_are_ready_) &&
delegate_->IsWatchingFileSystemChanges();
}
void ArcVolumeMounterBridge::SetUpExternalStorageMountPoints(
uint32_t media_provider_uid,
SetUpExternalStorageMountPointsCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(GetArcAndroidSdkVersionAsInt() >= arc::kArcVersionR);
if (media_provider_uid < kAndroidAppUidStart ||
media_provider_uid > kAndroidAppUidEnd) {
LOG(ERROR) << "Invalid MediaProvider UID: " << media_provider_uid;
std::move(callback).Run(false);
return;
}
if (external_storage_mount_points_are_ready_) {
std::move(callback).Run(true);
return;
}
DVLOG(1) << "MediaProvider UID is " << media_provider_uid;
const bool is_arcvm = IsArcVmEnabled();
const std::string job_name = is_arcvm ? kArcVmMediaSharingServicesJobName
: kArcppMediaSharingServicesJobName;
const std::string chromeos_user = GetChromeOsUserId();
DCHECK(!chromeos_user.empty());
std::vector<std::string> environment{
"CHROMEOS_USER=" + chromeos_user,
base::StringPrintf("MEDIA_PROVIDER_UID=%u", media_provider_uid)};
if (!is_arcvm) {
// We need to explicitly tell R container to use MediaProvider UID.
environment.push_back("IS_ANDROID_CONTAINER_RVC=true");
}
// Post OnSetUpExternalStorageMountPoints() as a task on the current thread
// because it eventually calls ArcFileSystemWatcherService's methods to attach
// watchers that need to be called on the UI thread.
ash::UpstartClient::Get()->StartJobWithErrorDetails(
job_name, std::move(environment),
base::BindPostTask(
base::SingleThreadTaskRunner::GetCurrentDefault(),
base::BindOnce(
&ArcVolumeMounterBridge::OnSetUpExternalStorageMountPoints,
weak_ptr_factory_.GetWeakPtr(), job_name, std::move(callback))));
}
void ArcVolumeMounterBridge::OnSetUpExternalStorageMountPoints(
const std::string& job_name,
SetUpExternalStorageMountPointsCallback callback,
bool result,
std::optional<std::string> error_name,
std::optional<std::string> error_message) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!external_storage_mount_points_are_ready_);
if (!result) {
// Check if the job has already been running, in which case we treat the
// result as a success. It can happen when Android's system services are
// restarted without rebooting.
if (error_name.has_value() &&
error_name.value() == ash::UpstartClient::kAlreadyStartedError) {
DVLOG(1) << job_name << " is already running";
} else {
LOG(ERROR) << "Failed to start " << job_name << ": "
<< (error_name.has_value() ? error_name.value()
: "unknown error")
<< ": "
<< (error_message.has_value() ? error_message.value() : "");
std::move(callback).Run(false);
return;
}
}
external_storage_mount_points_are_ready_ = true;
std::move(callback).Run(true);
}
// static
void ArcVolumeMounterBridge::EnsureFactoryBuilt() {
ArcVolumeMounterBridgeFactory::GetInstance();
}
} // namespace arc