Update session status

This commit is contained in:
Armin Ronacher 2025-06-15 22:17:38 +02:00
parent 2801bc870d
commit c3f5744494
5 changed files with 239 additions and 16 deletions

128
tty-fwd/Cargo.lock generated
View file

@ -31,6 +31,22 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "errno"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "getrandom"
version = "0.3.3"
@ -55,6 +71,12 @@ version = "0.2.173"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "memchr"
version = "2.7.5"
@ -73,6 +95,12 @@ dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "proc-macro2"
version = "1.0.95"
@ -97,6 +125,19 @@ version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rustix"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.20"
@ -165,6 +206,19 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "tty-fwd"
version = "0.4.0"
@ -175,6 +229,7 @@ dependencies = [
"serde",
"serde_json",
"signal-hook",
"tempfile",
"uuid",
]
@ -202,6 +257,79 @@ dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"

View file

@ -19,4 +19,5 @@ nix = { version = "0.29.0", default-features = false, features = ["fs", "process
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
signal-hook = { version = "0.3.14", default-features = false }
tempfile = "3.20.0"
uuid = { version = "1.17.0", features = ["v4"], default-features = false }

View file

@ -38,18 +38,45 @@ fn list_sessions(control_path: &Path) -> Result<(), anyhow::Error> {
let stdin_path = path.join("stdin");
if session_json_path.exists() {
let status = if stream_out_path.exists() && stdin_path.exists() {
"running"
let session_data = if let Ok(content) = fs::read_to_string(&session_json_path) {
if let Ok(session_info) = serde_json::from_str::<SessionInfo>(&content) {
serde_json::json!({
"cmdline": session_info.cmdline,
"name": session_info.name,
"cwd": session_info.cwd,
"pid": session_info.pid,
"status": session_info.status,
"exit_code": session_info.exit_code,
"stream-out": stream_out_path.to_string_lossy(),
"stdin": stdin_path.to_string_lossy()
})
} else {
// Fallback to old behavior if JSON parsing fails
let status = if stream_out_path.exists() && stdin_path.exists() {
"running"
} else {
"stopped"
};
serde_json::json!({
"status": status,
"stream-out": stream_out_path.to_string_lossy(),
"stdin": stdin_path.to_string_lossy()
})
}
} else {
"stopped"
// Fallback to old behavior if file reading fails
let status = if stream_out_path.exists() && stdin_path.exists() {
"running"
} else {
"stopped"
};
serde_json::json!({
"status": status,
"stream-out": stream_out_path.to_string_lossy(),
"stdin": stdin_path.to_string_lossy()
})
};
let session_data = serde_json::json!({
"status": status,
"stream-out": stream_out_path.to_string_lossy(),
"stdin": stdin_path.to_string_lossy()
});
sessions.insert(session_id.to_string(), session_data);
}
}
@ -121,10 +148,13 @@ fn main() -> Result<(), anyhow::Error> {
.collect(),
name: session_name.unwrap_or(executable_name),
cwd: current_dir,
pid: None,
status: "starting".to_string(),
exit_code: None,
};
let session_info_path = session_path.join("session.json");
let session_info_str = serde_json::to_string(&session_info)?;
fs::write(session_info_path, session_info_str)?;
fs::write(&session_info_path, session_info_str)?;
// Set up stream-out and stdin paths
let stream_out_path = session_path.join("stream-out");
@ -134,7 +164,8 @@ fn main() -> Result<(), anyhow::Error> {
let mut tty_spawn = TtySpawn::new_cmdline(cmdline.iter().map(|s| s.as_os_str()));
tty_spawn
.stdout_path(&stream_out_path, true)?
.stdin_path(&stdin_path)?;
.stdin_path(&stdin_path)?
.session_json_path(&session_info_path);
// Spawn the process
let exit_code = tty_spawn.spawn()?;

View file

@ -1,11 +1,16 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize)]
#[derive(Serialize, Deserialize)]
pub struct SessionInfo {
pub cmdline: Vec<String>,
pub name: String,
pub cwd: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub pid: Option<u32>,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub exit_code: Option<i32>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]

View file

@ -2,12 +2,15 @@ use std::ffi::{CString, OsStr, OsString};
use std::fs::File;
use std::os::fd::{AsFd, BorrowedFd, IntoRawFd, OwnedFd};
use std::os::unix::prelude::{AsRawFd, OpenOptionsExt, OsStrExt};
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{env, io};
use tempfile::NamedTempFile;
use crate::protocol::{AsciinemaEvent, AsciinemaEventType, AsciinemaHeader, StreamWriter};
use crate::protocol::{
AsciinemaEvent, AsciinemaEventType, AsciinemaHeader, SessionInfo, StreamWriter,
};
use nix::errno::Errno;
use nix::libc::{login_tty, O_NONBLOCK, TIOCGWINSZ, TIOCSWINSZ, VEOF};
@ -41,6 +44,7 @@ impl TtySpawn {
no_echo: false,
no_pager: false,
no_raw: false,
session_json_path: None,
}),
}
}
@ -191,6 +195,12 @@ impl TtySpawn {
self
}
/// Sets the session JSON path for status updates.
pub fn session_json_path<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.options_mut().session_json_path = Some(path.as_ref().to_path_buf());
self
}
/// Spawns the application in the TTY.
pub fn spawn(&mut self) -> Result<i32, io::Error> {
Ok(spawn(
@ -212,6 +222,36 @@ struct SpawnOptions {
no_echo: bool,
no_pager: bool,
no_raw: bool,
session_json_path: Option<PathBuf>,
}
/// Updates the session status in the JSON file
fn update_session_status(
session_json_path: &Path,
pid: Option<u32>,
status: &str,
exit_code: Option<i32>,
) -> Result<(), io::Error> {
if let Ok(content) = std::fs::read_to_string(session_json_path) {
if let Ok(mut session_info) = serde_json::from_str::<SessionInfo>(&content) {
if let Some(pid) = pid {
session_info.pid = Some(pid);
}
session_info.status = status.to_string();
if let Some(code) = exit_code {
session_info.exit_code = Some(code);
}
let updated_content = serde_json::to_string(&session_info)?;
// Write to temporary file first, then move to final location
let temp_file = NamedTempFile::new_in(
session_json_path.parent().unwrap_or_else(|| Path::new(".")),
)?;
std::fs::write(temp_file.path(), updated_content)?;
temp_file.persist(session_json_path)?;
}
}
Ok(())
}
/// Spawns a process in a PTY in a manor similar to `script`
@ -290,7 +330,18 @@ fn spawn(mut opts: SpawnOptions) -> Result<i32, Errno> {
} else {
None
};
return communication_loop(
// Update session status to running with PID
if let Some(ref session_json_path) = opts.session_json_path {
let _ = update_session_status(
session_json_path,
Some(child.as_raw() as u32),
"running",
None,
);
}
let exit_code = communication_loop(
pty.master,
child,
term_attrs.is_some(),
@ -298,7 +349,14 @@ fn spawn(mut opts: SpawnOptions) -> Result<i32, Errno> {
opts.stdin_file.as_mut(),
stderr_pty,
!opts.no_flush,
);
)?;
// Update session status to exited with exit code
if let Some(ref session_json_path) = opts.session_json_path {
let _ = update_session_status(session_json_path, None, "exited", Some(exit_code));
}
return Ok(exit_code);
}
// set the pagers to `cat` if it's disabled.