blob: 2b40a27790130708c559fce5a639866b0f8554ac [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.
// service module provides code entry path from D-BUS. Hence, functions here
// are naturally mapped to D-BUS methods.
// D-BUS methods and signals for other services (ex. concierge) are in
// individual modules.
mod concierge;
mod dlc;
pub mod helper;
pub mod signal;
mod spaced;
use crate::common::*;
use crate::shader_cache_mount::{ShaderCacheMount, ShaderCacheMountMapPtr, VmId};
use helper::unsafe_quota::set_quota_normal;
use anyhow::{anyhow, Result};
use dbus::{nonblock::SyncConnection, MethodErr};
use libchromeos::sys::{debug, warn};
use protobuf::Message;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use system_api::shadercached::{
InstallRequest, PrepareShaderCacheRequest, PrepareShaderCacheResponse, UninstallRequest,
UnmountRequest,
};
// Selectively expose service methods
pub use concierge::add_shader_cache_group_permission;
pub use concierge::handle_vm_stopped;
pub use dlc::handle_dlc_state_changed;
pub use spaced::handle_disk_space_update;
pub const DEFAULT_DBUS_TIMEOUT: Duration = Duration::from_secs(10);
pub async fn handle_install(
raw_bytes: Vec<u8>,
mount_map: ShaderCacheMountMapPtr,
conn: Arc<SyncConnection>,
) -> Result<()> {
let request: InstallRequest = protobuf::Message::parse_from_bytes(&raw_bytes)?;
// Populate mount path before installation to ensure mount happens
if request.mount {
debug!(
"Install and mount requested for game {}",
request.steam_app_id
);
let vm_id = VmId {
vm_name: request.vm_name,
vm_owner_id: request.vm_owner_id,
};
let mut mut_mount_map = mount_map.write().await;
// TODO(b/271776528): move the insert ShaderCacheMount to
// PrepareShaderCache method response once we support local RW shader
// cache in shadercached.
if !mut_mount_map.contains_key(&vm_id) {
let vm_gpu_cache_path =
Path::new(&concierge::get_vm_gpu_cache_path(&vm_id, conn.clone()).await?)
.to_path_buf();
mut_mount_map.insert(
vm_id.clone(),
ShaderCacheMount::new(vm_gpu_cache_path, &vm_id)?,
);
} else {
debug!("Reusing mount information for {:?}", vm_id);
}
let shader_cache_mount = mut_mount_map
.get_mut(&vm_id)
.ok_or_else(|| MethodErr::failed("Failed to get mount information"))?;
// Mesa cache path initialization must succeed before we enqueue mount
shader_cache_mount.initialize()?;
// Even if current application is mounted, re-install and re-mount
shader_cache_mount.enqueue_mount(request.steam_app_id);
}
dlc::install_shader_cache_dlc(request.steam_app_id, conn.clone()).await?;
Ok(())
}
pub async fn handle_uninstall(
raw_bytes: Vec<u8>,
mount_map: ShaderCacheMountMapPtr,
conn: Arc<SyncConnection>,
) -> Result<()> {
let request: UninstallRequest = protobuf::Message::parse_from_bytes(&raw_bytes)?;
// Instead of queueing unmount, we attempt to unmount directly here.
// Uninstall should only succeed if umounting succeeds immediately
// (ie. nothing is using this game's shader cache DLC).
dlc::unmount_dlc(request.steam_app_id, mount_map.clone()).await?;
mount_map
.wait_unmount_completed(Some(request.steam_app_id), UNMOUNTER_INTERVAL * 2)
.await?;
// TODO(b/270262568): Queue DLC uninstallations instead of waiting for
// unmounts.
dlc::uninstall_shader_cache_dlc(request.steam_app_id, conn.clone()).await?;
Ok(())
}
pub async fn unmount_and_uninstall_all_shader_cache_dlcs(
mount_map: ShaderCacheMountMapPtr,
conn: Arc<SyncConnection>,
) -> Result<()> {
mount_map.clear_all_mounts(None).await?;
mount_map
.wait_unmount_completed(None, UNMOUNTER_INTERVAL * 2)
.await?;
// TODO(b/270262568): Queue DLC uninstallations instead of waiting for
// unmounts. Ensure to disallow mounts during the period and allow mounts
// after completed.
dlc::uninstall_all_shader_cache_dlcs(conn.clone()).await?;
Ok(())
}
// TODO(b/270617399): Make Purge accept VmId in the request proto
pub async fn handle_purge(
mount_map: ShaderCacheMountMapPtr,
conn: Arc<SyncConnection>,
) -> Result<()> {
unmount_and_uninstall_all_shader_cache_dlcs(mount_map.clone(), conn.clone()).await?;
spaced::delete_precompiled_cache(mount_map.clone()).await?;
Ok(())
}
pub async fn handle_prepare_shader_cache(
raw_bytes: Vec<u8>,
_mount_map: ShaderCacheMountMapPtr,
) -> Result<std::vec::Vec<u8>> {
let request: PrepareShaderCacheRequest = protobuf::Message::parse_from_bytes(&raw_bytes)?;
let mut response = PrepareShaderCacheResponse::new();
if request.vm_name.is_empty() || request.vm_owner_id.is_empty() {
return Err(anyhow!("vm_name and vm_owner_id must be set"));
}
let vm_id = VmId {
vm_name: request.vm_name,
vm_owner_id: request.vm_owner_id,
};
debug!("Preparing shader cache for {:?}", vm_id);
// TODO(b/271776528): insert ShaderCacheMount here once we support local RW
// shader cache in shadercached.
let shader_cache_mount = ShaderCacheMount::new(PathBuf::new(), &vm_id)?;
let path = shader_cache_mount.local_precompiled_cache_path()?;
if !Path::new(&path).exists() {
std::fs::create_dir_all(&path)?;
}
if let Err(e) = set_quota_normal(&path) {
warn!("Failed to set quota for {}: {}", path, e);
}
response.set_precompiled_cache_path(path);
Ok(response.write_to_bytes()?)
}
pub async fn handle_unmount(raw_bytes: Vec<u8>, mount_map: ShaderCacheMountMapPtr) -> Result<()> {
let request: UnmountRequest = protobuf::Message::parse_from_bytes(&raw_bytes)?;
let vm_id = VmId {
vm_name: request.vm_name,
vm_owner_id: request.vm_owner_id,
};
let mut mount_map = mount_map.write().await;
if let Some(shader_cache_mount) = mount_map.get_mut(&vm_id) {
// If VM had mounted something before but nothing is currently mounted,
// unmount will be queued and unmount will succeed because there is
// nothing mounted.
// Shadercached will fire unmount signal in result, which can be
// picked up by `garcon <..> --unmount --wait` process and exit
// with success code.
// (note: --wait flag probably only needed for tasts and manual
// debugging).
shader_cache_mount.remove_game_from_db_list(request.steam_app_id)?;
} else {
return Err(anyhow!("VM had never mounted shader cache"));
}
Ok(())
}