From 46a859f876b8e9a5bafe9ee03ae700db7ee80577 Mon Sep 17 00:00:00 2001 From: Dirk Van Haerenborgh Date: Sun, 14 Jan 2024 15:50:17 +0100 Subject: [PATCH] work on flatpak toolbox --- Cargo.lock | 111 +++++++- Cargo.toml | 21 +- .../actions/checkmark-small-symbolic.svg | 9 +- data/resources/resources.gresource.xml | 11 +- src/bin/toolbox.rs | 104 ++++--- src/components/terminal/constants.rs | 2 - src/components/terminal/mod.rs | 2 +- src/components/terminal/spawn.rs | 254 +++++++++++++++++- src/components/terminal/terminal.rs | 87 ++++-- src/lib/toolbox.rs | 124 +++++++++ src/main.rs | 40 +-- 11 files changed, 666 insertions(+), 99 deletions(-) create mode 100644 src/lib/toolbox.rs diff --git a/Cargo.lock b/Cargo.lock index 8a841c5..50cd6ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.79" @@ -470,6 +518,52 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "clap" +version = "4.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e54881c004cec7895b0068a0a954cd5d62da01aef83fa35b1e594497bf5445" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cb82d7f531603d2fd1f507441cdd35184fa81beff7bd489570de7f773460bb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "concurrent-queue" version = "2.4.0" @@ -1527,9 +1621,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libredox" @@ -2693,9 +2787,13 @@ dependencies = [ name = "terms" version = "0.1.0" dependencies = [ + "anyhow", "ashpd", "async-channel 2.1.1", "async-std", + "async-trait", + "bitflags 2.4.1", + "clap", "constcat", "dirs", "elementtree", @@ -2708,6 +2806,7 @@ dependencies = [ "gtk4", "itertools 0.12.0", "libadwaita", + "libc", "librsvg", "once_cell", "pango", @@ -2718,9 +2817,11 @@ dependencies = [ "serde_with", "serde_yaml", "shell-quote", + "thiserror", "tracing", "tracing-subscriber", "vte4", + "zbus", ] [[package]] @@ -2966,6 +3067,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b0c8e50..421fd13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,18 @@ version = "0.1.0" authors = ["Dirk Van Haerenborgh "] edition = "2021" +[[bin]] +name="terms" +path = "./src/main.rs" + +[[bin]] +name="terms-toolbox" +path = "./src/bin/toolbox.rs" + +[lib] +name = "terms_toolbox" +path = "src/lib/toolbox.rs" + [profile.release] lto = true @@ -26,8 +38,8 @@ adw = { package = "libadwaita", version = "0.5", features = ["v1_4"] } vte = { git = "https://gitlab.gnome.org/vhdirk/vte4-rs", package = "vte4", branch = "0.7", version = "0.7", features = [ "v0_72", ] } -ashpd = { version = "0.6.8" } rsvg = { package = "librsvg", version = "2.57" } +ashpd = { version = "0.6.8" } gsettings-macro = "0.1.20" async-std = "1.12.0" @@ -42,6 +54,13 @@ rand = "0.8.5" elementtree = "1.2.3" serde_yaml = "0.9.30" itertools = "0.12.0" +bitflags = "2.4.1" +async-trait = "0.1.77" +clap = { version = "4.4.16", features = ["derive"] } +libc = "0.2.152" +thiserror = "1.0.56" +anyhow = "1.0.79" +zbus = "3.14.1" [build-dependencies] glib-build-tools = "0.18.0" diff --git a/data/icons/actions/checkmark-small-symbolic.svg b/data/icons/actions/checkmark-small-symbolic.svg index 553c250..cdef595 100644 --- a/data/icons/actions/checkmark-small-symbolic.svg +++ b/data/icons/actions/checkmark-small-symbolic.svg @@ -1,10 +1,11 @@ - + + + diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index 019d259..4a4747d 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -14,19 +14,16 @@ ../../src/components/search_toolbar/search_toolbar.ui ../../src/components/session/session.ui ../../src/components/style_switcher/style_switcher.ui - - style.css - style-dark.css svg/theme-thumbnail.svg - ../icons/actions/checkmark-small-symbolic.svg + ../icons/actions/checkmark-small-symbolic.svg - ../icons/io.github.vhdirk.Terms.svg - ../icons/io.github.vhdirk.Terms.Devel.svg - ../icons/io.github.vhdirk.Terms-symbolic.svg + ../icons/io.github.vhdirk.Terms.svg + ../icons/io.github.vhdirk.Terms.Devel.svg + ../icons/io.github.vhdirk.Terms-symbolic.svg diff --git a/src/bin/toolbox.rs b/src/bin/toolbox.rs index f31dd69..1c94a1f 100644 --- a/src/bin/toolbox.rs +++ b/src/bin/toolbox.rs @@ -2,17 +2,19 @@ use clap::{self, Parser, Subcommand}; use libc; use std::{ffi::CString, process::ExitCode}; +use terms_toolbox as toolbox; + // This is a simple program meant to be launched with flatpak-spawn --host to // retrieve host information Flatpak'ed apps don't have access to. The original // idea for this program came from // https://github.com/gnunn1/tilix/blob/master/experimental/flatpak/tilix-flatpak-toolbox.c // https://gitlab.gnome.org/raggesilver/blackbox/-/blob/e1862c558046783ef47ba6332734d77b25370e4d/toolbox/main.c -fn get_passwd(user: String) -> ExitCode { +fn exec_getent_passwd(uid: libc::uid_t) -> ExitCode { let getent = CString::new("getent").expect("Could not create getent arg"); let getent_arg = getent.clone(); let passwd = CString::new("passwd").expect("Could not create passwd arg"); - let user = CString::new(user).expect("Could not create user arg"); + let user = CString::new(uid.to_string()).expect("Could not create user arg"); let ret = unsafe { libc::execlp(getent.as_ptr(), getent_arg.as_ptr(), passwd.as_ptr(), user.as_ptr(), 0) }; @@ -24,40 +26,76 @@ fn get_passwd(user: String) -> ExitCode { return ExitCode::FAILURE; } +fn get_shell(uid: libc::uid_t) -> ExitCode { + match toolbox::user_shell(uid) { + Ok(shell) => { + println!("{}", shell); + ExitCode::SUCCESS + }, + Err(err) => { + eprintln!("error getting user shell {}", err); + ExitCode::FAILURE + }, + } +} + fn get_child_pid() -> ExitCode { + // Caller should have saved terminal to fd 3. // This is 3 because we create an array of fds that will be passed to this // program via a Flatpak DBus call, and the vte pty we need is in the 4th // slot in that array. - - let pid = unsafe { libc::tcgetpgrp(3) }; - if pid == -1 { - eprintln!("error calling tcgetpgrp"); - return ExitCode::FAILURE; + match toolbox::child_pid(3) { + Ok(pid) => { + println!("{}", pid); + ExitCode::SUCCESS + }, + Err(err) => { + eprintln!("error getting child pid {}", err); + ExitCode::FAILURE + }, } - - println!("{}", pid); - ExitCode::SUCCESS } -fn get_proc_stat(pid: u64) -> ExitCode { - let path = CString::new(format!("/proc/{}/stat", pid)).expect("Could not create pid stat path"); - let uid = unsafe { - let mut statbuf: libc::stat = std::mem::zeroed(); - let ret = libc::stat(path.as_ptr(), &mut statbuf); +fn get_process_owner(pid: libc::pid_t) -> ExitCode { + match toolbox::process_owner(pid) { + Ok(pid) => { + println!("{}", pid); + ExitCode::SUCCESS + }, + Err(err) => { + eprintln!("error getting child pid {}", err); + ExitCode::FAILURE + }, + } +} - if ret == -1 { - eprintln!("stat failed for pid {}", pid); - return ExitCode::FAILURE; - } +fn get_process_status(pid: libc::pid_t) -> ExitCode { + match toolbox::process_status(pid) { + Ok(stat) => { + println!("{}", stat); + ExitCode::SUCCESS + }, + Err(err) => { + eprintln!("error getting child status {}", err); + ExitCode::FAILURE + }, + } +} - statbuf.st_uid - }; - println!("{}", uid); - ExitCode::SUCCESS +fn get_process_cmdline(pid: libc::pid_t) -> ExitCode { + match toolbox::process_cmdline(pid) { + Ok(stat) => { + println!("{}", stat); + ExitCode::SUCCESS + }, + Err(err) => { + eprintln!("error getting child cmdline {}", err); + ExitCode::FAILURE + }, + } } #[derive(Debug, Parser)] -#[command(name = "terms-toolbox")] #[command(about = "Terms companion for flatpak")] struct Cli { #[command(subcommand)] @@ -66,17 +104,23 @@ struct Cli { #[derive(Debug, Subcommand)] enum Commands { - GetPasswd { user: String }, - GetChildPid, - GetProcStat { pid: u64 }, + Passwd { uid: libc::uid_t }, + Shell { uid: libc::uid_t }, + ChildPid, + ProcessOwner { pid: libc::pid_t }, + ProcessStatus { pid: libc::pid_t }, + ProcessCmdline { pid: libc::pid_t }, } fn main() -> ExitCode { let args = Cli::parse(); match args.command { - Commands::GetPasswd { user } => get_passwd(user), - Commands::GetChildPid => get_child_pid(), - Commands::GetProcStat { pid } => get_proc_stat(pid), + Commands::Passwd { uid } => exec_getent_passwd(uid), + Commands::Shell { uid } => get_shell(uid), + Commands::ChildPid => get_child_pid(), + Commands::ProcessOwner { pid } => get_process_owner(pid), + Commands::ProcessStatus { pid } => get_process_status(pid), + Commands::ProcessCmdline { pid } => get_process_cmdline(pid), } } diff --git a/src/components/terminal/constants.rs b/src/components/terminal/constants.rs index 55be5ab..26360e8 100644 --- a/src/components/terminal/constants.rs +++ b/src/components/terminal/constants.rs @@ -1,8 +1,6 @@ use constcat::concat; use gettextrs::gettext; -pub const PCRE_MULTILINE: u32 = 1024; - // Copyright (c) 2011-2017 elementary LLC. (https://elementary.io) // From: https://github.com/elementary/terminal/blob/c3e36fb2ab64c18028ff2b4a6da5bfb2171c1c04/src/Widgets/TerminalWidget.vala pub const USERCHARS: &'static str = "-[:alnum:]"; diff --git a/src/components/terminal/mod.rs b/src/components/terminal/mod.rs index ba19b50..72e4926 100644 --- a/src/components/terminal/mod.rs +++ b/src/components/terminal/mod.rs @@ -1,6 +1,6 @@ mod constants; +mod spawn; mod terminal; -// mod flatpak_spawn; use std::{collections::HashMap, path::PathBuf}; use glib::{closure_local, subclass::prelude::*, ObjectExt}; diff --git a/src/components/terminal/spawn.rs b/src/components/terminal/spawn.rs index 02ad3f1..003392f 100644 --- a/src/components/terminal/spawn.rs +++ b/src/components/terminal/spawn.rs @@ -1,7 +1,257 @@ +use std::{collections::HashMap, io, num::ParseIntError, os::fd::AsRawFd, path::PathBuf, string::FromUtf8Error}; +use super::terminal::SpawnArgs; +use ashpd::flatpak; +use async_std::stream::StreamExt; +use async_trait::async_trait; +use libc::FD_CLOEXEC; +use terms_toolbox as toolbox; +use thiserror::Error; +use tracing::warn; +use vte::{self, InputStreamExtManual}; +use zbus::zvariant::Fd; -async fn get_env() { - let dev_proxy = ashpd::flatpak::Development::new().await.unwrap(); +const FLATPAK_INFO: &str = "/.flatpak-info"; +const TOOLBOX: &str = "terms-toolbox"; +pub fn get_spawner() -> Box { + if PathBuf::from(FLATPAK_INFO).exists() { + Box::new(FlatpakSpawner::new()) + } else { + Box::new(NativeSpawner::new()) + } +} + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum SpawnError { + #[error("IO error")] + IoError(#[from] io::Error), + + #[error("Ashpd error")] + AshpdError(#[from] ashpd::Error), + + #[error("GLib error")] + GLibError(#[from] glib::Error), + + #[error("From UTF8 error")] + FromUtf8Error(#[from] FromUtf8Error), + + #[error("Toolbox error")] + ToolboxError(#[from] toolbox::ToolboxError), + + #[error("ParseInt error")] + ParseIntError(#[from] ParseIntError), + + #[error("Unknown error")] + Unknown(String), +} + +#[derive(Debug)] +pub struct NativeSpawner {} + +#[derive(Debug)] +pub struct FlatpakSpawner {} + +#[async_trait(?Send)] +pub trait Spawner: std::fmt::Debug { + /// Get the preferred shell of the user + async fn shell(&self) -> Option; + + async fn env(&self); + + async fn spawn(&self, term: &vte::Terminal, args: SpawnArgs) -> Result; + + /// Determines if a child process is running in the terminal, and returns the pid + async fn foreground_pid(&self, pty: &vte::Pty) -> Result; + + async fn process_status(&self, pid: libc::pid_t) -> Result; + + async fn process_cmdline(&self, pid: libc::pid_t) -> Result; +} + +#[async_trait(?Send)] +impl Spawner for NativeSpawner { + async fn shell(&self) -> Option { + let env_shell = std::env::var("SHELL"); + if env_shell.is_ok() { + return env_shell.ok(); + } + + warn!("Could not get user shell from env var {}", env_shell.unwrap_err()); + let uid = unsafe { libc::getuid() }; + + match toolbox::user_shell_async(uid).await { + Ok(shell) => Some(shell), + Err(err) => { + warn!("Could not get user shell {}", err); + None + }, + } + } + + async fn env(&self) { + todo!(); + } + + async fn spawn(&self, term: &vte::Terminal, args: SpawnArgs) -> Result { + todo!(); + } + + async fn foreground_pid(&self, pty: &vte::Pty) -> Result { + let fd = pty.fd().as_raw_fd(); + Ok(toolbox::child_pid(fd)?) + } + + async fn process_status(&self, pid: libc::pid_t) -> Result { + let stat = toolbox::process_status_async(pid).await?; + Ok(stat) + } + + async fn process_cmdline(&self, pid: libc::pid_t) -> Result { + let stat = toolbox::process_cmdline_async(pid).await?; + Ok(stat) + } +} + +#[async_trait(?Send)] +impl Spawner for FlatpakSpawner { + async fn shell(&self) -> Option { + let uid = unsafe { libc::getuid() }; + let out = match self.run_host_toolbox_command("passwd", Some(&uid), HashMap::new(), HashMap::new()).await { + Ok(out) => out, + Err(err) => { + warn!("Could not get user shell {}", err); + return None; + }, + }; + + let parts: Vec<&str> = out.split(":").collect(); + + if parts.len() < 7 { + warn!("Could not parse getent output: {}", out); + return None; + } + + Some(parts[6].trim().to_owned()) + } + + async fn env(&self) { + todo!(); + } + + async fn spawn(&self, term: &vte::Terminal, args: SpawnArgs) -> Result { + todo!(); + } + + async fn foreground_pid(&self, pty: &vte::Pty) -> Result { + let fds = HashMap::from([(3, Fd::from(pty.fd().as_raw_fd()))]); + let out = self.run_host_toolbox_command("child-pid", None::, fds, HashMap::new()).await?; + Ok(out.parse::()?) + } + + async fn process_status(&self, pid: libc::pid_t) -> Result { + let out = self + .run_host_toolbox_command("process-status", Some(pid), HashMap::new(), HashMap::new()) + .await?; + Ok(out) + } + + async fn process_cmdline(&self, pid: libc::pid_t) -> Result { + let out = self + .run_host_toolbox_command("process-cmdline", Some(pid), HashMap::new(), HashMap::new()) + .await?; + Ok(out) + } +} + +impl NativeSpawner { + pub fn new() -> Self { + Self {} + } +} + +impl FlatpakSpawner { + pub fn new() -> Self { + Self {} + } + + async fn host_root(&self) -> Result { + let contents = async_std::fs::read(&PathBuf::from(FLATPAK_INFO)).await?; + let keyfile = glib::KeyFile::new(); + keyfile.load_from_bytes(&glib::Bytes::from(&contents), glib::KeyFileFlags::NONE)?; + let host_root = keyfile.string("Instance", "app-path")?; + Ok(PathBuf::from(host_root).join("/bin")) + } + + /// A thin wrapper over sendHostCommand that asks the terms-toolbox for information + /// about the host system. + async fn run_host_toolbox_command( + &self, + command: &str, + command_arg: Option, + mut fds: HashMap, + envs: HashMap<&str, &str>, + ) -> Result { + let host_root = self.host_root().await?; + let toolbox_path = PathBuf::from(TOOLBOX); + + let mut argv = vec![toolbox_path, PathBuf::from(command)]; + if let Some(arg) = command_arg { + let argp = PathBuf::from(arg.to_string()); + argv.push(argp); + } + let dev_proxy = flatpak::Development::new().await?; + + // This creates two fds, where we can write to one and read from the + // other. We'll pass one fd to the HostCommand as stdout, which means + // we'll be able to read what is HostCommand prints out from the other + // fd we just opened. + let (read_fd, write_fd) = glib::unix_open_pipe(FD_CLOEXEC)?; + + let mut spawn_exit = dev_proxy.receive_spawn_exited().await?; + + fds.insert(1, write_fd.into()); + let pid = dev_proxy + .host_command( + host_root, + &argv, + fds, + envs, + flatpak::HostCommandFlags::ClearEnv | flatpak::HostCommandFlags::WatchBus, + ) + .await?; + + // this shouldn't take long + // TODO: what if it, for some reason, _does_ take long + let exit_status = loop { + if let Some((child_pid, exit_status)) = spawn_exit.next().await { + if child_pid == pid { + break exit_status; + } + } + }; + + // stream takes ownership of read_fd. No need to close it later + let input_stream = unsafe { gio::UnixInputStream::take_fd(read_fd) }; + + // TODO: is 1024 bytes not enough or way to much? + let out = match input_stream.read_future(vec![0; 1024], glib::Priority::DEFAULT).await { + Ok((buffer, size)) => String::from_utf8(buffer[0..size].to_vec()).map_err(|err| SpawnError::from(err)), + Err((_buffer, err)) => Err(SpawnError::from(err)), + }?; + + // make sure write fd is closed. We don't care about error + let write_fd_close_ret = unsafe { libc::close(write_fd) }; + if write_fd_close_ret == -1 { + let err = std::io::Error::last_os_error(); + warn!("Error occured while closing write fd {}", err); + } + if exit_status != 0 { + Err(SpawnError::Unknown(out)) + } else { + Ok(out) + } + } } diff --git a/src/components/terminal/terminal.rs b/src/components/terminal/terminal.rs index cb8099e..5ea327f 100644 --- a/src/components/terminal/terminal.rs +++ b/src/components/terminal/terminal.rs @@ -25,30 +25,56 @@ use shell_quote::Sh; use vte::{PopoverExt, PtyFlags, TerminalExt, TerminalExtManual, WidgetExt}; use crate::components::search_toolbar::SearchToolbar; +use crate::config::APP_NAME; +use crate::pcre2::PCRE2Flags; use crate::services::settings::ScrollbackMode; use crate::services::settings::Settings; use crate::services::theme_provider::Theme; use crate::services::theme_provider::ThemeProvider; -use super::constants::PCRE_MULTILINE; use super::constants::URL_REGEX_STRINGS; +use super::spawn::get_spawner; +use super::spawn::FlatpakSpawner; +use super::spawn::NativeSpawner; +use super::spawn::Spawner; use super::*; -struct SpawnArgs { +static TERMS_ENV: Lazy> = Lazy::new(|| { + let mut env = HashMap::from([ + ("TERM", "xterm-256color".to_string()), + ("COLORTERM", "truecolor".to_string()), + ("TERM_PROGRAM", APP_NAME.to_string()), + ( + "VTE_VERSION", + format!( + "{}", + vte::ffi::VTE_MAJOR_VERSION * 10000 + vte::ffi::VTE_MINOR_VERSION * 100 + vte::ffi::VTE_MICRO_VERSION + ), + ), + ]); + + if let Some(themes_dir) = ThemeProvider::user_themes_dir() { + env.insert("TERMS_THEMES_DIR", themes_dir.to_string_lossy().into()); + } + env +}); + +#[derive(Debug, Clone)] +pub struct SpawnArgs { pub cwd_path: PathBuf, pub argv: Vec, pub envv: HashMap, } #[derive(Debug)] -enum SpawnCtx { +pub enum SpawnCtx { Native, Flatpak, } #[derive(Debug, Default)] struct TerminalContext { - pub pid: Option, + // pub pid: Option, pub exit_handler: Option, pub spawn_handle: Option>, pub spawn_ctx: Option, @@ -57,10 +83,11 @@ struct TerminalContext { pub padding_provider: Option, } -#[derive(Debug, Default, CompositeTemplate)] +#[derive(Debug, CompositeTemplate)] #[template(resource = "/io/github/vhdirk/Terms/gtk/terminal.ui")] pub struct Terminal { pub init_args: RefCell, + pub spawner: Box, pub settings: Settings, ctx: RefCell, @@ -78,6 +105,21 @@ pub struct Terminal { scrolled: TemplateChild, } +impl Default for Terminal { + fn default() -> Self { + Self { + init_args: Default::default(), + spawner: get_spawner(), + settings: Default::default(), + ctx: Default::default(), + term: Default::default(), + search_toolbar: Default::default(), + popover_menu: Default::default(), + scrolled: Default::default(), + } + } +} + #[glib::object_subclass] impl ObjectSubclass for Terminal { const NAME: &'static str = "TermsTerminal"; @@ -199,6 +241,19 @@ impl Terminal { let spawn_handle = glib::spawn_future_local(clone!(@weak self as this => async move { let init_args = this.init_args.borrow().clone(); + let spawner = if ashpd::is_sandboxed().await { + Box::new(FlatpakSpawner::new()) as Box + // this.spawn_sandboxed(spawn_args).await + } else { + Box::new(NativeSpawner::new()) as Box + // this.spawn_native(spawn_args).await + } ; + + + + + + let spawn_args = SpawnArgs { // TODO: remove fallback when args are passed through cwd_path: init_args.working_dir.or(env::current_dir().ok()).unwrap_or(PathBuf::new()), @@ -206,16 +261,13 @@ impl Terminal { envv: HashMap::new(), }; - let spawn_result = if ashpd::is_sandboxed().await { - this.spawn_sandboxed(spawn_args).await - } else { - this.spawn_native(spawn_args).await - } ; - match spawn_result { - Ok(pid) => this.ctx.borrow_mut().pid = Some(pid), - Err(err) => error!("Could now spawn vte subprocess {:?}", err) - }; + let spawn_result = spawner.spawn(&this.term, spawn_args).await; + + // match spawn_result { + // Ok(pid) => this.ctx.borrow_mut().pid = Some(glib::Pid::), + // Err(err) => error!("Could now spawn vte subprocess {:?}", err) + // }; })); self.ctx.borrow_mut().spawn_handle = Some(spawn_handle); @@ -260,7 +312,7 @@ impl Terminal { fn setup_regexes(&self) { for reg_str in URL_REGEX_STRINGS { - if let Ok(reg) = vte::Regex::for_match(reg_str, PCRE_MULTILINE) { + if let Ok(reg) = vte::Regex::for_match(reg_str, PCRE2Flags::MULTILINE.bits()) { let id = self.term.match_add_regex(®, 0); self.term.match_set_cursor_name(id, "pointer") } @@ -454,7 +506,7 @@ impl Terminal { fn on_child_exited(&self, status: u32) { dbg!("Terminal child exited with status {}", status); - self.ctx.borrow_mut().pid = None; + // self.ctx.borrow_mut().pid = None; let handler = self.ctx.borrow_mut().exit_handler.take(); match handler { None => error!("missing exit signal handler"), @@ -488,6 +540,9 @@ impl Terminal { } async fn spawn_native(&self, spawn_args: SpawnArgs) -> Result { + let spawner = NativeSpawner {}; + spawner.spawn(&*self.term, spawn_args.clone()).await; + let SpawnArgs { cwd_path, argv, envv } = spawn_args; let args: Vec<&str> = argv.iter().map(|path| path.to_str().unwrap_or_default()).collect(); diff --git a/src/lib/toolbox.rs b/src/lib/toolbox.rs new file mode 100644 index 0000000..d55179d --- /dev/null +++ b/src/lib/toolbox.rs @@ -0,0 +1,124 @@ +use std::{ + ffi::{CString, NulError}, + os::fd::RawFd, +}; +use thiserror::Error; + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum ToolboxError { + #[error("NulError error")] + NulError(#[from] NulError), + + #[error("Io error")] + IoError(#[from] std::io::Error), + + #[error("Unknown error")] + Unknown(String), +} + +pub fn passwd_line_filter(uid: libc::uid_t) -> impl FnMut(&Result) -> bool { + let uid = uid.to_string(); + move |line| { + if let Ok(entry) = line { + let fields: Vec<&str> = entry.split(':').collect(); + if fields.get(0) == Some(&uid.as_str()) { + println!("Found user entry: {}", entry); + return true; + } + } + false + } +} + +pub fn shell_from_passwd_line(passwd_line: &str) -> Result { + let parts: Vec<&str> = passwd_line.split(":").collect(); + + if parts.len() < 7 { + return Err(ToolboxError::Unknown(format!("Could not parse user line from passwd line {}", { passwd_line }))); + } + + Ok(parts[6].trim().to_owned()) +} + +pub fn user_shell(uid: libc::uid_t) -> Result { + use std::io::BufRead; + let passwd_file = std::fs::File::open("/etc/passwd")?; + + let passwd_line = match std::io::BufReader::new(passwd_file).lines().find(passwd_line_filter(uid)) { + Some(line) => line.map_err(ToolboxError::from), + None => Err(ToolboxError::Unknown(format!("User {} not found in passwd file", { uid }))), + }?; + + shell_from_passwd_line(&passwd_line) +} + +pub async fn user_shell_async(uid: libc::uid_t) -> Result { + use async_std::{io::prelude::BufReadExt, stream::StreamExt}; + let passwd_file = async_std::fs::File::open("/etc/passwd").await?; + + let passwd_line = match async_std::io::BufReader::new(passwd_file).lines().find(passwd_line_filter(uid)).await { + Some(line) => line.map_err(ToolboxError::from), + None => Err(ToolboxError::Unknown(format!("User {} not found in passwd file", { uid }))), + }?; + + shell_from_passwd_line(&passwd_line) +} + +pub fn child_pid(fd: RawFd) -> Result { + let pid = unsafe { libc::tcgetpgrp(fd) }; + + if pid == -1 { + Err(std::io::Error::last_os_error().into()) + } else { + Ok(pid) + } +} + +pub fn process_libc_stat(pid: libc::pid_t) -> Result { + let path = CString::new(format!("/proc/{}/stat", pid))?; + let stat = unsafe { + let mut statbuf: libc::stat = std::mem::zeroed(); + let ret = libc::stat(path.as_ptr(), &mut statbuf); + + if ret == -1 { + return Err(std::io::Error::last_os_error().into()); + } + + statbuf + }; + Ok(stat) +} + +pub fn process_owner(pid: libc::pid_t) -> Result { + let stat = process_libc_stat(pid)?; + Ok(stat.st_uid) +} + +fn read_file(file_path: &str) -> Result { + let path = std::path::PathBuf::from(file_path); + let content = std::fs::read_to_string(path)?; + Ok(content) +} + +async fn read_file_async(file_path: &str) -> Result { + let path = async_std::path::PathBuf::from(file_path); + let content = async_std::fs::read_to_string(path).await?; + Ok(content) +} + +pub fn process_status(pid: libc::pid_t) -> Result { + read_file(&format!("/proc/{}/stat", pid)) +} + +pub async fn process_status_async(pid: libc::pid_t) -> Result { + read_file_async(&format!("/proc/{}/stat", pid)).await +} + +pub fn process_cmdline(pid: libc::pid_t) -> Result { + read_file(&format!("/proc/{}/cmdline", pid)) +} + +pub async fn process_cmdline_async(pid: libc::pid_t) -> Result { + read_file_async(&format!("/proc/{}/cmdline", pid)).await +} diff --git a/src/main.rs b/src/main.rs index 3a9bed0..912ddfe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,41 +1,16 @@ -use config::{APP_ID, GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE}; -use gettextrs::{gettext, LocaleCategory}; +use config::{GETTEXT_PACKAGE, LOCALEDIR, RESOURCES_FILE}; +use gettextrs::LocaleCategory; +use tracing_subscriber::{fmt, prelude::*, EnvFilter}; + +use application::Application; mod application; mod components; mod config; +mod pcre2; mod services; mod utils; -use application::Application; -use tracing_subscriber::{fmt, prelude::*, EnvFilter}; - -// #[doc(hidden)] -// pub fn resources_register_include_impl(bytes: &'static [u8]) -> Result<(), glib::Error> { -// let bytes = glib::Bytes::from_static(bytes); -// let resource = Resource::from_data(&bytes)?; -// resources_register(&resource); -// Ok(()) -// } - -// // rustdoc-stripper-ignore-next -// /// Include gresources generated with `glib_build_tools::compile_resources` and register with glib. `path` is -// /// relative to `OUTDIR`. -// /// -// /// ```ignore -// /// gio::resources_register_include!("compiled.gresource").unwrap(); -// /// ``` -// #[macro_export] -// macro_rules! resources_register_include { -// ($path:expr) => { -// $crate::resources_register_include_impl(include_bytes!(concat!( -// env!("OUT_DIR"), -// "/", -// $path -// ))) -// }; -// } - fn init_gettext() { gettextrs::setlocale(LocaleCategory::LcAll, ""); gettextrs::bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR).expect("Unable to bind the text domain"); @@ -56,9 +31,6 @@ pub fn init() { fn main() -> glib::ExitCode { init(); - // Create a new application let app = Application::new(); - - // Run the application app.run() }