| // 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 crate::{ | |
| common::{CRYPTO_HOME, PRECOMPILED_CACHE_DIR}, | |
| dbus_wrapper::DbusConnectionTrait, | |
| shader_cache_mount::{ShaderCacheMountMapPtr, VmId}, | |
| }; | |
| use log::{debug, info, warn}; | |
| use anyhow::Result; | |
| use system_api::spaced::{StatefulDiskSpaceState, StatefulDiskSpaceUpdate}; | |
| use tokio::sync::Mutex; | |
| #[cfg(test)] | |
| use super::helper::unsafe_ops::mock_quota::{set_quota_limited, set_quota_normal}; | |
| #[cfg(not(test))] | |
| use super::helper::unsafe_ops::quota::{set_quota_limited, set_quota_normal}; | |
| #[cfg(test)] | |
| lazy_static! { | |
| pub static ref PURGED: Mutex<bool> = Mutex::new(false); | |
| pub static ref LIMITED_QUOTA_PATHS: Mutex<HashSet<PathBuf>> = Mutex::new(HashSet::new()); | |
| } | |
| #[cfg(not(test))] | |
| 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<()> { | |
| 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.as_path())?).flatten() { | |
| let user_cryptohome = dir_entry.path(); | |
| dirs.push(user_cryptohome.join(PRECOMPILED_CACHE_DIR)) | |
| } | |
| Ok(dirs) | |
| } | |
| pub async fn delete_precompiled_cache_all( | |
| mount_map: ShaderCacheMountMapPtr, | |
| ) -> Result<Vec<PathBuf>> { | |
| let mut dirs_to_clear: Vec<PathBuf> = vec![]; | |
| // TODO(b/271776528): utilize ShaderCacheMount once it is reliable | |
| // SoT for cryptohome and mounts (even for VMs that are turned off). | |
| // 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() { | |
| // For each |local_cache_dir| (which is per user), there are | |
| // cache directories per VM. | |
| dirs_to_clear.push(dir_entry.path()); | |
| } | |
| } | |
| for local_cache_dir in &dirs_to_clear { | |
| info!("Deleting all files at {}", local_cache_dir.display()); | |
| delete_all_files(local_cache_dir)?; | |
| } | |
| Ok(dirs_to_clear) | |
| } | |
| pub async fn delete_precompiled_cache( | |
| mount_map: ShaderCacheMountMapPtr, | |
| vm_id: VmId, | |
| ) -> Result<Vec<PathBuf>> { | |
| let mut dirs_to_clear: Vec<PathBuf> = vec![]; | |
| // TODO(b/271776528): utilize ShaderCacheMount once it is reliable | |
| // SoT for cryptohome and mounts (even for VMs that are turned off). | |
| // 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; | |
| let encoded_vm_name = base64::encode_config(&vm_id.vm_name, base64::URL_SAFE); | |
| let path = CRYPTO_HOME | |
| .join(&vm_id.vm_owner_id) | |
| .join(PRECOMPILED_CACHE_DIR) | |
| .join(encoded_vm_name); | |
| if path.exists() { | |
| dirs_to_clear.push(path); | |
| } else { | |
| info!("No precompiled cache for {:?}", vm_id); | |
| return Ok(vec![]); | |
| } | |
| for local_cache_dir in &dirs_to_clear { | |
| info!("Deleting all files at {}", local_cache_dir.display()); | |
| delete_all_files(local_cache_dir)?; | |
| } | |
| Ok(dirs_to_clear) | |
| } | |
| pub async fn handle_disk_space_update<D: DbusConnectionTrait>( | |
| raw_bytes: Vec<u8>, | |
| mount_map: ShaderCacheMountMapPtr, | |
| dbus_conn: Arc<D>, | |
| ) -> 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.state, update_signal.free_space_bytes | |
| ); | |
| let state = update_signal | |
| .state | |
| .enum_value() | |
| .map_err(|e| dbus::MethodErr::invalid_arg(&e))?; | |
| 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 state == StatefulDiskSpaceState::LOW || 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(), | |
| dbus_conn.clone(), | |
| ) | |
| .await?; | |
| } else { | |
| // spaced will continue sending signals if the disk stays near full. | |
| // Clean up downloaded shader cache contents. | |
| delete_precompiled_cache_all(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 state == StatefulDiskSpaceState::NORMAL { | |
| debug!("Normal disk space, recovering if required"); | |
| 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(()) | |
| } |