Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a timeout when calling setxkbmap #1249

Merged
merged 6 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/agama-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ futures-util = { version = "0.3.30", default-features = false, features = [
"alloc",
] }
libsystemd = "0.7.0"
subprocess = "0.2.9"

[[bin]]
name = "agama-dbus-server"
Expand Down
102 changes: 91 additions & 11 deletions rust/agama-server/src/l10n/l10n.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use std::env;
use std::io;
use std::process::Command;
use std::time::Duration;

use crate::error::Error;
use agama_locale_data::{KeymapId, LocaleId};
use regex::Regex;
use subprocess::{Popen, PopenConfig, PopenError, Redirection};

use super::keyboard::KeymapsDatabase;
use super::locale::LocalesDatabase;
Expand All @@ -21,6 +24,79 @@ pub struct L10n {
pub ui_keymap: KeymapId,
}

// timeout for the setxkbmap call (in seconds), when there is an authentication
// problem when accessing the X server then it enters an infinite loop
const SETXKBMAP_TIMEOUT: u64 = 3;

// helper function which runs a command with timeout and collects it's standard
// output
fn run_with_timeout(cmd: &[&str], timeout: u64) -> Result<Option<String>, PopenError> {
// start the subprocess
let mut process = Popen::create(
cmd,
PopenConfig {
stdout: Redirection::Pipe,
stderr: Redirection::Pipe,
..Default::default()
},
)?;

// wait for it to finish or until the timeout is reached
if process
.wait_timeout(Duration::from_secs(timeout))?
.is_none()
{
tracing::warn!("Command {:?} timed out!", cmd);
// if the process is still running after the timeout then terminate it,
// ignore errors, there is another attempt later to kill the process
let _ = process.terminate();

// give the process some time to react to SIGTERM
if process.wait_timeout(Duration::from_secs(1))?.is_none() {
// process still running, kill it with SIGKILL
process.kill()?;
}

return Err(PopenError::LogicError("Timeout reached"));
}

// get the collected stdout/stderr
let (out, err) = process.communicate(None)?;

if let Some(err_str) = err {
if !err_str.is_empty() {
tracing::warn!("Error output size: {}", err_str.len());
}
}

return Ok(out);
}

// the default X display to use if not configured or when X forwarding is used
fn default_display() -> String {
String::from(":0")
}

// helper function to get the X display name, if not set it returns the default display
fn display() -> String {
let display = env::var("DISPLAY");

match display {
Ok(display) => {
// use the $DISPLAY value only when it is a local X server
if display.starts_with(":") {
display
} else {
// when using SSH X forwarding (e.g. "localhost:10.0") we could
// accidentally change the configuration of the remote X server,
// in that case try using the local X server if it is available
default_display()
}
}
Err(_) => default_display(),
}
}

impl L10n {
pub fn new_with_locale(ui_locale: &LocaleId) -> Result<Self, Error> {
const DEFAULT_TIMEZONE: &str = "Europe/Berlin";
Expand Down Expand Up @@ -112,11 +188,15 @@ impl L10n {
.args(["set-x11-keymap", &keymap])
.output()
.map_err(LocaleError::Commit)?;
Command::new("/usr/bin/setxkbmap")
.arg(keymap)
.env("DISPLAY", ":0")
.output()
.map_err(LocaleError::Commit)?;

let output = run_with_timeout(
&["setxkbmap", "-display", &display(), &keymap],
SETXKBMAP_TIMEOUT,
);
output.map_err(|e| {
LocaleError::Commit(io::Error::new(io::ErrorKind::Other, e.to_string()))
})?;

Ok(())
}

Expand All @@ -141,12 +221,12 @@ impl L10n {
}

fn x11_keymap() -> Result<String, io::Error> {
let output = Command::new("setxkbmap")
.arg("-query")
.env("DISPLAY", ":0")
.output()?;
let output = String::from_utf8(output.stdout)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
let output = run_with_timeout(
&["setxkbmap", "-query", "-display", &display()],
SETXKBMAP_TIMEOUT,
);
let output = output.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
let output = output.unwrap_or(String::new());

let keymap_regexp = Regex::new(r"(?m)^layout: (.+)$").unwrap();
let captures = keymap_regexp.captures(&output);
Expand Down
6 changes: 6 additions & 0 deletions rust/package/agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Thu May 23 15:47:28 UTC 2024 - Ladislav Slezák <lslezak@suse.com>

- Avoid deadlock when "setxkbmap" call gets stucked, use a timeout
(gh#openSUSE/agama#1249)

-------------------------------------------------------------------
Wed May 22 12:31:25 UTC 2024 - Josef Reidinger <jreidinger@suse.com>

Expand Down
Loading