mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-07-17 21:35:03 +03:00
fix(linux): reap a crashed headless session's leftovers on next start (#15348)
The teardown cleanup added for #15183 only runs on a clean disconnect. If the service or its --server crashes before then, the headless logind session scope and the /tmp/.X<n> lock files it created leak the same way #15183 leaked them, with nothing to reclaim them afterwards. Record the session scope and display when the headless session starts, and on the next --server start reap exactly what the previous run recorded, then drop the marker. It only ever touches the one scope and display the previous run recorded, never a scan, so unrelated sessions are untouched; the reap and X cleanup reuse the teardown path. A logind session id is only unique within a boot: the counter lives in /run and resets, so a recorded "session-N.scope" can name a different, live session after a reboot. Tag the marker with the boot id and only reap the scope when it matches the current boot. A leaked cgroup cannot outlive a reboot, so nothing legitimate is lost cross-boot; the X lock cleanup stays pid-guarded and runs either way. Signed-off-by: TBX3D <88289044+TBX3D@users.noreply.github.com>
This commit is contained in:
@@ -51,6 +51,7 @@ fn check_desktop_manager() {
|
|||||||
pub fn start_xdesktop() {
|
pub fn start_xdesktop() {
|
||||||
debug_assert!(crate::is_server());
|
debug_assert!(crate::is_server());
|
||||||
std::thread::spawn(|| {
|
std::thread::spawn(|| {
|
||||||
|
DesktopManager::recover_orphaned_session();
|
||||||
*DESKTOP_MANAGER.lock().unwrap() = Some(DesktopManager::new());
|
*DESKTOP_MANAGER.lock().unwrap() = Some(DesktopManager::new());
|
||||||
|
|
||||||
let interval = time::Duration::from_millis(super::SERVICE_INTERVAL);
|
let interval = time::Duration::from_millis(super::SERVICE_INTERVAL);
|
||||||
@@ -462,9 +463,10 @@ impl DesktopManager {
|
|||||||
let (child_xorg, child_wm) = Self::start_x11(uid, gid, username, display_num, &envs)?;
|
let (child_xorg, child_wm) = Self::start_x11(uid, gid, username, display_num, &envs)?;
|
||||||
is_child_running.store(true, Ordering::SeqCst);
|
is_child_running.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
// capture the logind session scope (from a live child) for teardown, see
|
// capture the logind session scope (from a live child) for teardown and crash
|
||||||
// reap_session_scope.
|
// recovery, see reap_session_scope and recover_orphaned_session.
|
||||||
let scope_dir = Self::session_scope_dir(child_xorg.id());
|
let scope_dir = Self::session_scope_dir(child_xorg.id());
|
||||||
|
Self::save_orphaned_marker(&scope_dir, display_num);
|
||||||
|
|
||||||
log::info!("Start xorg and wm done, notify and wait xtop x11");
|
log::info!("Start xorg and wm done, notify and wait xtop x11");
|
||||||
allow_err!(tx_res.send("".to_owned()));
|
allow_err!(tx_res.send("".to_owned()));
|
||||||
@@ -879,6 +881,66 @@ impl DesktopManager {
|
|||||||
std::io::Error::last_os_error().raw_os_error() == Some(hbb_common::libc::EPERM)
|
std::io::Error::last_os_error().raw_os_error() == Some(hbb_common::libc::EPERM)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ORPHANED_SESSION_KEY: &'static str = "headless-orphaned-session";
|
||||||
|
|
||||||
|
fn save_orphaned_marker(scope_dir: &str, display_num: u32) {
|
||||||
|
// tag the marker with this boot's id: a logind session id is only unique within a
|
||||||
|
// boot (the counter lives in /run and resets), so recovery must not reap a recorded
|
||||||
|
// scope path after a reboot, when it may name a different live session.
|
||||||
|
let boot_id = Self::current_boot_id().unwrap_or_default();
|
||||||
|
hbb_common::config::LocalConfig::set_option(
|
||||||
|
Self::ORPHANED_SESSION_KEY.to_owned(),
|
||||||
|
format!("{};{};{}", scope_dir, display_num, boot_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_boot_id() -> Option<String> {
|
||||||
|
std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
|
||||||
|
.ok()
|
||||||
|
.map(|s| s.trim().to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_orphaned_marker() {
|
||||||
|
hbb_common::config::LocalConfig::set_option(
|
||||||
|
Self::ORPHANED_SESSION_KEY.to_owned(),
|
||||||
|
String::new(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_orphaned_marker(marker: &str) -> Option<(&str, u32, &str)> {
|
||||||
|
let (rest, boot_id) = marker.rsplit_once(';')?;
|
||||||
|
let (scope_dir, display) = rest.rsplit_once(';')?;
|
||||||
|
Some((scope_dir, display.trim().parse::<u32>().ok()?, boot_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
// a run that dies before wait_stop_x11 (service or --server crash) leaks the headless
|
||||||
|
// session scope + X lock files, the same as a missed teardown (rustdesk/rustdesk#15183).
|
||||||
|
// reap exactly what the dead run recorded - never a scan, so unrelated sessions are safe.
|
||||||
|
fn recover_orphaned_session() {
|
||||||
|
let marker = hbb_common::config::LocalConfig::get_option(Self::ORPHANED_SESSION_KEY);
|
||||||
|
if marker.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some((scope_dir, display_num, boot_id)) = Self::parse_orphaned_marker(&marker) {
|
||||||
|
// only reap the recorded scope when the marker is from this same boot: a leaked
|
||||||
|
// cgroup cannot outlive a reboot, so cross-boot there is nothing legitimate to
|
||||||
|
// reap, and the recorded "session-N.scope" may by then name a different live
|
||||||
|
// session. the X lock cleanup is pid-guarded, so run it either way.
|
||||||
|
let same_boot = Self::current_boot_id().map_or(false, |b| b == boot_id);
|
||||||
|
log::info!(
|
||||||
|
"Recovering leaked headless session from a previous run: scope {}, display {} (same boot: {})",
|
||||||
|
scope_dir,
|
||||||
|
display_num,
|
||||||
|
same_boot
|
||||||
|
);
|
||||||
|
if same_boot {
|
||||||
|
Self::reap_session_scope(scope_dir);
|
||||||
|
}
|
||||||
|
Self::cleanup_x_display_files(display_num);
|
||||||
|
}
|
||||||
|
Self::clear_orphaned_marker();
|
||||||
|
}
|
||||||
|
|
||||||
fn try_wait_stop_x11(
|
fn try_wait_stop_x11(
|
||||||
child_xorg: &mut Child,
|
child_xorg: &mut Child,
|
||||||
child_wm: &mut Child,
|
child_wm: &mut Child,
|
||||||
@@ -898,6 +960,7 @@ impl DesktopManager {
|
|||||||
Self::wait_x11_children_exit(child_xorg, child_wm);
|
Self::wait_x11_children_exit(child_xorg, child_wm);
|
||||||
Self::reap_session_scope(scope_dir);
|
Self::reap_session_scope(scope_dir);
|
||||||
Self::cleanup_x_display_files(display_num);
|
Self::cleanup_x_display_files(display_num);
|
||||||
|
Self::clear_orphaned_marker();
|
||||||
desktop_manager
|
desktop_manager
|
||||||
.is_child_running
|
.is_child_running
|
||||||
.store(false, Ordering::SeqCst);
|
.store(false, Ordering::SeqCst);
|
||||||
@@ -1082,4 +1145,27 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(pids, vec![100, 101, 200, 300]);
|
assert_eq!(pids, vec![100, 101, 200, 300]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_orphaned_session_marker() {
|
||||||
|
assert_eq!(
|
||||||
|
DesktopManager::parse_orphaned_marker(
|
||||||
|
"/sys/fs/cgroup/user.slice/user-1000.slice/session-3.scope;7;abc-123"
|
||||||
|
),
|
||||||
|
Some((
|
||||||
|
"/sys/fs/cgroup/user.slice/user-1000.slice/session-3.scope",
|
||||||
|
7,
|
||||||
|
"abc-123"
|
||||||
|
))
|
||||||
|
);
|
||||||
|
// an empty scope still carries the display so its stale X lock can be cleaned
|
||||||
|
assert_eq!(DesktopManager::parse_orphaned_marker(";5;abc-123"), Some(("", 5, "abc-123")));
|
||||||
|
// an empty boot id never matches the live one, so the scope reap is skipped
|
||||||
|
assert_eq!(DesktopManager::parse_orphaned_marker("/scope;5;"), Some(("/scope", 5, "")));
|
||||||
|
assert_eq!(DesktopManager::parse_orphaned_marker(""), None);
|
||||||
|
assert_eq!(DesktopManager::parse_orphaned_marker("garbage"), None);
|
||||||
|
// the pre-boot-id two-field format no longer parses, recovery just skips it
|
||||||
|
assert_eq!(DesktopManager::parse_orphaned_marker("/scope;7"), None);
|
||||||
|
assert_eq!(DesktopManager::parse_orphaned_marker("/scope;notnum;abc"), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user