blob: ce3e12a764810bd931b53d4d2c400a7f9c3a62ec [file] [log] [blame]
// Copyright 2018 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 "cryptohome/storage/arc_disk_quota.h"
#include <optional>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <brillo/cryptohome.h>
#include "cryptohome/projectid_config.h"
namespace cryptohome {
namespace {
bool IsAndroidProjectId(int project_id) {
return (project_id >= kProjectIdForAndroidFilesStart &&
project_id <= kProjectIdForAndroidFilesEnd) ||
(project_id >= kProjectIdForAndroidAppsStart &&
project_id <= kProjectIdForAndroidAppsEnd);
}
} // namespace
ArcDiskQuota::ArcDiskQuota(HomeDirs* homedirs,
Platform* platform,
const base::FilePath& home)
: homedirs_(homedirs), platform_(platform), home_(home) {}
ArcDiskQuota::~ArcDiskQuota() = default;
void ArcDiskQuota::Initialize() {
device_ = GetDevice();
}
bool ArcDiskQuota::IsQuotaSupported() const {
base::FilePath device = GetDevice();
if (device.empty()) {
LOG(ERROR) << "No quota mount is found.";
return false;
}
int cnt = homedirs_->GetUnmountedAndroidDataCount();
if (cnt != 0) {
// Quota is not supported if there are one or more unmounted Android users.
// (b/181159107)
return false;
}
return true;
}
int64_t ArcDiskQuota::GetCurrentSpaceForUid(uid_t android_uid) const {
base::FilePath device = GetDevice();
if (android_uid < kAndroidUidStart || android_uid > kAndroidUidEnd) {
LOG(ERROR) << "Android uid " << android_uid
<< " is outside the allowed query range";
return -1;
}
if (device.empty()) {
LOG(ERROR) << "No quota mount is found";
return -1;
}
uid_t real_uid = android_uid + kArcContainerShiftUid;
int64_t current_space =
platform_->GetQuotaCurrentSpaceForUid(device, real_uid);
if (current_space == -1) {
PLOG(ERROR) << "Failed to get disk stats for uid: " << real_uid;
return -1;
}
return current_space;
}
int64_t ArcDiskQuota::GetCurrentSpaceForGid(gid_t android_gid) const {
base::FilePath device = GetDevice();
if (android_gid < kAndroidGidStart || android_gid > kAndroidGidEnd) {
LOG(ERROR) << "Android gid " << android_gid
<< " is outside the allowed query range";
return -1;
}
if (device.empty()) {
LOG(ERROR) << "No quota mount is found";
return -1;
}
gid_t real_gid = android_gid + kArcContainerShiftGid;
int64_t current_space =
platform_->GetQuotaCurrentSpaceForGid(device, real_gid);
if (current_space == -1) {
PLOG(ERROR) << "Failed to get disk stats for gid: " << real_gid;
return -1;
}
return current_space;
}
int64_t ArcDiskQuota::GetCurrentSpaceForProjectId(int project_id) const {
base::FilePath device = GetDevice();
if (!IsAndroidProjectId(project_id)) {
LOG(ERROR) << "Project id " << project_id
<< " is outside the allowed query range";
return -1;
}
if (device.empty()) {
LOG(ERROR) << "No quota mount is found";
return -1;
}
int64_t current_space =
platform_->GetQuotaCurrentSpaceForProjectId(device, project_id);
if (current_space == -1) {
PLOG(ERROR) << "Failed to get disk stats for project id: " << project_id;
return -1;
}
return current_space;
}
bool ArcDiskQuota::SetProjectId(int project_id,
SetProjectIdAllowedPathType parent_path,
const base::FilePath& child_path,
const std::string& obfuscated_username) const {
if (!IsAndroidProjectId(project_id)) {
LOG(ERROR) << "Project id " << project_id
<< " is outside the allowed query range";
return false;
}
if (child_path.ReferencesParent()) {
LOG(ERROR) << "child_path contains \"..\" : " << child_path;
return false;
}
if (child_path.IsAbsolute()) {
LOG(ERROR) << "child_path is an absolute path : " << child_path;
return false;
}
MountError mount_error;
if (!homedirs_->CryptohomeExists(obfuscated_username, &mount_error)) {
if (mount_error != MOUNT_ERROR_NONE) {
LOG(ERROR) << "Failed to check cryptohome existence for : "
<< obfuscated_username << " error = " << mount_error;
} else {
LOG(ERROR) << "A cryptohome vault doesn't exist for : "
<< obfuscated_username;
}
return false;
}
base::FilePath path;
switch (parent_path) {
case PATH_DOWNLOADS:
// /home/user/<obfuscated_username>/Downloads/<child_path>
path = brillo::cryptohome::home::GetUserPathPrefix()
.Append(obfuscated_username)
.Append(kUserDownloadsDir)
.Append(child_path);
break;
case PATH_ANDROID_DATA:
// /home/root/<obfuscated_username>/android-data/<child_path>
path = brillo::cryptohome::home::GetRootPathPrefix()
.Append(obfuscated_username)
.Append(kAndroidDataDir)
.Append(child_path);
break;
}
if (path.empty()) {
LOG(ERROR) << "Invalid parent path type : " << parent_path;
return false;
}
return platform_->SetQuotaProjectId(project_id, path);
}
bool ArcDiskQuota::SetMediaRWDataFileProjectId(int project_id,
int fd,
int* out_error) const {
if (!IsAndroidProjectId(project_id)) {
LOG(ERROR) << "Project id " << project_id
<< " is outside the allowed query range";
*out_error = EINVAL;
return false;
}
std::optional<std::string> context = platform_->GetSELinuxContextOfFD(fd);
if (!context) {
LOG(ERROR) << "Failed to get the SELinux context of FD.";
*out_error = EIO;
return false;
}
if (*context != kMediaRWDataFileSELinuxContext) {
LOG(ERROR) << "Unexpected SELinux context: " << *context;
*out_error = EPERM;
return false;
}
return platform_->SetQuotaProjectIdWithFd(project_id, fd, out_error);
}
bool ArcDiskQuota::SetMediaRWDataFileProjectInheritanceFlag(
bool enable, int fd, int* out_error) const {
std::optional<std::string> context = platform_->GetSELinuxContextOfFD(fd);
if (!context) {
LOG(ERROR) << "Failed to get the SELinux context of FD.";
*out_error = EIO;
return false;
}
if (*context != kMediaRWDataFileSELinuxContext) {
LOG(ERROR) << "Unexpected SELinux context: " << *context;
*out_error = EPERM;
return false;
}
return platform_->SetQuotaProjectInheritanceFlagWithFd(enable, fd, out_error);
}
base::FilePath ArcDiskQuota::GetDevice() const {
std::string device;
if (!platform_->FindFilesystemDevice(home_, &device)) {
LOG(ERROR) << "Home device is not found.";
return base::FilePath();
}
// Check if the device is mounted with quota option.
if (platform_->GetQuotaCurrentSpaceForUid(base::FilePath(device), 0) < 0) {
LOG(ERROR) << "Device is not mounted with quota feature enabled.";
return base::FilePath();
}
return base::FilePath(device);
}
} // namespace cryptohome