blob: 08b9d993e7fdf374ef082900a9a0f5cfa808618c [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::{
collections::HashSet,
path::{Path, PathBuf},
sync::Arc,
};
use super::helper::unsafe_quota::{set_quota_limited, set_quota_normal};
use crate::{
common::{CRYPTO_HOME, PRECOMPILED_CACHE_DIR},
shader_cache_mount::ShaderCacheMountMapPtr,
};
use dbus::nonblock::SyncConnection;
use libchromeos::sys::{debug, info, warn};
use anyhow::Result;
use system_api::spaced::{StatefulDiskSpaceState, StatefulDiskSpaceUpdate};
use tokio::sync::Mutex;
lazy_static! {
static ref PURGED: Mutex<bool> = Mutex::new(false);
static ref LIMITED_QUOTA_PATHS: Mutex<HashSet<PathBuf>> = Mutex::new(HashSet::new());
}
fn delete_all_files(path: &Path) -> Result<()> {
debug!("Cleaning up {}", path.display());
for dir_entry in (std::fs::read_dir(path)?).flatten() {
if dir_entry.path().is_dir() {
std::fs::remove_dir_all(dir_entry.path())?;
} else {
std::fs::remove_file(dir_entry.path())?;
}
}
Ok(())
}
fn get_all_precompiled_cache_dir() -> Result<Vec<PathBuf>> {
let mut dirs: Vec<PathBuf> = vec![];
for dir_entry in (std::fs::read_dir(CRYPTO_HOME)?).flatten() {
let user_cryptohome = dir_entry.path();
dirs.push(user_cryptohome.join(PRECOMPILED_CACHE_DIR))
}
Ok(dirs)
}
pub async fn delete_precompiled_cache(mount_map: ShaderCacheMountMapPtr) -> Result<()> {
// TODO(b/271776528): utilize ShaderCacheMount once it is reliable
// SoT for cryptohome and mounts. For now, just get the lock and
// call get_all_precompiled_cache_dir(). This has no runtime
// differences.
let _mount_map = mount_map.write().await;
for local_cache_dir in get_all_precompiled_cache_dir()? {
for dir_entry in (std::fs::read_dir(local_cache_dir)?).flatten() {
info!("Deleting all files at {}", dir_entry.path().display());
delete_all_files(&dir_entry.path())?;
}
}
Ok(())
}
pub async fn handle_disk_space_update(
raw_bytes: Vec<u8>,
mount_map: ShaderCacheMountMapPtr,
conn: Arc<SyncConnection>,
) -> Result<()> {
let update_signal: StatefulDiskSpaceUpdate = protobuf::Message::parse_from_bytes(&raw_bytes)
.map_err(|e| dbus::MethodErr::invalid_arg(&e))?;
debug!(
"Spaced status {:?}, free space bytes {}",
update_signal.get_state(),
update_signal.free_space_bytes
);
let mut is_purged = PURGED.lock().await;
let mut limited_quota_paths = LIMITED_QUOTA_PATHS.lock().await;
// Clean things up if low
// LOW = < 1%
if update_signal.get_state() == StatefulDiskSpaceState::LOW
|| update_signal.get_state() == StatefulDiskSpaceState::CRITICAL
{
if !*is_purged {
// In the first attempt, just delete the DLCs and see if disk space
// recovers.
info!("Low/critical disk space, removing all shader cache DLCs");
// Set is_purged early, so that we do the next step if DLC
// uninstallation fails.
*is_purged = true;
// Purge all shader cache DLCs, only once until recovery
super::unmount_and_uninstall_all_shader_cache_dlcs(mount_map.clone(), conn.clone())
.await?;
} else {
// spaced will continue sending signals if the disk stays near full.
// Clean up downloaded shader cache contents.
delete_precompiled_cache(mount_map.clone()).await?;
// TODO(b/271776528): Ditto as above
let _mount_map = mount_map.write().await;
for local_cache_dir in get_all_precompiled_cache_dir()? {
if !limited_quota_paths.contains(&local_cache_dir) {
if let Err(e) = set_quota_limited(&local_cache_dir) {
warn!(
"Failed to limit quota at {}: {}",
local_cache_dir.display(),
e
);
} else {
limited_quota_paths.insert(local_cache_dir);
}
}
}
}
} else if update_signal.get_state() == StatefulDiskSpaceState::NORMAL {
debug!("Normal disk space, recovering if required");
// TODO(b/271776528): Ditto as above
let _mount_map = mount_map.write().await;
if *is_purged {
*is_purged = false;
}
let mut failed = HashSet::new();
for local_cache_dir in limited_quota_paths.drain() {
if let Err(e) = set_quota_normal(&local_cache_dir) {
warn!(
"Failed to limit quota at {}: {}",
local_cache_dir.display(),
e
);
failed.insert(local_cache_dir);
}
}
limited_quota_paths.extend(failed);
}
Ok(())
}