// Copyright 2018 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/disk_space/arc_disk_space_bridge.h"
#include <map>
#include <utility>
#include <vector>
#include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "base/functional/bind.h"
#include "base/memory/singleton.h"
#include "chromeos/ash/components/dbus/spaced/spaced_client.h"
#include "chromeos/ash/components/dbus/userdataauth/userdataauth_client.h"
namespace arc {
namespace {
// Path to query disk space and disk quota for ARC.
constexpr char kArcDiskHome[] = "/home/chronos/user";
// Singleton factory for ArcDiskSpaceBridge.
class ArcDiskSpaceBridgeFactory
: public internal::ArcBrowserContextKeyedServiceFactoryBase<
ArcDiskSpaceBridge,
ArcDiskSpaceBridgeFactory> {
public:
// Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
static constexpr const char* kName = "ArcDiskSpaceBridgeFactory";
static ArcDiskSpaceBridgeFactory* GetInstance() {
return base::Singleton<ArcDiskSpaceBridgeFactory>::get();
}
private:
friend base::DefaultSingletonTraits<ArcDiskSpaceBridgeFactory>;
ArcDiskSpaceBridgeFactory() = default;
~ArcDiskSpaceBridgeFactory() override = default;
};
bool IsAndroidUid(uint32_t uid) {
const uint32_t android_uid_end = GetArcAndroidSdkVersionAsInt() < kArcVersionT
? kAndroidUidEndBeforeT
: kAndroidUidEndAfterT;
return kAndroidUidStart <= uid && uid <= android_uid_end;
}
bool IsAndroidGid(uint32_t gid) {
return kAndroidGidStart <= gid && gid <= kAndroidGidEnd;
}
bool IsAndroidProjectId(uint32_t project_id) {
const uint32_t project_id_for_android_apps_end =
GetArcAndroidSdkVersionAsInt() < kArcVersionT
? kProjectIdForAndroidAppsEndBeforeT
: kProjectIdForAndroidAppsEndAfterT;
return (project_id >= kProjectIdForAndroidFilesStart &&
project_id <= kProjectIdForAndroidFilesEnd) ||
(project_id >= kProjectIdForAndroidAppsStart &&
project_id <= project_id_for_android_apps_end);
}
void IsQuotaSupportedOnArcDiskHome(
ArcDiskSpaceBridge::IsQuotaSupportedCallback callback) {
ash::SpacedClient::Get()->IsQuotaSupported(
kArcDiskHome,
base::BindOnce(
[](ArcDiskSpaceBridge::IsQuotaSupportedCallback callback,
std::optional<bool> reply) {
LOG_IF(ERROR, !reply.has_value())
<< "Failed to retrieve result from IsQuotaSupported";
std::move(callback).Run(reply.value_or(false));
},
std::move(callback)));
}
bool ValidateIds(const std::vector<uint32_t>& ids,
base::RepeatingCallback<bool(uint32_t)> is_in_allowed_range,
std::string_view id_type) {
for (const uint32_t id : ids) {
if (!is_in_allowed_range.Run(id)) {
LOG(ERROR) << "Android " << id_type << " " << id
<< " is outside the allowed query range";
return false;
}
}
return true;
}
std::vector<uint32_t> GetVecWithShiftedIds(const std::vector<uint32_t>& ids,
uint32_t shift_width) {
std::vector<uint32_t> shifted_ids(ids.size());
std::transform(ids.begin(), ids.end(), shifted_ids.begin(),
[&shift_width](const auto& id) { return id + shift_width; });
return shifted_ids;
}
std::vector<int64_t> GetSpacesForIds(const std::map<uint32_t, int64_t>& map,
const std::vector<uint32_t>& ids,
std::string_view id_type) {
std::vector<int64_t> spaces;
for (uint32_t id : ids) {
auto iter = map.find(id);
if (iter == map.end()) {
LOG(ERROR) << "Space for " << id_type << " " << id << " is not found in "
<< "the map returned from spaced";
// Return an empty list if the result for any ID is missing.
return std::vector<int64_t>{};
}
spaces.push_back(iter->second);
}
return spaces;
}
} // namespace
// static
ArcDiskSpaceBridge* ArcDiskSpaceBridge::GetForBrowserContext(
content::BrowserContext* context) {
return ArcDiskSpaceBridgeFactory::GetForBrowserContext(context);
}
// static
ArcDiskSpaceBridge* ArcDiskSpaceBridge::GetForBrowserContextForTesting(
content::BrowserContext* context) {
return ArcDiskSpaceBridgeFactory::GetForBrowserContextForTesting(context);
}
ArcDiskSpaceBridge::ArcDiskSpaceBridge(content::BrowserContext* context,
ArcBridgeService* bridge_service)
: arc_bridge_service_(bridge_service) {
arc_bridge_service_->disk_space()->AddObserver(this);
arc_bridge_service_->disk_space()->SetHost(this);
}
ArcDiskSpaceBridge::~ArcDiskSpaceBridge() {
arc_bridge_service_->disk_space()->SetHost(nullptr);
arc_bridge_service_->disk_space()->RemoveObserver(this);
}
void ArcDiskSpaceBridge::IsQuotaSupported(IsQuotaSupportedCallback callback) {
// Whether ARC quota is supported is an AND of the following two booleans:
// * Whether there are no unmounted Android users (from cryptohome)
// * Whether |kArcDiskHome| is mounted with quota enabled (from spaced)
// Query cryptohome first, as the first one is more likely to be false.
ash::UserDataAuthClient::Get()->GetArcDiskFeatures(
user_data_auth::GetArcDiskFeaturesRequest(),
base::BindOnce(
[](IsQuotaSupportedCallback callback,
std::optional<user_data_auth::GetArcDiskFeaturesReply> reply) {
LOG_IF(ERROR, !reply.has_value())
<< "Failed to retrieve result from GetArcDiskFeatures call.";
if (!reply.has_value() || !reply->quota_supported()) {
std::move(callback).Run(false);
return;
}
IsQuotaSupportedOnArcDiskHome(std::move(callback));
},
std::move(callback)));
}
void ArcDiskSpaceBridge::GetQuotaCurrentSpaceForUid(
uint32_t android_uid,
GetQuotaCurrentSpaceForUidCallback callback) {
if (!IsAndroidUid(android_uid)) {
LOG(ERROR) << "Android uid " << android_uid
<< " is outside the allowed query range";
std::move(callback).Run(-1);
return;
}
const uint32_t cros_uid = android_uid + kArcUidShift;
ash::SpacedClient::Get()->GetQuotaCurrentSpaceForUid(
kArcDiskHome, cros_uid,
base::BindOnce(
[](GetQuotaCurrentSpaceForUidCallback callback, int cros_uid,
std::optional<int64_t> reply) {
LOG_IF(ERROR, !reply.has_value())
<< "Failed to retrieve result from GetQuotaCurrentSpaceForUid "
<< "for uid=" << cros_uid;
std::move(callback).Run(reply.value_or(-1));
},
std::move(callback), cros_uid));
}
void ArcDiskSpaceBridge::GetQuotaCurrentSpaceForGid(
uint32_t android_gid,
GetQuotaCurrentSpaceForGidCallback callback) {
if (!IsAndroidGid(android_gid)) {
LOG(ERROR) << "Android gid " << android_gid
<< " is outside the allowed query range";
std::move(callback).Run(-1);
return;
}
const uint32_t cros_gid = android_gid + kArcGidShift;
ash::SpacedClient::Get()->GetQuotaCurrentSpaceForGid(
kArcDiskHome, cros_gid,
base::BindOnce(
[](GetQuotaCurrentSpaceForGidCallback callback, int cros_gid,
std::optional<int64_t> reply) {
LOG_IF(ERROR, !reply.has_value())
<< "Failed to retrieve result from GetQuotaCurrentSpaceForGid "
<< "for gid=" << cros_gid;
std::move(callback).Run(reply.value_or(-1));
},
std::move(callback), cros_gid));
}
void ArcDiskSpaceBridge::GetQuotaCurrentSpaceForProjectId(
uint32_t project_id,
GetQuotaCurrentSpaceForProjectIdCallback callback) {
if (!IsAndroidProjectId(project_id)) {
LOG(ERROR) << "Android project id " << project_id
<< " is outside the allowed query range";
std::move(callback).Run(-1);
return;
}
ash::SpacedClient::Get()->GetQuotaCurrentSpaceForProjectId(
kArcDiskHome, project_id,
base::BindOnce(
[](GetQuotaCurrentSpaceForProjectIdCallback callback, int project_id,
std::optional<int64_t> reply) {
LOG_IF(ERROR, !reply.has_value())
<< "Failed to retrieve result from "
"GetQuotaCurrentSpaceForProjectId for project_id="
<< project_id;
std::move(callback).Run(reply.value_or(-1));
},
std::move(callback), project_id));
}
void ArcDiskSpaceBridge::GetQuotaCurrentSpacesForIds(
const std::vector<uint32_t>& android_uids,
const std::vector<uint32_t>& android_gids,
const std::vector<uint32_t>& android_project_ids,
GetQuotaCurrentSpacesForIdsCallback callback) {
if (!ValidateIds(android_uids, base::BindRepeating(&IsAndroidUid), "UID") ||
!ValidateIds(android_gids, base::BindRepeating(&IsAndroidGid), "GID") ||
!ValidateIds(android_project_ids,
base::BindRepeating(&IsAndroidProjectId), "project ID")) {
std::move(callback).Run(nullptr);
return;
}
const std::vector<uint32_t> cros_uids =
GetVecWithShiftedIds(android_uids, kArcUidShift);
const std::vector<uint32_t> cros_gids =
GetVecWithShiftedIds(android_gids, kArcGidShift);
ash::SpacedClient::Get()->GetQuotaCurrentSpacesForIds(
kArcDiskHome, cros_uids, cros_gids, android_project_ids,
base::BindOnce(
[](GetQuotaCurrentSpacesForIdsCallback callback,
const std::vector<uint32_t>& cros_uids,
const std::vector<uint32_t>& cros_gids,
const std::vector<uint32_t>& project_ids,
std::optional<ash::SpacedClient::SpaceMaps> result) {
if (!result.has_value()) {
LOG(ERROR) << "SpacedClient::GetQuotaCurrentSpacesForIds failed";
std::move(callback).Run(nullptr);
return;
}
mojom::QuotaSpacesPtr quota_spaces = mojom::QuotaSpaces::New();
quota_spaces->curspaces_for_uids =
GetSpacesForIds(result->curspaces_for_uids, cros_uids, "UID");
quota_spaces->curspaces_for_gids =
GetSpacesForIds(result->curspaces_for_gids, cros_gids, "GID");
quota_spaces->curspaces_for_project_ids = GetSpacesForIds(
result->curspaces_for_project_ids, project_ids, "project ID");
std::move(callback).Run(std::move(quota_spaces));
},
std::move(callback), cros_uids, cros_gids, android_project_ids));
}
void ArcDiskSpaceBridge::GetFreeDiskSpace(GetFreeDiskSpaceCallback callback) {
ash::SpacedClient::Get()->GetFreeDiskSpace(
kArcDiskHome,
base::BindOnce(
[](GetFreeDiskSpaceCallback callback, std::optional<int64_t> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "spaced::GetFreeDiskSpace failed";
std::move(callback).Run(nullptr);
return;
}
mojom::DiskSpacePtr disk_space = mojom::DiskSpace::New();
disk_space->space_in_bytes = reply.value();
std::move(callback).Run(std::move(disk_space));
},
std::move(callback)));
}
bool ArcDiskSpaceBridge::GetApplicationsSize(
GetApplicationsSizeCallback callback) {
auto* disk_space_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_bridge_service_->disk_space(), GetApplicationsSize);
if (!disk_space_instance) {
return false;
}
disk_space_instance->GetApplicationsSize(std::move(callback));
return true;
}
void ArcDiskSpaceBridge::OnConnectionReady() {
ash::SpacedClient::Get()->AddObserver(this);
ash::SpacedClient::Get()->GetFreeDiskSpace(
kArcDiskHome, base::BindOnce(&ArcDiskSpaceBridge::OnGetFreeDiskSpace,
weak_factory_.GetWeakPtr()));
}
void ArcDiskSpaceBridge::OnConnectionClosed() {
ash::SpacedClient::Get()->RemoveObserver(this);
}
void ArcDiskSpaceBridge::OnGetFreeDiskSpace(std::optional<int64_t> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to call GetFreeDiskSpace from ArcDiskSpaceBridge";
return;
}
SendResizeStorageBalloon(reply.value());
}
void ArcDiskSpaceBridge::OnSpaceUpdate(const SpaceEvent& event) {
SendResizeStorageBalloon(event.free_space_bytes());
}
void ArcDiskSpaceBridge::SendResizeStorageBalloon(int64_t free_space_bytes) {
auto* disk_space_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_bridge_service_->disk_space(), ResizeStorageBalloon);
if (!disk_space_instance) {
return;
}
disk_space_instance->ResizeStorageBalloon(std::max(
int64_t(free_space_bytes - kStorageBalloonFreeSpaceBufferSizeInBytes),
int64_t(0)));
}
// static
void ArcDiskSpaceBridge::EnsureFactoryBuilt() {
ArcDiskSpaceBridgeFactory::GetInstance();
}
} // namespace arc