ProcessManager: Add a ProcessManager so we can cancel/pause/resume.

In order to be able to pause/resume/cancel some operations
that could take some time we need to keep track of those processes
and give callers an id to pause/resume/cancel them with later.

In these long running cases, return an object path that can be used
to pause/resume/cancel when told to.

TODO:
- Add some tests and manually test that the right things happen
This commit is contained in:
Jeremy Whiting 2024-04-09 12:56:09 -06:00
parent f6a50b8970
commit c49426f6af
4 changed files with 207 additions and 38 deletions

View file

@ -14,8 +14,8 @@ strip="symbols"
anyhow = "1" anyhow = "1"
clap = { version = "4.5", default-features = false, features = ["derive", "help", "std", "usage"] } clap = { version = "4.5", default-features = false, features = ["derive", "help", "std", "usage"] }
inotify = { version = "0.10", default-features = false, features = ["stream"] } inotify = { version = "0.10", default-features = false, features = ["stream"] }
nix = { version = "0.28", default-features = false, features = ["fs"] } nix = { version = "0.28", default-features = false, features = ["fs", "signal"] }
tokio = { version = "1", default-features = false, features = ["fs", "io-util", "macros", "rt-multi-thread", "signal", "sync"] } tokio = { version = "1", default-features = false, features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "signal", "sync"] }
tokio-stream = { version = "0.1", default-features = false } tokio-stream = { version = "0.1", default-features = false }
tokio-util = { version = "0.7", default-features = false } tokio-util = { version = "0.7", default-features = false }
tracing = { version = "0.1", default-features = false } tracing = { version = "0.1", default-features = false }

View file

@ -104,19 +104,27 @@
UpdateBios: UpdateBios:
Perform a BIOS update. Perform a BIOS update.
@objectpath: An object path that can be used to pause/resume/cancel/kill the operation
Version available: 7 Version available: 8
--> -->
<method name="UpdateBios" /> <method name="UpdateBios">
<arg type="o" name="objectpath" direction="out"/>
</method>
<!-- <!--
UpdateDock: UpdateDock:
Perform a Dock Firmware update. Perform a Dock Firmware update.
Version available: 7 @objectpath: An object path that can be used to pause/resume/cancel/kill the operation
Version available: 8
--> -->
<method name="UpdateDock" /> <method name="UpdateDock">
<arg type="o" name="objectpath" direction="out"/>
</method>
<!-- <!--
TrimDevices: TrimDevices:
@ -125,9 +133,13 @@
is important as some devices are not safe to trim unless some kernel is important as some devices are not safe to trim unless some kernel
quirks are available. quirks are available.
Version available: 7 @objectpath: An object path that can be used to pause/resume/cancel/kill the operation
Version available: 8
--> -->
<method name="TrimDevices" /> <method name="TrimDevices">
<arg type="o" name="objectpath" direction="out"/>
</method>
<!-- <!--
FormatDevice: FormatDevice:
@ -135,15 +147,17 @@
@device: Which device to format, e.g. /dev/mmcblk0 @device: Which device to format, e.g. /dev/mmcblk0
@label: Filesystem label to assign to the formatted device @label: Filesystem label to assign to the formatted device
@validate: When set runs common checks for conterfeit flash media before formatting, e.g. f3probe @validate: When set runs common checks for conterfeit flash media before formatting, e.g. f3probe
@objectpath: An object path that can be used to pause/resume/cancel/kill the operation
Format and optionally validate a storage device to a steam compatible filesystem. Format and optionally validate a storage device to a steam compatible filesystem.
Version available: 7 Version available: 8
--> -->
<method name="FormatDevice"> <method name="FormatDevice">
<arg type="s" name="device" direction="in"/> <arg type="s" name="device" direction="in"/>
<arg type="s" name="label" direction="in"/> <arg type="s" name="label" direction="in"/>
<arg type="b" name="validate" direction="in"/> <arg type="b" name="validate" direction="in"/>
<arg type="o" name="objectpath" direction="out"/>
</method> </method>
<!-- <!--
@ -265,4 +279,45 @@
</interface> </interface>
<!--
com.steampowered.SteamOSManager1.Process
@short_description: Interface to control a subprocess
Version available: 8
-->
<interface name="com.steampowered.SteamOSManager1.Process">
<!--
Pause the operation
-->
<method name="Pause"/>
<!--
Resume the operation
-->
<method name="Resume"/>
<!--
Cancel the operation
Note this sends a SIGTERM to the subprocess, vs Kill which sends SIGKILL.
-->
<method name="Cancel"/>
<!--
Kill the operation
Note this sends a SIGKILL, vs Cancel which sends SIGTERM.
-->
<method name="Kill"/>
<!--
Get the exit code of the process after waiting for it to finish if needed.
@exit_code The exit code
-->
<method name="ExitCode">
<arg type="i" name="exit_code" direction="out"/>
</method>
</interface>
</node> </node>

View file

@ -17,7 +17,7 @@ use crate::power::{
get_gpu_clocks, get_gpu_performance_level, get_tdp_limit, set_gpu_clocks, get_gpu_clocks, get_gpu_performance_level, get_tdp_limit, set_gpu_clocks,
set_gpu_performance_level, set_tdp_limit, GPUPerformanceLevel, set_gpu_performance_level, set_tdp_limit, GPUPerformanceLevel,
}; };
use crate::process::{run_script, script_output}; use crate::process::{run_script, script_output, ProcessManager};
use crate::wifi::{ use crate::wifi::{
get_wifi_backend, get_wifi_power_management_state, set_wifi_backend, set_wifi_debug_mode, get_wifi_backend, get_wifi_power_management_state, set_wifi_backend, set_wifi_debug_mode,
set_wifi_power_management_state, WifiBackend, WifiDebugMode, WifiPowerManagement, set_wifi_power_management_state, WifiBackend, WifiDebugMode, WifiPowerManagement,
@ -38,6 +38,8 @@ pub struct SteamOSManager {
// Whether we should use trace-cmd or not. // Whether we should use trace-cmd or not.
// True on galileo devices, false otherwise // True on galileo devices, false otherwise
should_trace: bool, should_trace: bool,
// Used by ProcessManager but need to only have one of these
next_process: u32,
} }
impl SteamOSManager { impl SteamOSManager {
@ -47,6 +49,7 @@ impl SteamOSManager {
connection, connection,
wifi_debug_mode: WifiDebugMode::Off, wifi_debug_mode: WifiDebugMode::Off,
should_trace: variant().await? == HardwareVariant::Galileo, should_trace: variant().await? == HardwareVariant::Galileo,
next_process: 0,
}) })
} }
} }
@ -142,47 +145,60 @@ impl SteamOSManager {
} }
} }
async fn update_bios(&self) -> zbus::fdo::Result<()> { async fn update_bios(&mut self) -> zbus::fdo::Result<zbus::zvariant::OwnedObjectPath> {
// Update the bios as needed // Update the bios as needed
run_script("/usr/bin/jupiter-biosupdate", &["--auto"]) ProcessManager::get_command_object_path(
.await "/usr/bin/jupiter-biosupdate",
.inspect_err(|message| error!("Error updating BIOS: {message}")) &["--auto"],
.map_err(to_zbus_fdo_error) &mut self.connection,
} &mut self.next_process,
"updating BIOS",
async fn update_dock(&self) -> zbus::fdo::Result<()> {
// Update the dock firmware as needed
run_script(
"/usr/lib/jupiter-dock-updater/jupiter-dock-updater.sh",
&[] as &[String; 0],
) )
.await .await
.inspect_err(|message| error!("Error updating dock: {message}"))
.map_err(to_zbus_fdo_error)
} }
async fn trim_devices(&self) -> zbus::fdo::Result<()> { async fn update_dock(&mut self) -> zbus::fdo::Result<zbus::zvariant::OwnedObjectPath> {
// Update the dock firmware as needed
ProcessManager::get_command_object_path(
"/usr/lib/jupiter-dock-updater/jupiter-dock-updater.sh",
&[] as &[String; 0],
&mut self.connection,
&mut self.next_process,
"updating dock",
)
.await
}
async fn trim_devices(&mut self) -> zbus::fdo::Result<zbus::zvariant::OwnedObjectPath> {
// Run steamos-trim-devices script // Run steamos-trim-devices script
run_script("/usr/lib/hwsupport/trim-devices.sh", &[] as &[String; 0]) ProcessManager::get_command_object_path(
.await "/usr/lib/hwsupport/trim-devices.sh",
.inspect_err(|message| error!("Error updating trimming devices: {message}")) &[] as &[String; 0],
.map_err(to_zbus_fdo_error) &mut self.connection,
&mut self.next_process,
"trimming devices",
)
.await
} }
async fn format_device( async fn format_device(
&self, &mut self,
device: &str, device: &str,
label: &str, label: &str,
validate: bool, validate: bool,
) -> zbus::fdo::Result<()> { ) -> zbus::fdo::Result<zbus::zvariant::OwnedObjectPath> {
let mut args = vec!["--label", label, "--device", device]; let mut args = vec!["--label", label, "--device", device];
if !validate { if !validate {
args.push("--skip-validation"); args.push("--skip-validation");
} }
run_script("/usr/lib/hwsupport/format-device.sh", args.as_ref()) ProcessManager::get_command_object_path(
.await "/usr/lib/hwsupport/format-device.sh",
.inspect_err(|message| error!("Error formatting {device}: {message}")) args.as_ref(),
.map_err(to_zbus_fdo_error) &mut self.connection,
&mut self.next_process,
format!("formatting {device}").as_str(),
)
.await
} }
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property(emits_changed_signal = "false"))]

View file

@ -5,10 +5,108 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
use anyhow::{anyhow, Result}; use anyhow::{anyhow, bail, Result};
use nix::sys::signal;
use nix::sys::signal::Signal;
use nix::unistd::Pid;
use std::ffi::OsStr; use std::ffi::OsStr;
#[cfg(not(test))] use tokio::process::{Child, Command};
use tokio::process::Command; use tracing::error;
use zbus::interface;
use crate::{to_zbus_fdo_error};
const PROCESS_PREFIX: &str = "/com/steampowered/SteamOSManager1/Process";
pub struct ProcessManager {
process: Child,
}
impl ProcessManager {
pub async fn get_command_object_path(
executable: &str,
args: &[impl AsRef<OsStr>],
connection: &mut zbus::Connection,
next_process: &mut u32,
operation_name: &str,
) -> zbus::fdo::Result<zbus::zvariant::OwnedObjectPath> {
// Run the given executable and give back an object path
let path = format!("{}{}", PROCESS_PREFIX, next_process);
*next_process += 1;
let pm = ProcessManager::run_long_command(executable, args)
.await
.inspect_err(|message| error!("Error {operation_name}: {message}"))
.map_err(to_zbus_fdo_error)?;
connection.object_server().at(path.as_str(), pm).await?;
zbus::zvariant::OwnedObjectPath::try_from(path).map_err(to_zbus_fdo_error)
}
fn send_signal(&self, signal: nix::sys::signal::Signal) -> Result<()> {
// if !self.processes.contains_key(&id) {
// println!("no process found with id {id}");
// return Err(anyhow!("No process found with id {id}"));
// }
let command = &self.process;
let pid: Result<i32, std::io::Error> = match command.id() {
Some(id) => match id.try_into() {
Ok(raw_pid) => Ok(raw_pid),
Err(message) => {
bail!("Unable to get pid_t from command {message}");
}
},
None => {
bail!("Unable to get pid from command, it likely finished running");
}
};
signal::kill(Pid::from_raw(pid.unwrap()), signal)?;
Ok(())
}
async fn exit_code_internal(&mut self) -> Result<i32> {
let status = self.process.wait().await?;
match status.code() {
Some(code) => Ok(code),
None => bail!("Process exited without giving a code somehow."),
}
}
pub async fn run_long_command(
executable: &str,
args: &[impl AsRef<OsStr>],
) -> Result<ProcessManager> {
// Run the given executable with the given arguments
// Return an id that can be used later to pause/cancel/resume as needed
let child = Command::new(executable).args(args).spawn()?;
Ok(ProcessManager { process: child })
}
}
#[interface(name = "com.steampowered.SteamOSManager1.ProcessManager")]
impl ProcessManager {
pub async fn pause(&self) -> zbus::fdo::Result<()> {
// Pause the given process if possible
// Return true on success, false otherwise
self.send_signal(Signal::SIGSTOP).map_err(to_zbus_fdo_error)
}
pub async fn resume(&self) -> zbus::fdo::Result<()> {
// Resume the given process if possible
self.send_signal(Signal::SIGCONT).map_err(to_zbus_fdo_error)
}
pub async fn cancel(&self) -> zbus::fdo::Result<()> {
self.send_signal(Signal::SIGTERM).map_err(to_zbus_fdo_error)
}
pub async fn kill(&self) -> zbus::fdo::Result<()> {
self.send_signal(signal::SIGKILL).map_err(to_zbus_fdo_error)
}
pub async fn exit_code(&mut self) -> zbus::fdo::Result<i32> {
self.exit_code_internal().await.map_err(to_zbus_fdo_error)
}
}
#[cfg(not(test))] #[cfg(not(test))]
pub async fn script_exit_code(executable: &str, args: &[impl AsRef<OsStr>]) -> Result<i32> { pub async fn script_exit_code(executable: &str, args: &[impl AsRef<OsStr>]) -> Result<i32> {