blob: 77be61e2d5e0793e18708776cb35602ca86238a9 [file] [log] [blame]
// Copyright 2020 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/cleanup/disk_cleanup.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <base/logging.h>
#include <base/time/time.h>
#include <base/timer/elapsed_timer.h>
#include "cryptohome/cleanup/disk_cleanup_routines.h"
#include "cryptohome/cleanup/user_oldest_activity_timestamp_manager.h"
#include "cryptohome/cryptohome_metrics.h"
#include "cryptohome/filesystem_layout.h"
#include "cryptohome/platform.h"
#include "cryptohome/storage/homedirs.h"
namespace cryptohome {
DiskCleanup::DiskCleanup(Platform* platform,
HomeDirs* homedirs,
UserOldestActivityTimestampManager* timestamp_manager)
: platform_(platform),
homedirs_(homedirs),
timestamp_manager_(timestamp_manager),
routines_(std::make_unique<DiskCleanupRoutines>(homedirs_, platform_)) {}
std::optional<int64_t> DiskCleanup::AmountOfFreeDiskSpace() const {
int64_t free_space = platform_->AmountOfFreeDiskSpace(ShadowRoot());
if (free_space < 0) {
return std::nullopt;
} else {
return free_space;
}
}
DiskCleanup::FreeSpaceState DiskCleanup::GetFreeDiskSpaceState() const {
return GetFreeDiskSpaceState(AmountOfFreeDiskSpace());
}
DiskCleanup::FreeSpaceState DiskCleanup::GetFreeDiskSpaceState(
std::optional<int64_t> free_disk_space) const {
if (!free_disk_space) {
return DiskCleanup::FreeSpaceState::kError;
}
int64_t value = free_disk_space.value();
if (value >= target_free_space_) {
return DiskCleanup::FreeSpaceState::kAboveTarget;
} else if (value >= normal_cleanup_threshold_) {
return DiskCleanup::FreeSpaceState::kAboveThreshold;
} else if (value >= aggressive_cleanup_threshold_) {
return DiskCleanup::FreeSpaceState::kNeedNormalCleanup;
} else if (value >= critical_cleanup_threshold_) {
return DiskCleanup::FreeSpaceState::kNeedAggressiveCleanup;
} else {
return DiskCleanup::FreeSpaceState::kNeedCriticalCleanup;
}
}
bool DiskCleanup::HasTargetFreeSpace() const {
return GetFreeDiskSpaceState() == DiskCleanup::FreeSpaceState::kAboveTarget;
}
bool DiskCleanup::IsFreeableDiskSpaceAvailable() {
if (!homedirs_->enterprise_owned())
return false;
const auto homedirs = homedirs_->GetHomeDirs();
int unmounted_cryptohomes =
std::count_if(homedirs.begin(), homedirs.end(),
[](auto& dir) { return !dir.is_mounted; });
return unmounted_cryptohomes > 0;
}
bool DiskCleanup::FreeDiskSpace() {
auto free_space = AmountOfFreeDiskSpace();
switch (GetFreeDiskSpaceState(free_space)) {
case DiskCleanup::FreeSpaceState::kAboveTarget:
case DiskCleanup::FreeSpaceState::kAboveThreshold:
// Already have enough space. No need to clean up.
VLOG(1) << "Skipping cleanup with " << *free_space << " space available";
ReportDiskCleanupResult(DiskCleanupResult::kDiskCleanupSkip);
return true;
case DiskCleanup::FreeSpaceState::kNeedNormalCleanup:
case DiskCleanup::FreeSpaceState::kNeedAggressiveCleanup:
case DiskCleanup::FreeSpaceState::kNeedCriticalCleanup:
// Trigger cleanup.
VLOG(1) << "Starting cleanup with " << *free_space << " space available";
break;
case DiskCleanup::FreeSpaceState::kError:
LOG(ERROR) << "Failed to get the amount of free disk space";
return false;
}
auto now = platform_->GetCurrentTime();
if (last_free_disk_space_) {
auto diff = now - *last_free_disk_space_;
ReportTimeBetweenFreeDiskSpace(diff.InSeconds());
}
last_free_disk_space_ = now;
base::ElapsedTimer total_timer;
bool result = FreeDiskSpaceInternal();
if (result) {
ReportDiskCleanupResult(DiskCleanupResult::kDiskCleanupSuccess);
} else {
ReportDiskCleanupResult(DiskCleanupResult::kDiskCleanupError);
}
int cleanup_time = total_timer.Elapsed().InMilliseconds();
ReportFreeDiskSpaceTotalTime(cleanup_time);
VLOG(1) << "Disk cleanup took " << cleanup_time << "ms.";
auto after_cleanup = AmountOfFreeDiskSpace();
if (!after_cleanup) {
LOG(ERROR) << "Failed to get the amount of free disk space";
return false;
}
auto cleaned_in_mb = std::max(static_cast<int64_t>(0),
after_cleanup.value() - free_space.value()) /
1024 / 1024;
ReportFreeDiskSpaceTotalFreedInMb(cleaned_in_mb);
VLOG(1) << "Disk cleanup cleared " << cleaned_in_mb << "MB.";
LOG(INFO) << "Disk cleanup complete.";
return result;
}
bool DiskCleanup::FreeDiskSpaceDuringLogin(const std::string& obfuscated) {
base::ElapsedTimer total_timer;
// Only runs for enterprise users.
if (!homedirs_->enterprise_owned()) {
VLOG(1) << "Login cleanup skipped on a consumer device";
return true;
}
// Only run if enabled by policy.
if (!homedirs_->MustRunAutomaticCleanupOnLogin()) {
VLOG(1) << "Login cleanup not enabled by policy";
return true;
}
auto free_space = AmountOfFreeDiskSpace();
switch (GetFreeDiskSpaceState(free_space)) {
case DiskCleanup::FreeSpaceState::kAboveTarget:
case DiskCleanup::FreeSpaceState::kAboveThreshold:
case DiskCleanup::FreeSpaceState::kNeedNormalCleanup:
case DiskCleanup::FreeSpaceState::kNeedAggressiveCleanup:
// Already have enough space. No need to clean up.
VLOG(1) << "Skipping login cleanup with " << *free_space
<< " space available";
ReportLoginDiskCleanupResult(DiskCleanupResult::kDiskCleanupSkip);
return true;
case DiskCleanup::FreeSpaceState::kNeedCriticalCleanup:
// Trigger cleanup.
break;
case DiskCleanup::FreeSpaceState::kError:
LOG(ERROR) << "Failed to get the amount of free disk space";
return false;
}
LOG(WARNING) << "Starting login cleanup with " << *free_space
<< " space available for " << obfuscated;
bool result = FreeDiskSpaceDuringLoginInternal(obfuscated);
if (result) {
ReportLoginDiskCleanupResult(DiskCleanupResult::kDiskCleanupSuccess);
} else {
ReportLoginDiskCleanupResult(DiskCleanupResult::kDiskCleanupError);
}
int cleanup_time = total_timer.Elapsed().InMilliseconds();
ReportLoginDiskCleanupTotalTime(cleanup_time);
VLOG(1) << "Login disk cleanup took " << cleanup_time << "ms.";
auto after_cleanup = AmountOfFreeDiskSpace();
if (!after_cleanup) {
LOG(ERROR) << "Failed to get the amount of free disk space";
return false;
}
auto cleaned_in_mb = std::max(static_cast<int64_t>(0),
after_cleanup.value() - free_space.value()) /
1024 / 1024;
VLOG(1) << "Login disk cleanup cleared " << cleaned_in_mb << "MB.";
LOG(INFO) << "Login disk cleanup complete.";
return result;
}
void DiskCleanup::set_routines_for_testing(DiskCleanupRoutines* routines) {
routines_.reset(routines);
}
bool DiskCleanup::FreeDiskSpaceInternal() {
// If ephemeral users are enabled, remove all cryptohomes except those
// currently mounted or belonging to the owner.
// |AreEphemeralUsers| will reload the policy to guarantee freshness.
if (homedirs_->AreEphemeralUsersEnabled()) {
homedirs_->RemoveNonOwnerCryptohomes();
ReportDiskCleanupProgress(
DiskCleanupProgress::kEphemeralUserProfilesCleaned);
return true;
}
auto homedirs = homedirs_->GetHomeDirs();
auto unmounted_homedirs = homedirs;
FilterMountedHomedirs(&unmounted_homedirs);
std::sort(
unmounted_homedirs.begin(), unmounted_homedirs.end(),
[&](const HomeDirs::HomeDir& a, const HomeDirs::HomeDir& b) {
return timestamp_manager_->GetLastUserActivityTimestamp(a.obfuscated) >
timestamp_manager_->GetLastUserActivityTimestamp(b.obfuscated);
});
auto normal_cleanup_homedirs = unmounted_homedirs;
if (last_normal_disk_cleanup_complete_) {
base::Time cutoff = last_normal_disk_cleanup_complete_.value();
FilterHomedirsProcessedBeforeCutoff(cutoff, &normal_cleanup_homedirs);
}
bool result = true;
// Clean Cache directories for every unmounted user that has logged out after
// the last normal cleanup happened.
for (auto dir = normal_cleanup_homedirs.rbegin();
dir != normal_cleanup_homedirs.rend(); dir++) {
if (!routines_->DeleteUserCache(dir->obfuscated))
result = false;
if (HasTargetFreeSpace()) {
ReportDiskCleanupProgress(
DiskCleanupProgress::kBrowserCacheCleanedAboveTarget);
return result;
}
}
auto free_disk_space = AmountOfFreeDiskSpace();
if (!free_disk_space) {
LOG(ERROR) << "Failed to get the amount of free space";
return false;
}
// Clean GCache directories for every unmounted user that has logged out after
// after the last normal cleanup happened.
for (auto dir = normal_cleanup_homedirs.rbegin();
dir != normal_cleanup_homedirs.rend(); dir++) {
if (!routines_->DeleteUserGCache(dir->obfuscated))
result = false;
if (HasTargetFreeSpace()) {
ReportDiskCleanupProgress(
DiskCleanupProgress::kGoogleDriveCacheCleanedAboveTarget);
return result;
}
}
auto old_free_disk_space = free_disk_space;
free_disk_space = AmountOfFreeDiskSpace();
if (!free_disk_space) {
LOG(ERROR) << "Failed to get the amount of free space";
return false;
}
const int64_t freed_gcache_space =
free_disk_space.value() - old_free_disk_space.value();
// Report only if something was deleted.
if (freed_gcache_space > 0) {
ReportFreedGCacheDiskSpaceInMb(freed_gcache_space / 1024 / 1024);
}
free_disk_space = AmountOfFreeDiskSpace();
if (!free_disk_space) {
LOG(ERROR) << "Failed to get the amount of free space";
return false;
}
bool early_stop = false;
// Purge Dmcrypt cache vaults.
for (auto dir = normal_cleanup_homedirs.rbegin();
dir != normal_cleanup_homedirs.rend(); dir++) {
if (!routines_->DeleteCacheVault(dir->obfuscated))
result = false;
if (HasTargetFreeSpace()) {
early_stop = true;
break;
}
}
old_free_disk_space = free_disk_space;
free_disk_space = AmountOfFreeDiskSpace();
if (!free_disk_space) {
LOG(ERROR) << "Failed to get the amount of free space";
return false;
}
const int64_t freed_vault_cache_space =
free_disk_space.value() - old_free_disk_space.value();
// Report only if something was deleted.
if (freed_gcache_space > 0) {
ReportFreedCacheVaultDiskSpaceInMb(freed_vault_cache_space / 1024 / 1024);
}
if (!early_stop)
last_normal_disk_cleanup_complete_ = platform_->GetCurrentTime();
switch (GetFreeDiskSpaceState(free_disk_space)) {
case DiskCleanup::FreeSpaceState::kAboveTarget:
ReportDiskCleanupProgress(
DiskCleanupProgress::kCacheVaultsCleanedAboveTarget);
return result;
case DiskCleanup::FreeSpaceState::kAboveThreshold:
case DiskCleanup::FreeSpaceState::kNeedNormalCleanup:
ReportDiskCleanupProgress(
DiskCleanupProgress::kCacheVaultsCleanedAboveMinimum);
return result;
case DiskCleanup::FreeSpaceState::kNeedAggressiveCleanup:
case DiskCleanup::FreeSpaceState::kNeedCriticalCleanup:
// continue cleanup
break;
case DiskCleanup::FreeSpaceState::kError:
LOG(ERROR) << "Failed to get the amount of free space";
return false;
}
auto aggressive_cleanup_homedirs = unmounted_homedirs;
if (last_aggressive_disk_cleanup_complete_) {
base::Time cutoff = last_aggressive_disk_cleanup_complete_.value();
FilterHomedirsProcessedBeforeCutoff(cutoff, &aggressive_cleanup_homedirs);
}
// Clean Android cache directories for every unmounted user that has logged
// out after after the last normal cleanup happened.
for (auto dir = aggressive_cleanup_homedirs.rbegin();
dir != aggressive_cleanup_homedirs.rend(); dir++) {
if (!routines_->DeleteUserAndroidCache(dir->obfuscated))
result = false;
if (HasTargetFreeSpace()) {
early_stop = true;
break;
}
}
if (!early_stop)
last_aggressive_disk_cleanup_complete_ = platform_->GetCurrentTime();
switch (GetFreeDiskSpaceState()) {
case DiskCleanup::FreeSpaceState::kAboveTarget:
ReportDiskCleanupProgress(
DiskCleanupProgress::kAndroidCacheCleanedAboveTarget);
return result;
case DiskCleanup::FreeSpaceState::kAboveThreshold:
case DiskCleanup::FreeSpaceState::kNeedNormalCleanup:
ReportDiskCleanupProgress(
DiskCleanupProgress::kAndroidCacheCleanedAboveMinimum);
return result;
case DiskCleanup::FreeSpaceState::kNeedAggressiveCleanup:
case DiskCleanup::FreeSpaceState::kNeedCriticalCleanup:
// continue cleanup
break;
case DiskCleanup::FreeSpaceState::kError:
LOG(ERROR) << "Failed to get the amount of free space";
return false;
}
// Delete old users, the oldest first. Count how many are deleted.
// Don't delete anyone if we don't know who the owner is.
// For consumer devices, don't delete the device owner. Enterprise-enrolled
// devices have no owner, so don't delete the most-recent user.
int deleted_users_count = 0;
std::string owner;
if (!homedirs_->enterprise_owned() && !homedirs_->GetOwner(&owner))
return result;
int mounted_cryptohomes_count =
std::count_if(homedirs.begin(), homedirs.end(),
[](auto& dir) { return dir.is_mounted; });
for (auto dir = unmounted_homedirs.rbegin(); dir != unmounted_homedirs.rend();
dir++) {
if (homedirs_->enterprise_owned()) {
// Leave the most-recent user on the device intact.
// The most-recent user is the first in unmounted_homedirs.
if (dir == unmounted_homedirs.rend() - 1 &&
mounted_cryptohomes_count == 0) {
LOG(INFO) << "Skipped deletion of the most recent device user.";
continue;
}
} else if (dir->obfuscated == owner) {
// We never delete the device owner.
LOG(INFO) << "Skipped deletion of the device owner.";
continue;
}
auto before_cleanup = AmountOfFreeDiskSpace();
if (!before_cleanup) {
LOG(ERROR) << "Failed to get the amount of free space";
return false;
}
LOG(INFO) << "Freeing disk space by deleting user " << dir->obfuscated;
if (!routines_->DeleteUserProfile(dir->obfuscated))
result = false;
timestamp_manager_->RemoveUser(dir->obfuscated);
++deleted_users_count;
auto after_cleanup = AmountOfFreeDiskSpace();
if (!after_cleanup) {
LOG(ERROR) << "Failed to get the amount of free space";
return false;
}
auto cleaned_in_mb =
std::max(static_cast<int64_t>(0),
after_cleanup.value() - before_cleanup.value()) /
1024 / 1024;
LOG(INFO) << "Removing user " << dir->obfuscated << " freed "
<< cleaned_in_mb << " MiB";
if (HasTargetFreeSpace())
break;
}
if (deleted_users_count > 0) {
ReportDeletedUserProfiles(deleted_users_count);
}
// We had a chance to delete a user only if any unmounted homes existed.
if (unmounted_homedirs.size() > 0) {
ReportDiskCleanupProgress(
HasTargetFreeSpace()
? DiskCleanupProgress::kWholeUserProfilesCleanedAboveTarget
: DiskCleanupProgress::kWholeUserProfilesCleaned);
} else {
ReportDiskCleanupProgress(DiskCleanupProgress::kNoUnmountedCryptohomes);
}
return result;
}
bool DiskCleanup::FreeDiskSpaceDuringLoginInternal(
const std::string& logging_in) {
auto unmounted_homedirs = homedirs_->GetHomeDirs();
FilterMountedHomedirs(&unmounted_homedirs);
std::sort(
unmounted_homedirs.begin(), unmounted_homedirs.end(),
[&](const HomeDirs::HomeDir& a, const HomeDirs::HomeDir& b) {
return timestamp_manager_->GetLastUserActivityTimestamp(a.obfuscated) >
timestamp_manager_->GetLastUserActivityTimestamp(b.obfuscated);
});
bool result = true;
for (auto dir = unmounted_homedirs.rbegin(); dir != unmounted_homedirs.rend();
dir++) {
if (dir->obfuscated == logging_in) {
LOG(INFO) << "Skipped deletion of the user logging in.";
continue;
}
LOG(INFO) << "Freeing disk space by deleting user " << dir->obfuscated;
if (!routines_->DeleteUserProfile(dir->obfuscated))
result = false;
timestamp_manager_->RemoveUser(dir->obfuscated);
// Login cleanup stops at kAboveThreshold.
auto state = GetFreeDiskSpaceState();
if (state == DiskCleanup::FreeSpaceState::kAboveThreshold ||
state == DiskCleanup::FreeSpaceState::kAboveTarget) {
break;
}
}
return result;
}
void DiskCleanup::FilterMountedHomedirs(
std::vector<HomeDirs::HomeDir>* homedirs) {
homedirs->erase(std::remove_if(homedirs->begin(), homedirs->end(),
[](const HomeDirs::HomeDir& dir) {
return dir.is_mounted;
}),
homedirs->end());
}
void DiskCleanup::FilterHomedirsProcessedBeforeCutoff(
base::Time cutoff, std::vector<HomeDirs::HomeDir>* homedirs) {
homedirs->erase(
std::remove_if(homedirs->begin(), homedirs->end(),
[&](const HomeDirs::HomeDir& dir) {
return timestamp_manager_->GetLastUserActivityTimestamp(
dir.obfuscated) < cutoff;
}),
homedirs->end());
}
} // namespace cryptohome