mirror of
https://gitlab.steamos.cloud/holo/steamos-manager.git
synced 2025-07-12 17:32:21 -04:00
Add steamosctl.
Add a second binary which is a command-line utility to get and set the properties of the DBus api. Add commands to set wifi debug mode and backend.
This commit is contained in:
parent
c3011c93aa
commit
8788e99245
6 changed files with 327 additions and 30 deletions
215
src/lib.rs
Normal file
215
src/lib.rs
Normal file
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* Copyright © 2023 Collabora Ltd.
|
||||
* Copyright © 2024 Valve Software
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{info, warn};
|
||||
|
||||
mod cec;
|
||||
mod daemon;
|
||||
mod ds_inhibit;
|
||||
mod hardware;
|
||||
mod manager;
|
||||
mod power;
|
||||
mod process;
|
||||
mod proxy;
|
||||
mod root;
|
||||
mod sls;
|
||||
mod systemd;
|
||||
mod user;
|
||||
mod user_manager;
|
||||
mod wifi;
|
||||
|
||||
#[cfg(test)]
|
||||
mod testing;
|
||||
|
||||
pub use proxy::ManagerProxy;
|
||||
pub use root::daemon as RootDaemon;
|
||||
pub use user::daemon as UserDaemon;
|
||||
pub use wifi::{WifiBackend, WifiDebugMode, WifiPowerManagement};
|
||||
|
||||
const API_VERSION: u32 = 8;
|
||||
|
||||
pub trait Service
|
||||
where
|
||||
Self: Sized + Send,
|
||||
{
|
||||
const NAME: &'static str;
|
||||
|
||||
fn run(&mut self) -> impl Future<Output = Result<()>> + Send;
|
||||
|
||||
fn shutdown(&mut self) -> impl Future<Output = Result<()>> + Send {
|
||||
async { Ok(()) }
|
||||
}
|
||||
|
||||
fn start(mut self, token: CancellationToken) -> impl Future<Output = Result<()>> + Send {
|
||||
async move {
|
||||
info!("Starting {}", Self::NAME);
|
||||
let res = tokio::select! {
|
||||
r = self.run() => r,
|
||||
_ = token.cancelled() => Ok(()),
|
||||
};
|
||||
if res.is_err() {
|
||||
warn!(
|
||||
"{} encountered an error: {}",
|
||||
Self::NAME,
|
||||
res.as_ref().unwrap_err()
|
||||
);
|
||||
token.cancel();
|
||||
}
|
||||
info!("Shutting down {}", Self::NAME);
|
||||
self.shutdown().await.and(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub fn path<S: AsRef<str>>(path: S) -> PathBuf {
|
||||
PathBuf::from(path.as_ref())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn path<S: AsRef<str>>(path: S) -> PathBuf {
|
||||
let current_test = crate::testing::current();
|
||||
let test_path = current_test.path();
|
||||
PathBuf::from(test_path.as_os_str().to_str().unwrap())
|
||||
.join(path.as_ref().trim_start_matches('/'))
|
||||
}
|
||||
|
||||
pub async fn write_synced<P: AsRef<Path>>(path: P, bytes: &[u8]) -> Result<()> {
|
||||
let mut file = File::create(path.as_ref()).await?;
|
||||
file.write_all(bytes).await?;
|
||||
Ok(file.sync_data().await?)
|
||||
}
|
||||
|
||||
pub fn read_comm(pid: u32) -> Result<String> {
|
||||
let comm = std::fs::read_to_string(path(format!("/proc/{}/comm", pid)))?;
|
||||
Ok(comm.trim_end().to_string())
|
||||
}
|
||||
|
||||
pub fn get_appid(pid: u32) -> Result<Option<u64>> {
|
||||
let environ = std::fs::read_to_string(path(format!("/proc/{}/environ", pid)))?;
|
||||
for env_var in environ.split('\0') {
|
||||
let (key, value) = match env_var.split_once('=') {
|
||||
Some((k, v)) => (k, v),
|
||||
None => continue,
|
||||
};
|
||||
if key != "SteamGameId" {
|
||||
continue;
|
||||
}
|
||||
match value.parse() {
|
||||
Ok(appid) => return Ok(Some(appid)),
|
||||
Err(_) => break,
|
||||
};
|
||||
}
|
||||
|
||||
let stat = std::fs::read_to_string(path(format!("/proc/{}/stat", pid)))?;
|
||||
let stat = match stat.rsplit_once(") ") {
|
||||
Some((_, v)) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let ppid = match stat.split(' ').nth(1) {
|
||||
Some(ppid) => ppid,
|
||||
None => return Err(anyhow!("stat data invalid")),
|
||||
};
|
||||
let ppid: u32 = ppid.parse()?;
|
||||
if ppid > 1 {
|
||||
get_appid(ppid)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn reload() -> Result<()> {
|
||||
loop {
|
||||
let mut sighup = signal(SignalKind::hangup())?;
|
||||
sighup
|
||||
.recv()
|
||||
.await
|
||||
.ok_or(anyhow!("SIGHUP handler failed!"))?;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_zbus_fdo_error<S: ToString>(error: S) -> zbus::fdo::Error {
|
||||
zbus::fdo::Error::Failed(error.to_string())
|
||||
}
|
||||
|
||||
pub fn to_zbus_error<S: ToString>(error: S) -> zbus::Error {
|
||||
zbus::Error::Failure(error.to_string())
|
||||
}
|
||||
|
||||
pub fn zbus_to_zbus_fdo(error: zbus::Error) -> zbus::fdo::Error {
|
||||
match error {
|
||||
zbus::Error::FDO(error) => *error,
|
||||
error => zbus::fdo::Error::Failed(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::testing;
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
fn read_comm() {
|
||||
let h = testing::start();
|
||||
let path = h.test.path();
|
||||
fs::create_dir_all(path.join("proc/123456")).expect("create_dir_all");
|
||||
fs::write(path.join("proc/123456/comm"), "test\n").expect("write comm");
|
||||
|
||||
assert_eq!(crate::read_comm(123456).expect("read_comm"), "test");
|
||||
assert!(crate::read_comm(123457).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appid_environ() {
|
||||
let h = testing::start();
|
||||
let path = h.test.path();
|
||||
fs::create_dir_all(path.join("proc/123456")).expect("create_dir_all");
|
||||
fs::write(
|
||||
path.join("proc/123456/environ"),
|
||||
"A=B\0SteamGameId=98765\0C=D",
|
||||
)
|
||||
.expect("write environ");
|
||||
|
||||
assert_eq!(crate::get_appid(123456).expect("get_appid"), Some(98765));
|
||||
assert!(crate::get_appid(123457).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appid_parent_environ() {
|
||||
let h = testing::start();
|
||||
let path = h.test.path();
|
||||
fs::create_dir_all(path.join("proc/123456")).expect("create_dir_all");
|
||||
fs::write(
|
||||
path.join("proc/123456/environ"),
|
||||
"A=B\0SteamGameId=98765\0C=D",
|
||||
)
|
||||
.expect("write environ");
|
||||
fs::create_dir_all(path.join("proc/123457")).expect("create_dir_all");
|
||||
fs::write(path.join("proc/123457/environ"), "A=B\0C=D").expect("write environ");
|
||||
fs::write(path.join("proc/123457/stat"), "0 (comm) S 123456 ...").expect("write stat");
|
||||
|
||||
assert_eq!(crate::get_appid(123457).expect("get_appid"), Some(98765));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appid_missing() {
|
||||
let h = testing::start();
|
||||
let path = h.test.path();
|
||||
fs::create_dir_all(path.join("proc/123457")).expect("create_dir_all");
|
||||
fs::write(path.join("proc/123457/environ"), "A=B\0C=D").expect("write environ");
|
||||
fs::write(path.join("proc/123457/stat"), "0 (comm) S 1 ...").expect("write stat");
|
||||
|
||||
assert_eq!(crate::get_appid(123457).expect("get_appid"), None);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue