use crate::config;
use gtk::prelude::*;
use gtk::{gio, glib};
use serde::Deserialize;
use std::cell::{Cell, RefCell};
use std::collections::BTreeMap;
use std::env;
use std::ffi::OsString;
use std::fs::{self, DirBuilder};
use std::io::ErrorKind;
use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
use std::path::{Path, PathBuf};
use std::rc::{Rc, Weak};
use std::time::Duration;
const MAX_CARGO_RECEIPT_BYTES: u64 = 1024 * 1024;
const MAX_CARGO_STDERR_BYTES: usize = 32 * 1024;
const CARGO_STDERR_READ_BYTES: usize = 8 * 1024;
const CARGO_INSTALL_TIMEOUT: Duration = Duration::from_secs(30 * 60);
const CARGO_STOP_GRACE: Duration = Duration::from_secs(5);
const CARGO_KILL_WAIT: Duration = Duration::from_secs(2);
const OFFICIAL_PLUGINS: &[PluginSpec] = &[PluginSpec {
key: "lios_bar",
name: "LiosBar",
crate_name: "lios-bar",
binary_name: "lios-bar",
version: "0.1.1",
description: "Sidebar workspace for navigating several live terminal sessions.",
}];
#[derive(Debug, Clone, Copy)]
struct PluginSpec {
key: &'static str,
name: &'static str,
crate_name: &'static str,
binary_name: &'static str,
version: &'static str,
description: &'static str,
}
struct PluginState {
spec: &'static PluginSpec,
config_path: Option<PathBuf>,
toggle: glib::WeakRef<gtk::ToggleButton>,
install: glib::WeakRef<gtk::Button>,
launch: glib::WeakRef<gtk::Button>,
state_label: glib::WeakRef<gtk::Label>,
global_status: glib::WeakRef<gtk::Label>,
syncing: Cell<bool>,
checking: Cell<bool>,
busy: Cell<bool>,
install_generation: Cell<u64>,
active_install: RefCell<Option<Rc<PluginInstallOperation>>>,
installed: RefCell<PluginInstallation>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum InstallStopReason {
Cancelled,
TimedOut,
WidgetDestroyed,
}
#[derive(Default)]
struct BoundedDiagnostic {
bytes: Vec<u8>,
truncated: bool,
}
impl BoundedDiagnostic {
fn push(&mut self, chunk: &[u8]) {
if chunk.is_empty() {
return;
}
if chunk.len() >= MAX_CARGO_STDERR_BYTES {
self.bytes.clear();
self.bytes
.extend_from_slice(&chunk[chunk.len() - MAX_CARGO_STDERR_BYTES..]);
self.truncated = true;
return;
}
let overflow = self
.bytes
.len()
.saturating_add(chunk.len())
.saturating_sub(MAX_CARGO_STDERR_BYTES);
if overflow > 0 {
self.bytes.drain(..overflow);
self.truncated = true;
}
self.bytes.extend_from_slice(chunk);
}
fn message(&self) -> Option<String> {
const MAX_CHARS: usize = 180;
let message = String::from_utf8_lossy(&self.bytes);
let sanitized = strip_terminal_controls(&message);
let normalized = sanitized.split_whitespace().collect::<Vec<_>>().join(" ");
if normalized.is_empty() {
return None;
}
let character_count = normalized.chars().count();
let truncated = self.truncated || character_count > MAX_CHARS;
let prefix = if truncated { "… " } else { "" };
let limit = MAX_CHARS.saturating_sub(prefix.chars().count());
let skip = character_count.saturating_sub(limit);
let message = normalized.chars().skip(skip).collect::<String>();
Some(format!("{prefix}{message}"))
}
}
struct PluginInstallOperation {
state: Weak<PluginState>,
generation: u64,
process: gio::Subprocess,
process_group: Option<i32>,
cancellable: gio::Cancellable,
timeout: RefCell<Option<glib::SourceId>>,
stop_grace: RefCell<Option<glib::SourceId>>,
kill_wait: RefCell<Option<glib::SourceId>>,
stderr: RefCell<BoundedDiagnostic>,
stderr_done: Cell<bool>,
wait_result: RefCell<Option<Result<(), String>>>,
stop_reason: Cell<Option<InstallStopReason>>,
completed: Cell<bool>,
enabled_by_toggle: bool,
}
impl PluginInstallOperation {
fn new(
state: &Rc<PluginState>,
generation: u64,
process: gio::Subprocess,
enabled_by_toggle: bool,
) -> Rc<Self> {
let process_group = process
.identifier()
.as_deref()
.and_then(process_group_from_identifier);
Rc::new(Self {
state: Rc::downgrade(state),
generation,
process,
process_group,
cancellable: gio::Cancellable::new(),
timeout: RefCell::new(None),
stop_grace: RefCell::new(None),
kill_wait: RefCell::new(None),
stderr: RefCell::new(BoundedDiagnostic::default()),
stderr_done: Cell::new(false),
wait_result: RefCell::new(None),
stop_reason: Cell::new(None),
completed: Cell::new(false),
enabled_by_toggle,
})
}
fn start(self: &Rc<Self>) {
let timeout = glib::timeout_add_local_once(CARGO_INSTALL_TIMEOUT, {
let operation = Rc::downgrade(self);
move || {
if let Some(operation) = operation.upgrade() {
operation.timeout.borrow_mut().take();
operation.request_stop(InstallStopReason::TimedOut);
}
}
});
self.timeout.replace(Some(timeout));
if let Some(stderr) = self.process.stderr_pipe() {
self.read_stderr(stderr);
} else {
self.stderr_done.set(true);
}
let operation = self.clone();
self.process
.wait_check_async(Some(&self.cancellable), move |result| {
operation.wait_finished(result.map_err(|error| error.to_string()));
});
}
fn read_stderr(self: &Rc<Self>, stream: gio::InputStream) {
if self.completed.get() {
return;
}
let operation = self.clone();
let next_stream = stream.clone();
stream.read_bytes_async(
CARGO_STDERR_READ_BYTES,
glib::Priority::DEFAULT,
Some(&self.cancellable),
move |result| {
if operation.completed.get() {
return;
}
match result {
Ok(chunk) if chunk.is_empty() => operation.stderr_finished(),
Ok(chunk) => {
operation.stderr.borrow_mut().push(chunk.as_ref());
operation.read_stderr(next_stream);
}
Err(_) => operation.stderr_finished(),
}
},
);
}
fn stderr_finished(self: &Rc<Self>) {
if self.stderr_done.replace(true) {
return;
}
self.try_finish();
}
fn wait_finished(self: &Rc<Self>, result: Result<(), String>) {
if self.completed.get() {
return;
}
self.wait_result.replace(Some(result));
if let Some(reason) = self.stop_reason.get() {
self.complete_stopped(reason);
} else {
self.try_finish();
}
}
fn try_finish(self: &Rc<Self>) {
if self.completed.get() || !self.stderr_done.get() || self.wait_result.borrow().is_none() {
return;
}
let result = self
.wait_result
.borrow_mut()
.take()
.expect("wait result checked above");
if self.completed.replace(true) {
return;
}
self.remove_sources();
let Some(state) = self.detach_state() else {
return;
};
match result {
Ok(()) => finish_install_success(&state),
Err(wait_error) => {
let error = self
.stderr
.borrow()
.message()
.unwrap_or_else(|| bounded_message(&wait_error));
finish_install_error(&state, self.enabled_by_toggle, &error);
}
}
}
fn request_stop(self: &Rc<Self>, reason: InstallStopReason) {
if self.completed.get() || !claim_stop_reason(&self.stop_reason, reason) {
return;
}
if let Some(timeout) = self.timeout.borrow_mut().take() {
timeout.remove();
}
if self.wait_result.borrow().is_some() {
self.complete_stopped(reason);
return;
}
if reason == InstallStopReason::WidgetDestroyed {
self.force_kill_process_tree();
self.complete_without_ui();
return;
}
self.signal_process_tree(libc::SIGTERM);
if let Some(state) = self.current_state() {
let message = match reason {
InstallStopReason::Cancelled => {
format!("Cancelling the {} installation…", state.spec.name)
}
InstallStopReason::TimedOut => format!(
"{} exceeded the 30-minute install limit; stopping Cargo…",
state.spec.name
),
InstallStopReason::WidgetDestroyed => unreachable!(),
};
set_global_status(&state, &message);
refresh(&state);
}
let stop_grace = glib::timeout_add_local_once(CARGO_STOP_GRACE, {
let operation = Rc::downgrade(self);
move || {
if let Some(operation) = operation.upgrade() {
operation.stop_grace.borrow_mut().take();
operation.begin_force_kill(reason);
}
}
});
self.stop_grace.replace(Some(stop_grace));
}
fn begin_force_kill(self: &Rc<Self>, reason: InstallStopReason) {
if self.completed.get() {
return;
}
self.force_kill_process_tree();
if self.wait_result.borrow().is_some() {
self.complete_stopped(reason);
return;
}
let kill_wait = glib::timeout_add_local_once(CARGO_KILL_WAIT, {
let operation = Rc::downgrade(self);
move || {
if let Some(operation) = operation.upgrade() {
operation.kill_wait.borrow_mut().take();
operation.complete_stopped(reason);
}
}
});
self.kill_wait.replace(Some(kill_wait));
}
fn complete_stopped(self: &Rc<Self>, reason: InstallStopReason) {
if self.completed.replace(true) {
return;
}
self.remove_sources();
self.cancellable.cancel();
let Some(state) = self.detach_state() else {
return;
};
finish_install_stopped(&state, self.enabled_by_toggle, reason);
}
fn complete_without_ui(self: &Rc<Self>) {
if self.completed.replace(true) {
return;
}
self.remove_sources();
self.cancellable.cancel();
if let Some(state) = self.detach_state() {
state.busy.set(false);
}
}
fn signal_process_tree(&self, signal: i32) {
let live_process = self
.process
.identifier()
.as_deref()
.and_then(process_group_from_identifier);
let group_signalled = live_process
.filter(|process_group| Some(*process_group) == self.process_group)
.is_some_and(|process_group| {
unsafe { libc::kill(-process_group, signal) == 0 }
});
if live_process.is_some() && !group_signalled {
self.process.send_signal(signal);
}
}
fn force_kill_process_tree(&self) {
let live_process = self
.process
.identifier()
.as_deref()
.and_then(process_group_from_identifier);
let group_signalled = live_process
.filter(|process_group| Some(*process_group) == self.process_group)
.is_some_and(|process_group| {
unsafe { libc::kill(-process_group, libc::SIGKILL) == 0 }
});
if live_process.is_some() && !group_signalled {
self.process.force_exit();
}
}
fn remove_sources(&self) {
if let Some(timeout) = self.timeout.borrow_mut().take() {
timeout.remove();
}
if let Some(stop_grace) = self.stop_grace.borrow_mut().take() {
stop_grace.remove();
}
if let Some(kill_wait) = self.kill_wait.borrow_mut().take() {
kill_wait.remove();
}
}
fn current_state(&self) -> Option<Rc<PluginState>> {
let state = self.state.upgrade()?;
let is_current = state.install_generation.get() == self.generation
&& state
.active_install
.borrow()
.as_ref()
.is_some_and(|operation| operation.generation == self.generation);
is_current.then_some(state)
}
fn detach_state(self: &Rc<Self>) -> Option<Rc<PluginState>> {
let state = self.current_state()?;
let is_current = state
.active_install
.borrow()
.as_ref()
.is_some_and(|operation| Rc::ptr_eq(operation, self));
if !is_current {
return None;
}
state.active_install.borrow_mut().take();
Some(state)
}
}
fn claim_stop_reason(slot: &Cell<Option<InstallStopReason>>, reason: InstallStopReason) -> bool {
if slot.get().is_some() {
return false;
}
slot.set(Some(reason));
true
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum PluginInstallation {
Missing,
Unmanaged(PathBuf),
Managed(PathBuf),
}
impl PluginInstallation {
fn is_managed(&self) -> bool {
matches!(self, Self::Managed(_))
}
fn launch_path(&self) -> Option<PathBuf> {
match self {
Self::Managed(path) => Some(path.clone()),
Self::Missing | Self::Unmanaged(_) => None,
}
}
}
#[derive(Debug, Deserialize)]
struct CargoInstallMetadata {
installs: BTreeMap<String, CargoInstallEntry>,
}
#[derive(Debug, Deserialize)]
struct CargoInstallEntry {
bins: Vec<String>,
}
pub(crate) fn build_card(status: >k::Label, config_path: Option<PathBuf>) -> gtk::Box {
let card = gtk::Box::new(gtk::Orientation::Vertical, 6);
card.add_css_class("lios-card");
card.set_hexpand(true);
card.set_vexpand(false);
card.set_size_request(230, -1);
let title = gtk::Label::new(Some("Plugins"));
title.add_css_class("lios-row-label");
title.set_xalign(0.0);
let subtitle = gtk::Label::new(Some(
"Curated, version-pinned helpers kept in Lios's private plugin directory. Disable keeps the binary installed.",
));
subtitle.add_css_class("lios-muted");
subtitle.set_xalign(0.0);
subtitle.set_wrap(true);
card.append(&title);
card.append(&subtitle);
for spec in OFFICIAL_PLUGINS {
card.append(&build_plugin_row(spec, status, config_path.clone()));
}
let boundary = gtk::Label::new(Some(
"Installs run only after your click. No arbitrary package names, automatic updates, or background downloads.",
));
boundary.add_css_class("lios-hint");
boundary.set_xalign(0.0);
boundary.set_wrap(true);
card.append(&boundary);
card
}
fn build_plugin_row(
spec: &'static PluginSpec,
global_status: >k::Label,
config_path: Option<PathBuf>,
) -> gtk::Box {
let row = gtk::Box::new(gtk::Orientation::Vertical, 5);
row.add_css_class("lios-plugin-row");
let heading = gtk::Box::new(gtk::Orientation::Horizontal, 6);
let identity = gtk::Box::new(gtk::Orientation::Vertical, 1);
identity.set_hexpand(true);
let name = gtk::Label::new(Some(spec.name));
name.add_css_class("lios-row-label");
name.set_xalign(0.0);
let description = gtk::Label::new(Some(spec.description));
description.add_css_class("lios-muted");
description.set_xalign(0.0);
description.set_wrap(true);
identity.append(&name);
identity.append(&description);
let toggle = gtk::ToggleButton::with_label("Disabled");
toggle.add_css_class("lios-choice");
toggle.set_tooltip_text(Some(
"Enable Lios integration; disabling does not uninstall the plugin",
));
heading.append(&identity);
heading.append(&toggle);
row.append(&heading);
let state_label = gtk::Label::new(None);
state_label.add_css_class("lios-hint");
state_label.set_xalign(0.0);
state_label.set_ellipsize(gtk::pango::EllipsizeMode::Middle);
row.append(&state_label);
let actions = gtk::Box::new(gtk::Orientation::Horizontal, 6);
let install = gtk::Button::with_label("Install");
install.add_css_class("lios-choice");
install.set_hexpand(true);
install.set_tooltip_text(Some(
"Install or repair the exact reviewed crates.io release in Lios's managed plugin directory; click again while Cargo is running to cancel",
));
let launch = gtk::Button::with_label("Launch");
launch.add_css_class("lios-choice");
launch.set_hexpand(true);
launch.set_tooltip_text(Some("Launch the enabled installed plugin"));
actions.append(&install);
actions.append(&launch);
row.append(&actions);
let state = Rc::new(PluginState {
spec,
config_path,
toggle: toggle.downgrade(),
install: install.downgrade(),
launch: launch.downgrade(),
state_label: state_label.downgrade(),
global_status: global_status.downgrade(),
syncing: Cell::new(true),
checking: Cell::new(true),
busy: Cell::new(false),
install_generation: Cell::new(0),
active_install: RefCell::new(None),
installed: RefCell::new(PluginInstallation::Missing),
});
state.syncing.set(false);
refresh(&state);
toggle.connect_toggled({
let state = state.clone();
move |toggle| {
if state.syncing.get() {
return;
}
let enabled = toggle.is_active();
if !enabled {
match save_enabled(&state, false) {
Ok(()) => set_global_status(
&state,
&format!(
"{} disabled. Its installed binary was preserved.",
state.spec.name
),
),
Err(error) => {
set_global_status(&state, &error);
set_toggle(&state, true);
}
}
refresh(&state);
return;
}
if !state.installed.borrow().is_managed() {
begin_install(&state, true);
return;
}
match save_enabled(&state, true) {
Ok(()) => set_global_status(
&state,
&format!("{} enabled and ready to launch.", state.spec.name),
),
Err(error) => {
set_global_status(&state, &error);
set_toggle(&state, false);
}
}
refresh(&state);
}
});
install.connect_clicked({
let state = state.clone();
move |_| {
if state.busy.get() {
cancel_install(&state);
} else {
begin_install(&state, false);
}
}
});
launch.connect_clicked({
let state = state.clone();
move |_| launch_plugin(&state)
});
row.connect_destroy({
let state = Rc::downgrade(&state);
move |_| {
if let Some(state) = state.upgrade() {
stop_install(&state, InstallStopReason::WidgetDestroyed);
}
}
});
begin_detection(&state);
row
}
fn begin_detection(state: &Rc<PluginState>) {
let spec = state.spec;
let config_path = state.config_path.clone();
let task = gio::spawn_blocking(move || {
let installed = detect_installation(spec);
let enabled = config::plugin_enabled(config_path.as_deref(), spec.key);
(installed, enabled)
});
let state = Rc::downgrade(state);
glib::spawn_future_local(async move {
let Ok((installed, enabled)) = task.await else {
if let Some(state) = state.upgrade() {
state.checking.set(false);
set_global_status(&state, "Could not inspect the installed plugin state.");
refresh(&state);
}
return;
};
let Some(state) = state.upgrade() else {
return;
};
*state.installed.borrow_mut() = installed;
state.checking.set(false);
match enabled {
Ok(enabled) => set_toggle(&state, enabled),
Err(error) => {
set_toggle(&state, false);
set_global_status(&state, &error);
}
}
refresh(&state);
});
}
fn begin_install(state: &Rc<PluginState>, enabled_by_toggle: bool) {
if state.checking.get() || state.busy.replace(true) {
return;
}
refresh(state);
set_global_status(
state,
&format!(
"Installing pinned {} {} with Cargo…",
state.spec.name, state.spec.version
),
);
let Some(cargo) = executable_path("cargo") else {
finish_install_error(
state,
enabled_by_toggle,
"Cargo was not found in PATH, CARGO_HOME, or ~/.cargo/bin.",
);
return;
};
let install_root = match managed_install_root(state.spec) {
Ok(root) => root,
Err(error) => {
finish_install_error(state, enabled_by_toggle, &error);
return;
}
};
if let Err(error) = prepare_managed_install_root(&install_root, state.spec) {
finish_install_error(state, enabled_by_toggle, &error);
return;
}
let arguments = install_arguments(&cargo, &install_root, state.spec);
let argument_refs = arguments
.iter()
.map(OsString::as_os_str)
.collect::<Vec<_>>();
let flags = gio::SubprocessFlags::STDOUT_SILENCE | gio::SubprocessFlags::STDERR_PIPE;
let launcher = gio::SubprocessLauncher::new(flags);
launcher.set_child_setup(|| {
unsafe {
if libc::setpgid(0, 0) != 0 {
libc::_exit(127);
}
}
});
let process = match launcher.spawn(&argument_refs) {
Ok(process) => process,
Err(error) => {
finish_install_error(state, enabled_by_toggle, &error.to_string());
return;
}
};
let generation = state.install_generation.get().wrapping_add(1);
state.install_generation.set(generation);
let operation = PluginInstallOperation::new(state, generation, process, enabled_by_toggle);
state.active_install.replace(Some(operation.clone()));
operation.start();
}
fn cancel_install(state: &Rc<PluginState>) {
stop_install(state, InstallStopReason::Cancelled);
}
fn stop_install(state: &Rc<PluginState>, reason: InstallStopReason) {
let operation = state.active_install.borrow().clone();
if let Some(operation) = operation {
operation.request_stop(reason);
}
}
fn finish_install_success(state: &Rc<PluginState>) {
let installed = detect_installation(state.spec);
*state.installed.borrow_mut() = installed;
state.busy.set(false);
if !state.installed.borrow().is_managed() {
set_toggle(state, false);
set_global_status(
state,
&format!(
"{} installation completed, but its managed binary or Cargo receipt did not pass validation. Repair the installation before enabling it.",
state.spec.name
),
);
refresh(state);
return;
}
let enabled = state
.toggle
.upgrade()
.is_some_and(|toggle| toggle.is_active());
if let Err(error) = save_enabled(state, enabled) {
set_toggle(state, false);
set_global_status(state, &error);
} else if enabled {
set_global_status(
state,
&format!("{} installed, enabled, and ready.", state.spec.name),
);
} else {
set_global_status(
state,
&format!(
"{} installed. Enable it when you want Lios to use it.",
state.spec.name
),
);
}
refresh(state);
}
fn finish_install_error(state: &Rc<PluginState>, enabled_by_toggle: bool, error: &str) {
state.busy.set(false);
*state.installed.borrow_mut() = detect_installation(state.spec);
if enabled_by_toggle {
set_toggle(state, false);
}
set_global_status(
state,
&format!(
"Could not install {} {}: {}",
state.spec.name,
state.spec.version,
bounded_message(error)
),
);
refresh(state);
}
fn finish_install_stopped(
state: &Rc<PluginState>,
enabled_by_toggle: bool,
reason: InstallStopReason,
) {
state.busy.set(false);
*state.installed.borrow_mut() = detect_installation(state.spec);
if enabled_by_toggle {
set_toggle(state, false);
}
let message = match reason {
InstallStopReason::Cancelled => {
format!("{} installation cancelled.", state.spec.name)
}
InstallStopReason::TimedOut => format!(
"{} installation stopped after the 30-minute safety limit.",
state.spec.name
),
InstallStopReason::WidgetDestroyed => return,
};
set_global_status(state, &message);
refresh(state);
}
fn launch_plugin(state: &Rc<PluginState>) {
let enabled = state
.toggle
.upgrade()
.is_some_and(|toggle| toggle.is_active());
if !enabled {
set_global_status(
state,
&format!("Enable {} before launching it.", state.spec.name),
);
return;
}
*state.installed.borrow_mut() = detect_installation(state.spec);
let Some(binary) = state.installed.borrow().launch_path() else {
set_global_status(
state,
&format!(
"{} is missing or not the pinned registry release. Install or repair it first.",
state.spec.name
),
);
refresh(state);
return;
};
let launcher = gio::SubprocessLauncher::new(gio::SubprocessFlags::NONE);
launcher.unsetenv("GSK_RENDERER");
match launcher.spawn(&[binary.as_os_str()]) {
Ok(_) => set_global_status(state, &format!("Launched {}.", state.spec.name)),
Err(error) => set_global_status(
state,
&format!(
"Could not launch {}: {}",
state.spec.name,
bounded_message(&error.to_string())
),
),
}
}
fn save_enabled(state: &PluginState, enabled: bool) -> Result<(), String> {
match &state.config_path {
Some(path) => config::set_plugin_enabled(path, state.spec.key, enabled),
None => Ok(()),
}
}
fn set_toggle(state: &PluginState, enabled: bool) {
let Some(toggle) = state.toggle.upgrade() else {
return;
};
state.syncing.set(true);
toggle.set_active(enabled);
state.syncing.set(false);
}
fn refresh(state: &PluginState) {
let enabled = state
.toggle
.upgrade()
.is_some_and(|toggle| toggle.is_active());
let installation = state.installed.borrow().clone();
let installed = installation.is_managed();
let unmanaged = matches!(installation, PluginInstallation::Unmanaged(_));
let checking = state.checking.get();
let busy = state.busy.get();
let stopping = state
.active_install
.borrow()
.as_ref()
.is_some_and(|operation| operation.stop_reason.get().is_some());
if let Some(toggle) = state.toggle.upgrade() {
toggle.set_sensitive(!checking && !busy);
toggle.set_label(if enabled { "Enabled" } else { "Disabled" });
if enabled {
toggle.add_css_class("lios-selected");
} else {
toggle.remove_css_class("lios-selected");
}
}
if let Some(install) = state.install.upgrade() {
install.set_sensitive(!checking && !stopping);
install.set_label(if checking {
"Checking…"
} else if stopping {
"Stopping…"
} else if busy {
"Cancel"
} else if installed || unmanaged {
"Repair"
} else {
"Install"
});
}
if let Some(launch) = state.launch.upgrade() {
launch.set_sensitive(installed && enabled && !checking && !busy);
}
if let Some(label) = state.state_label.upgrade() {
let text = if checking {
"CHECKING INSTALLATION…".to_string()
} else {
match (installed, enabled, busy, stopping) {
(_, _, true, true) => "STOPPING CARGO…".to_string(),
(_, _, true, false) => format!(
"INSTALLING {} {} · EXACT VERSION",
state.spec.crate_name, state.spec.version
),
(true, true, false, false) => {
format!("INSTALLED {} · ENABLED", state.spec.version)
}
(true, false, false, false) => {
format!("INSTALLED {} · DISABLED", state.spec.version)
}
(false, _, false, false) if unmanaged => {
"UNMANAGED OR UNSAFE INSTALL · REPAIR REQUIRED".to_string()
}
(false, true, false, false) => "ENABLED · INSTALL REQUIRED".to_string(),
(false, false, false, false) => "NOT INSTALLED · DISABLED".to_string(),
(_, _, false, true) => unreachable!("stopping requires a busy installer"),
}
};
label.set_text(&text);
}
}
fn set_global_status(state: &PluginState, message: &str) {
if let Some(status) = state.global_status.upgrade() {
status.set_text(message);
}
}
fn install_arguments(cargo: &Path, install_root: &Path, spec: &PluginSpec) -> Vec<OsString> {
vec![
cargo.as_os_str().to_os_string(),
OsString::from("install"),
OsString::from("--root"),
install_root.as_os_str().to_os_string(),
OsString::from("--locked"),
OsString::from("--force"),
OsString::from("--registry"),
OsString::from("crates-io"),
OsString::from("--version"),
OsString::from(format!("={}", spec.version)),
OsString::from(spec.crate_name),
]
}
fn process_group_from_identifier(identifier: &str) -> Option<i32> {
identifier
.parse::<i32>()
.ok()
.filter(|process_group| *process_group > 1)
}
fn executable_path(binary: &str) -> Option<PathBuf> {
let mut directories = env::var_os("PATH")
.map(|path| env::split_paths(&path).collect::<Vec<_>>())
.unwrap_or_default();
if let Ok(current) = env::current_exe() {
if let Some(parent) = current.parent() {
directories.push(parent.to_path_buf());
}
}
if let Some(cargo_home) = env::var_os("CARGO_HOME").filter(|value| !value.is_empty()) {
directories.push(PathBuf::from(cargo_home).join("bin"));
}
if let Some(home) = env::var_os("HOME").filter(|value| !value.is_empty()) {
directories.push(PathBuf::from(home).join(".cargo/bin"));
}
find_executable_in(binary, &directories)
}
fn detect_installation(spec: &PluginSpec) -> PluginInstallation {
let Ok(root) = managed_install_root(spec) else {
return PluginInstallation::Missing;
};
detect_installation_at(&root, spec)
}
fn managed_install_root(spec: &PluginSpec) -> Result<PathBuf, String> {
managed_install_root_from(
env::var_os("XDG_DATA_HOME").map(PathBuf::from),
env::var_os("HOME").map(PathBuf::from),
spec,
)
}
fn managed_install_root_from(
xdg_data_home: Option<PathBuf>,
home: Option<PathBuf>,
spec: &PluginSpec,
) -> Result<PathBuf, String> {
let xdg_data_home =
xdg_data_home.filter(|path| !path.as_os_str().is_empty() && path.is_absolute());
let home_data = home
.filter(|path| !path.as_os_str().is_empty() && path.is_absolute())
.map(|path| path.join(".local/share"));
let data_home = xdg_data_home.or(home_data).ok_or_else(|| {
"Lios cannot locate a private data directory. Set XDG_DATA_HOME or HOME to an absolute path."
.to_string()
})?;
Ok(data_home
.join("lios")
.join("plugins")
.join(format!("{}-{}", spec.crate_name, spec.version)))
}
fn prepare_managed_install_root(root: &Path, spec: &PluginSpec) -> Result<(), String> {
match fs::symlink_metadata(root) {
Ok(_) => {}
Err(error) if error.kind() == ErrorKind::NotFound => {
let mut builder = DirBuilder::new();
builder.recursive(true).mode(0o700);
builder.create(root).map_err(|error| {
format!(
"Could not create managed plugin directory '{}': {error}",
root.display()
)
})?;
}
Err(error) => {
return Err(format!(
"Could not inspect managed plugin directory '{}': {error}",
root.display()
));
}
}
if !managed_directory_is_safe(root) {
return Err(format!(
"Managed plugin directory '{}' must be a real, user-owned directory without group/world write access.",
root.display()
));
}
let bin = root.join("bin");
match fs::symlink_metadata(&bin) {
Ok(_) => {}
Err(error) if error.kind() == ErrorKind::NotFound => {
let mut builder = DirBuilder::new();
builder.mode(0o700);
builder.create(&bin).map_err(|error| {
format!(
"Could not create managed plugin bin directory '{}': {error}",
bin.display()
)
})?;
}
Err(error) => {
return Err(format!(
"Could not inspect managed plugin bin directory '{}': {error}",
bin.display()
));
}
}
if !managed_directory_is_safe(&bin) {
return Err(format!(
"Managed plugin bin directory '{}' must be a real, user-owned directory without group/world write access.",
bin.display()
));
}
let binary = bin.join(spec.binary_name);
if path_exists_without_following(&binary) && !managed_executable_is_safe(&binary) {
return Err(format!(
"Existing managed plugin binary '{}' is a symlink, has an unsafe owner/mode, or is not executable.",
binary.display()
));
}
let receipt = root.join(".crates2.json");
if path_exists_without_following(&receipt) && !managed_receipt_is_safe(&receipt) {
return Err(format!(
"Existing Cargo receipt '{}' is a symlink, has an unsafe owner/mode, or is too large.",
receipt.display()
));
}
Ok(())
}
fn detect_installation_at(root: &Path, spec: &PluginSpec) -> PluginInstallation {
match fs::symlink_metadata(root) {
Err(error) if error.kind() == ErrorKind::NotFound => return PluginInstallation::Missing,
Err(_) => return PluginInstallation::Unmanaged(root.to_path_buf()),
Ok(_) if !managed_directory_is_safe(root) => {
return PluginInstallation::Unmanaged(root.to_path_buf());
}
Ok(_) => {}
}
let bin = root.join("bin");
let receipt = root.join(".crates2.json");
match fs::symlink_metadata(&bin) {
Err(error) if error.kind() == ErrorKind::NotFound => {
return if path_exists_without_following(&receipt) {
PluginInstallation::Unmanaged(receipt)
} else {
PluginInstallation::Missing
};
}
Err(_) => return PluginInstallation::Unmanaged(bin),
Ok(_) if !managed_directory_is_safe(&bin) => {
return PluginInstallation::Unmanaged(bin);
}
Ok(_) => {}
}
let binary = bin.join(spec.binary_name);
match fs::symlink_metadata(&binary) {
Err(error) if error.kind() == ErrorKind::NotFound => {
return if path_exists_without_following(&receipt) {
PluginInstallation::Unmanaged(receipt)
} else {
PluginInstallation::Missing
};
}
Err(_) => return PluginInstallation::Unmanaged(binary),
Ok(_) if !managed_executable_is_safe(&binary) => {
return PluginInstallation::Unmanaged(binary);
}
Ok(_) => {}
}
if !managed_receipt_is_safe(&receipt) {
return PluginInstallation::Unmanaged(receipt);
}
if !cargo_metadata_matches(root, spec) {
return PluginInstallation::Unmanaged(binary);
}
PluginInstallation::Managed(binary)
}
fn path_exists_without_following(path: &Path) -> bool {
fs::symlink_metadata(path).is_ok()
}
fn managed_directory_is_safe(path: &Path) -> bool {
let Ok(metadata) = fs::symlink_metadata(path) else {
return false;
};
metadata.file_type().is_dir()
&& metadata.uid() == effective_uid()
&& metadata.mode() & 0o022 == 0
&& metadata.mode() & 0o700 == 0o700
}
fn managed_executable_is_safe(path: &Path) -> bool {
let Ok(metadata) = fs::symlink_metadata(path) else {
return false;
};
metadata.file_type().is_file()
&& metadata.uid() == effective_uid()
&& metadata.nlink() == 1
&& metadata.mode() & 0o022 == 0
&& metadata.mode() & 0o500 == 0o500
}
fn managed_receipt_is_safe(path: &Path) -> bool {
let Ok(metadata) = fs::symlink_metadata(path) else {
return false;
};
metadata.file_type().is_file()
&& metadata.uid() == effective_uid()
&& metadata.nlink() == 1
&& metadata.mode() & 0o022 == 0
&& metadata.mode() & 0o400 == 0o400
&& metadata.len() <= MAX_CARGO_RECEIPT_BYTES
}
fn effective_uid() -> u32 {
unsafe { libc::geteuid() }
}
fn cargo_metadata_matches(root: &Path, spec: &PluginSpec) -> bool {
let receipt = root.join(".crates2.json");
if !managed_receipt_is_safe(&receipt) {
return false;
}
let Ok(contents) = fs::read(receipt) else {
return false;
};
let Ok(metadata) = serde_json::from_slice::<CargoInstallMetadata>(&contents) else {
return false;
};
let expected_identity = format!(
"{} {} (registry+https://github.com/rust-lang/crates.io-index)",
spec.crate_name, spec.version
);
let mut providers = metadata
.installs
.iter()
.filter(|(_, entry)| entry.bins.iter().any(|binary| binary == spec.binary_name));
matches!(providers.next(), Some((identity, _)) if identity == &expected_identity)
&& providers.next().is_none()
}
fn find_executable_in(binary: &str, directories: &[PathBuf]) -> Option<PathBuf> {
if binary.is_empty() || binary.as_bytes().contains(&b'/') {
return None;
}
directories.iter().find_map(|directory| {
let candidate = directory.join(binary);
is_executable(&candidate).then_some(candidate)
})
}
fn is_executable(path: &Path) -> bool {
fs::metadata(path)
.is_ok_and(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
}
fn bounded_message(message: &str) -> String {
bounded_message_with_limit(message, 180)
}
fn bounded_message_with_limit(message: &str, max_chars: usize) -> String {
let sanitized = strip_terminal_controls(message);
let normalized = sanitized.split_whitespace().collect::<Vec<_>>().join(" ");
let mut bounded = normalized.chars().take(max_chars).collect::<String>();
if normalized.chars().count() > max_chars {
bounded.push('…');
}
bounded
}
fn strip_terminal_controls(message: &str) -> String {
#[derive(Clone, Copy)]
enum EscapeState {
Text,
Escape,
Csi,
Osc,
OscEscape,
}
let mut output = String::with_capacity(message.len());
let mut state = EscapeState::Text;
for character in message.chars() {
state = match state {
EscapeState::Text if character == '\u{1b}' => EscapeState::Escape,
EscapeState::Text => {
if character.is_control() {
output.push(' ');
} else {
output.push(character);
}
EscapeState::Text
}
EscapeState::Escape => match character {
'[' => EscapeState::Csi,
']' => EscapeState::Osc,
'\u{1b}' => EscapeState::Escape,
_ => EscapeState::Text,
},
EscapeState::Csi if ('@'..='~').contains(&character) => EscapeState::Text,
EscapeState::Csi => EscapeState::Csi,
EscapeState::Osc if character == '\u{7}' => EscapeState::Text,
EscapeState::Osc if character == '\u{1b}' => EscapeState::OscEscape,
EscapeState::Osc => EscapeState::Osc,
EscapeState::OscEscape if character == '\\' => EscapeState::Text,
EscapeState::OscEscape if character == '\u{1b}' => EscapeState::OscEscape,
EscapeState::OscEscape => EscapeState::Osc,
};
}
output
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::OpenOptions;
use std::os::unix::fs::{OpenOptionsExt, symlink};
use std::time::{SystemTime, UNIX_EPOCH};
const VALID_RECEIPT: &str = r#"{"installs":{"lios-bar 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)":{"bins":["lios-bar"]}}}"#;
fn temporary_path(label: &str) -> PathBuf {
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
env::temp_dir().join(format!(
"lios-plugin-{label}-{}-{nonce}",
std::process::id()
))
}
fn create_private_directory(path: &Path) {
fs::create_dir_all(path).unwrap();
fs::set_permissions(path, fs::Permissions::from_mode(0o700)).unwrap();
}
fn create_executable(path: &Path) {
OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o700)
.open(path)
.unwrap();
}
fn create_managed_installation(root: &Path) -> PathBuf {
create_private_directory(root);
let bin = root.join("bin");
create_private_directory(&bin);
let binary = bin.join("lios-bar");
create_executable(&binary);
fs::write(root.join(".crates2.json"), VALID_RECEIPT).unwrap();
fs::set_permissions(
root.join(".crates2.json"),
fs::Permissions::from_mode(0o600),
)
.unwrap();
binary
}
#[test]
fn official_install_is_exact_locked_and_never_uses_a_shell() {
let spec = &OFFICIAL_PLUGINS[0];
let arguments = install_arguments(
Path::new("/usr/bin/cargo"),
Path::new("/home/test/.local/share/lios/plugins/lios-bar-0.1.1"),
spec,
);
let actual = arguments
.iter()
.map(|argument| argument.to_string_lossy().into_owned())
.collect::<Vec<_>>();
assert_eq!(
actual,
[
"/usr/bin/cargo",
"install",
"--root",
"/home/test/.local/share/lios/plugins/lios-bar-0.1.1",
"--locked",
"--force",
"--registry",
"crates-io",
"--version",
"=0.1.1",
"lios-bar"
]
);
assert!(!actual.iter().any(|argument| {
matches!(
argument.as_str(),
"sh" | "/bin/sh" | "bash" | "/bin/bash" | "-c"
)
}));
}
#[test]
fn executable_discovery_rejects_paths_and_requires_execute_permission() {
let directory = temporary_path("cargo-path");
fs::create_dir_all(&directory).unwrap();
let binary = directory.join("lios-bar");
OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o600)
.open(&binary)
.unwrap();
assert_eq!(
find_executable_in("lios-bar", std::slice::from_ref(&directory)),
None
);
fs::set_permissions(&binary, fs::Permissions::from_mode(0o700)).unwrap();
assert_eq!(
find_executable_in("lios-bar", std::slice::from_ref(&directory)),
Some(binary.clone())
);
assert_eq!(
find_executable_in("../lios-bar", std::slice::from_ref(&directory)),
None
);
fs::remove_dir_all(directory).unwrap();
}
#[test]
fn managed_root_prefers_absolute_xdg_data_home_and_versions_each_plugin() {
let spec = &OFFICIAL_PLUGINS[0];
assert_eq!(
managed_install_root_from(
Some(PathBuf::from("/var/lib/test-data")),
Some(PathBuf::from("/home/test")),
spec,
)
.unwrap(),
PathBuf::from("/var/lib/test-data/lios/plugins/lios-bar-0.1.1")
);
assert_eq!(
managed_install_root_from(
Some(PathBuf::from("relative-data")),
Some(PathBuf::from("/home/test")),
spec,
)
.unwrap(),
PathBuf::from("/home/test/.local/share/lios/plugins/lios-bar-0.1.1")
);
assert!(
managed_install_root_from(None, Some(PathBuf::from("relative-home")), spec).is_err()
);
}
#[test]
fn preparation_creates_private_root_and_bin_directories() {
let container = temporary_path("prepare");
create_private_directory(&container);
let root = container.join("lios/plugins/lios-bar-0.1.1");
prepare_managed_install_root(&root, &OFFICIAL_PLUGINS[0]).unwrap();
assert!(managed_directory_is_safe(&root));
assert!(managed_directory_is_safe(&root.join("bin")));
assert_eq!(fs::symlink_metadata(&root).unwrap().mode() & 0o777, 0o700);
assert_eq!(
fs::symlink_metadata(root.join("bin")).unwrap().mode() & 0o777,
0o700
);
fs::remove_dir_all(container).unwrap();
}
#[test]
fn detection_ignores_path_lookalikes_and_uses_only_the_managed_root() {
let container = temporary_path("path-spoof");
create_private_directory(&container);
let spoof_dir = container.join("spoof-bin");
create_private_directory(&spoof_dir);
let spoof = spoof_dir.join("lios-bar");
create_executable(&spoof);
assert_eq!(
find_executable_in("lios-bar", std::slice::from_ref(&spoof_dir)),
Some(spoof)
);
let managed_root = container.join("managed/lios-bar-0.1.1");
assert_eq!(
detect_installation_at(&managed_root, &OFFICIAL_PLUGINS[0]),
PluginInstallation::Missing
);
fs::remove_dir_all(container).unwrap();
}
#[test]
fn detection_accepts_only_safe_owned_managed_artifacts() {
let root = temporary_path("safe-install");
let binary = create_managed_installation(&root);
assert_eq!(
detect_installation_at(&root, &OFFICIAL_PLUGINS[0]),
PluginInstallation::Managed(binary.clone())
);
fs::set_permissions(&binary, fs::Permissions::from_mode(0o777)).unwrap();
assert_eq!(
detect_installation_at(&root, &OFFICIAL_PLUGINS[0]),
PluginInstallation::Unmanaged(binary.clone())
);
fs::set_permissions(&binary, fs::Permissions::from_mode(0o700)).unwrap();
let receipt = root.join(".crates2.json");
fs::set_permissions(&receipt, fs::Permissions::from_mode(0o666)).unwrap();
assert_eq!(
detect_installation_at(&root, &OFFICIAL_PLUGINS[0]),
PluginInstallation::Unmanaged(receipt.clone())
);
fs::set_permissions(&receipt, fs::Permissions::from_mode(0o600)).unwrap();
let bin = root.join("bin");
fs::set_permissions(&bin, fs::Permissions::from_mode(0o777)).unwrap();
assert_eq!(
detect_installation_at(&root, &OFFICIAL_PLUGINS[0]),
PluginInstallation::Unmanaged(bin.clone())
);
fs::set_permissions(&bin, fs::Permissions::from_mode(0o700)).unwrap();
fs::set_permissions(&root, fs::Permissions::from_mode(0o777)).unwrap();
assert_eq!(
detect_installation_at(&root, &OFFICIAL_PLUGINS[0]),
PluginInstallation::Unmanaged(root.clone())
);
fs::set_permissions(&root, fs::Permissions::from_mode(0o700)).unwrap();
fs::remove_dir_all(root).unwrap();
}
#[test]
fn detection_rejects_symlinked_root_bin_binary_and_receipt() {
let container = temporary_path("symlinks");
create_private_directory(&container);
let real_root = container.join("real-root");
create_managed_installation(&real_root);
let linked_root = container.join("linked-root");
symlink(&real_root, &linked_root).unwrap();
assert_eq!(
detect_installation_at(&linked_root, &OFFICIAL_PLUGINS[0]),
PluginInstallation::Unmanaged(linked_root.clone())
);
let bin_root = container.join("bin-root");
create_private_directory(&bin_root);
let external_bin = container.join("external-bin");
create_private_directory(&external_bin);
let linked_bin = bin_root.join("bin");
symlink(&external_bin, &linked_bin).unwrap();
assert_eq!(
detect_installation_at(&bin_root, &OFFICIAL_PLUGINS[0]),
PluginInstallation::Unmanaged(linked_bin)
);
let binary_root = container.join("binary-root");
create_private_directory(&binary_root);
let binary_bin = binary_root.join("bin");
create_private_directory(&binary_bin);
let external_binary = container.join("external-binary");
create_executable(&external_binary);
let linked_binary = binary_bin.join("lios-bar");
symlink(&external_binary, &linked_binary).unwrap();
assert_eq!(
detect_installation_at(&binary_root, &OFFICIAL_PLUGINS[0]),
PluginInstallation::Unmanaged(linked_binary)
);
let receipt_root = container.join("receipt-root");
let receipt_binary = create_managed_installation(&receipt_root);
let receipt = receipt_root.join(".crates2.json");
fs::remove_file(&receipt).unwrap();
let external_receipt = container.join("external-receipt");
fs::write(&external_receipt, VALID_RECEIPT).unwrap();
fs::set_permissions(&external_receipt, fs::Permissions::from_mode(0o600)).unwrap();
symlink(&external_receipt, &receipt).unwrap();
assert_eq!(
detect_installation_at(&receipt_root, &OFFICIAL_PLUGINS[0]),
PluginInstallation::Unmanaged(receipt)
);
assert!(managed_executable_is_safe(&receipt_binary));
fs::remove_dir_all(container).unwrap();
}
#[test]
fn error_messages_are_single_line_and_bounded() {
let message = format!("first line\n\u{1b}[31mred\u{1b}[0m\u{8}{}", "x".repeat(300));
let bounded = bounded_message(&message);
assert!(!bounded.contains('\n'));
assert!(!bounded.contains('\u{1b}'));
assert!(!bounded.contains("[31m"));
assert!(!bounded.contains('\u{8}'));
assert!(bounded.chars().count() <= 181);
}
#[test]
fn streamed_cargo_diagnostics_keep_only_a_sanitized_bounded_tail() {
let mut diagnostic = BoundedDiagnostic::default();
diagnostic.push(&vec![b'x'; MAX_CARGO_STDERR_BYTES + 128]);
diagnostic.push(b"\n\x1b[31mfinal cargo error\x1b[0m\x1b]8;;file:///secret\x07\n");
assert_eq!(diagnostic.bytes.len(), MAX_CARGO_STDERR_BYTES);
assert!(diagnostic.truncated);
let message = diagnostic.message().unwrap();
assert!(message.starts_with("… "));
assert!(message.ends_with("final cargo error"));
assert!(!message.contains('\u{1b}'));
assert!(!message.contains("secret"));
assert!(message.chars().count() <= 180);
}
#[test]
fn process_group_identifiers_must_be_safe_positive_process_ids() {
assert_eq!(process_group_from_identifier("4321"), Some(4321));
for invalid in ["", "cargo", "-42", "0", "1", "4294967296"] {
assert_eq!(process_group_from_identifier(invalid), None);
}
}
#[test]
fn first_install_stop_reason_wins() {
let reason = Cell::new(None);
assert!(claim_stop_reason(&reason, InstallStopReason::Cancelled));
assert!(!claim_stop_reason(&reason, InstallStopReason::TimedOut));
assert_eq!(reason.get(), Some(InstallStopReason::Cancelled));
}
#[test]
fn cargo_metadata_requires_exact_registry_version_and_binary() {
let root = temporary_path("metadata");
let bin = root.join("bin");
create_private_directory(&bin);
let spec = &OFFICIAL_PLUGINS[0];
fs::write(
root.join(".crates2.json"),
r#"{"installs":{"lios-bar 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)":{"bins":["lios-bar"]}}}"#,
)
.unwrap();
assert!(!cargo_metadata_matches(&root, spec));
fs::write(
root.join(".crates2.json"),
r#"{"installs":{"lios-bar 0.1.1 (path+file:///tmp/LiosBar)":{"bins":["lios-bar"]}}}"#,
)
.unwrap();
assert!(!cargo_metadata_matches(&root, spec));
fs::write(
root.join(".crates2.json"),
r#"{"installs":{"lios-bar 0.1.1 (registry+https://packages.example.invalid/index)":{"bins":["lios-bar"]}}}"#,
)
.unwrap();
assert!(!cargo_metadata_matches(&root, spec));
fs::write(
root.join(".crates2.json"),
r#"{"installs":{"lios-bar 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)-lookalike":{"bins":["lios-bar"]}}}"#,
)
.unwrap();
assert!(!cargo_metadata_matches(&root, spec));
fs::write(
root.join(".crates2.json"),
r#"{"installs":{"lios-bar 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)":{"bins":["lios-bar"]},"lookalike 9.9.9 (registry+https://github.com/rust-lang/crates.io-index)":{"bins":["lios-bar"]}}}"#,
)
.unwrap();
assert!(!cargo_metadata_matches(&root, spec));
fs::write(
root.join(".crates2.json"),
r#"{"installs":{"lios-bar 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)":{"bins":["lios-bar"]}}}"#,
)
.unwrap();
assert!(cargo_metadata_matches(&root, spec));
fs::remove_dir_all(root).unwrap();
}
}