Use setsid on power commands to ensure they do not use our TTY (disablable). Run commands in a thread to prevent UI blocking.

This commit is contained in:
Antoine POPINEAU 2021-10-04 16:05:36 +02:00
parent a34140eb7b
commit f12688fa0d
No known key found for this signature in database
GPG Key ID: A78AC64694F84063
11 changed files with 136 additions and 26 deletions

17
Cargo.lock generated
View File

@ -492,6 +492,12 @@ dependencies = [
"objc",
]
[[package]]
name = "once_cell"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "ordered-multimap"
version = "0.3.1"
@ -786,6 +792,15 @@ dependencies = [
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.6.1"
@ -904,7 +919,9 @@ dependencies = [
"memchr",
"mio",
"num_cpus",
"once_cell",
"pin-project-lite",
"signal-hook-registry",
"tokio-macros",
"winapi",
]

View File

@ -19,7 +19,7 @@ rust-embed = "^5.9.0"
rust-ini = "^0.17.0"
smart-default = "0.6.0"
textwrap = "^0.14.0"
tokio = { version = "1.2", default_features = false, features = ["macros", "rt-multi-thread", "net", "sync", "time"] }
tokio = { version = "1.2", default_features = false, features = ["macros", "rt-multi-thread", "net", "sync", "time", "process"] }
unic-langid = "^0.9"
zeroize = "^1.3.0"

View File

@ -33,6 +33,8 @@ Options:
command to run to shut down the system
--power-reboot 'CMD [ARGS]...'
command to run to reboot the system
--power-no-setsid
do not prefix power commands with setsid
```
## Usage

View File

@ -8,7 +8,7 @@ action_command = Change command
action_session = Choose session
action_power = Power
date = %a, %d %h %Y - %H:%M
date = %a, %d %h %Y - %H:%M:%S
username = Username:
wait = Please wait...

View File

@ -13,6 +13,7 @@ use getopts::{Matches, Options};
use i18n_embed::DesktopLanguageRequester;
use tokio::{
net::UnixStream,
process::Command,
sync::{
oneshot::{Receiver, Sender},
RwLock, RwLockWriteGuard,
@ -43,7 +44,7 @@ impl Display for AuthStatus {
impl Error for AuthStatus {}
#[derive(SmartDefault, Copy, Clone, PartialEq)]
#[derive(SmartDefault, Debug, Copy, Clone, PartialEq)]
pub enum Mode {
#[default]
Username,
@ -51,6 +52,7 @@ pub enum Mode {
Command,
Sessions,
Power,
Processing,
}
#[derive(SmartDefault)]
@ -87,6 +89,8 @@ pub struct Greeter {
pub message: Option<String>,
pub power_commands: HashMap<PowerOption, String>,
pub power_command: Option<Command>,
pub power_setsid: bool,
pub working: bool,
pub done: bool,
@ -249,6 +253,7 @@ impl Greeter {
opts.optopt("", "power-shutdown", "command to run to shut down the system", "'CMD [ARGS]...'");
opts.optopt("", "power-reboot", "command to run to reboot the system", "'CMD [ARGS]...'");
opts.optflag("", "power-no-setsid", "do not prefix power commands with setsid");
self.config = match opts.parse(&env::args().collect::<Vec<String>>()) {
Ok(matches) => Some(matches),
@ -312,6 +317,8 @@ impl Greeter {
self.power_commands.insert(PowerOption::Reboot, command);
}
self.power_setsid = !self.config().opt_present("power-no-setsid");
self.connect().await;
}

View File

@ -149,15 +149,13 @@ pub async fn handle(greeter: Arc<RwLock<Greeter>>, events: &mut Events, ipc: Ipc
Mode::Power => {
if let Some((option, _)) = POWER_OPTIONS.get(greeter.selected_power_option) {
match power(&greeter, *option) {
Ok(status) if status.success() => {}
Ok(status) => greeter.message = Some(format!("Command exited with {}", status)),
Err(err) => greeter.message = Some(format!("Command failed: {}", err)),
}
power(&mut greeter, *option);
}
greeter.mode = greeter.previous_mode;
}
Mode::Processing => {}
},
Key::Char(c) => insert_key(&mut greeter, c).await,
@ -190,7 +188,7 @@ async fn insert_key(greeter: &mut Greeter, c: char) {
Mode::Username => greeter.username.clone(),
Mode::Password => greeter.answer.clone(),
Mode::Command => greeter.new_command.clone(),
Mode::Sessions | Mode::Power => return,
Mode::Sessions | Mode::Power | Mode::Processing => return,
};
let index = (value.chars().count() as i16 + greeter.cursor_offset) as usize;
@ -213,7 +211,7 @@ async fn delete_key(greeter: &mut Greeter, key: Key) {
Mode::Username => greeter.username.clone(),
Mode::Password => greeter.answer.clone(),
Mode::Command => greeter.new_command.clone(),
Mode::Sessions | Mode::Power => return,
Mode::Sessions | Mode::Power | Mode::Processing => return,
};
let index = match key {
@ -232,7 +230,7 @@ async fn delete_key(greeter: &mut Greeter, key: Key) {
Mode::Username => greeter.username = value,
Mode::Password => greeter.answer = value,
Mode::Command => greeter.new_command = value,
Mode::Sessions | Mode::Power => return,
Mode::Sessions | Mode::Power | Mode::Processing => return,
};
if let Key::Delete = key {

View File

@ -62,6 +62,37 @@ async fn run() -> Result<(), Box<dyn Error>> {
}
});
tokio::task::spawn({
let greeter = greeter.clone();
async move {
loop {
let command = greeter.write().await.power_command.take();
if let Some(mut command) = command {
greeter.write().await.mode = Mode::Processing;
let message = match tokio::spawn(async move { command.status().await }).await {
Ok(result) => match result {
Ok(status) if status.success() => None,
Ok(status) => Some(format!("Command exited with {}", status)),
Err(err) => Some(format!("Command failed: {}", err)),
},
Err(_) => Some("Command failed".to_string()),
};
let mode = greeter.read().await.previous_mode;
let mut greeter = greeter.write().await;
greeter.mode = mode;
greeter.message = message;
}
}
}
});
loop {
if let Some(ref mut rx) = greeter.write().await.exit_rx {
if let Ok(status) = rx.try_recv() {

View File

@ -1,7 +1,6 @@
use std::{
io,
process::{Command, ExitStatus, Stdio},
};
use std::process::Stdio;
use tokio::process::Command;
use crate::Greeter;
@ -11,8 +10,8 @@ pub enum PowerOption {
Reboot,
}
pub fn power(greeter: &Greeter, option: PowerOption) -> Result<ExitStatus, io::Error> {
let mut command = match greeter.power_commands.get(&option) {
pub fn power(greeter: &mut Greeter, option: PowerOption) {
let command = match greeter.power_commands.get(&option) {
None => {
let mut command = Command::new("shutdown");
@ -25,15 +24,30 @@ pub fn power(greeter: &Greeter, option: PowerOption) -> Result<ExitStatus, io::E
command
}
Some(command) => {
let mut args: Vec<&str> = command.split(' ').collect();
let exe = args.remove(0);
Some(args) => {
let mut command = match greeter.power_setsid {
true => {
let mut command = Command::new("setsid");
command.args(args.split(' '));
command
}
false => {
let mut args = args.split(' ');
let mut command = Command::new(args.next().unwrap_or_default());
command.args(args);
command
}
};
command.stdin(Stdio::null());
command.stdout(Stdio::null());
command.stderr(Stdio::null());
let mut command = Command::new(exe);
command.args(args);
command
}
};
command.stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()).env_clear().status()
greeter.power_command = Some(command);
}

View File

@ -1,6 +1,7 @@
mod command;
mod i18n;
mod power;
mod processing;
mod prompt;
mod sessions;
mod util;
@ -111,6 +112,7 @@ pub async fn draw(greeter: Arc<RwLock<Greeter>>, terminal: &mut Term) -> Result<
Mode::Command => self::command::draw(&mut greeter, &mut f).ok(),
Mode::Sessions => self::sessions::draw(&mut greeter, &mut f).ok(),
Mode::Power => self::power::draw(&mut greeter, &mut f).ok(),
Mode::Processing => self::processing::draw(&mut greeter, &mut f).ok(),
_ => self::prompt::draw(&mut greeter, &mut f).ok(),
};

39
src/ui/processing.rs Normal file
View File

@ -0,0 +1,39 @@
use std::{error::Error, io};
use termion::raw::RawTerminal;
use tui::{
backend::TermionBackend,
layout::{Alignment, Constraint, Direction, Layout, Rect},
text::Span,
widgets::{Block, BorderType, Borders, Paragraph},
Frame,
};
use super::util::*;
use crate::Greeter;
pub fn draw(greeter: &mut Greeter, f: &mut Frame<'_, TermionBackend<RawTerminal<io::Stdout>>>) -> Result<(u16, u16), Box<dyn Error>> {
let size = f.size();
let width = greeter.width();
let height: u16 = get_height(greeter) + 1;
let x = (size.width - width) / 2;
let y = (size.height - height) / 2;
let container = Rect::new(x, y, width, height);
let container_padding = greeter.container_padding();
let frame = Rect::new(x + container_padding, y + container_padding, width - (2 * container_padding), height - (2 * container_padding));
let block = Block::default().borders(Borders::ALL).border_type(BorderType::Plain);
let constraints = [Constraint::Length(1)];
let chunks = Layout::default().direction(Direction::Vertical).constraints(constraints.as_ref()).split(frame);
let text = Span::from(fl!("wait"));
let paragraph = Paragraph::new(text).alignment(Alignment::Center);
f.render_widget(paragraph, chunks[0]);
f.render_widget(block, container);
Ok((1, 1))
}

View File

@ -5,7 +5,7 @@ pub fn titleize(message: &str) -> String {
}
pub fn should_hide_cursor(greeter: &Greeter) -> bool {
greeter.working || (greeter.mode == Mode::Password && greeter.prompt.is_none()) || greeter.mode == Mode::Sessions || greeter.mode == Mode::Power
greeter.working || (greeter.mode == Mode::Password && greeter.prompt.is_none()) || greeter.mode == Mode::Sessions || greeter.mode == Mode::Power || greeter.mode == Mode::Processing
}
pub fn get_height(greeter: &Greeter) -> u16 {
@ -19,11 +19,11 @@ pub fn get_height(greeter: &Greeter) -> u16 {
Some(_) => (2 * container_padding) + prompt_padding + 2,
None => (2 * container_padding) + 1,
},
Mode::Sessions | Mode::Power => (2 * container_padding),
Mode::Sessions | Mode::Power | Mode::Processing => (2 * container_padding),
};
match greeter.mode {
Mode::Command | Mode::Sessions | Mode::Power => initial,
Mode::Command | Mode::Sessions | Mode::Power | Mode::Processing => initial,
_ => initial + greeting_height,
}
}