git.delta.rocks / fleet / refs/commits / 087938500f2e

difftreelog

source

remowt/cmds/remowt-agent/src/editor.rs4.0 KiBsourcehistory
1use std::env::{current_dir, temp_dir};2use std::path::Path;3use std::time::Duration;4use std::{fs, io};56use anyhow::{bail, Context as _};7use nix::libc;8use remowt_link_shared::editor::EditorEndpointsClient;9use tokio::process::Command;10use zbus::{fdo, interface, proxy, Connection};1112use remowt_link_shared::BifConfig;1314const BUS_NAME: &str = "lach.RemowtEditor";15const SERVICE_PATH: &str = "/lach/Editor";1617pub struct EditorService {18	editor: EditorEndpointsClient<BifConfig>,19}2021#[interface(name = "lach.RemowtEditor")]22impl EditorService {23	/// Attach the User's GUI to the nvim server at `socket_path` (on the remote),24	/// blocking until the user is done.25	async fn edit(&self, socket_path: String) -> fdo::Result<()> {26		self.editor27			.open_editor(socket_path)28			.await29			.map_err(|e| fdo::Error::Failed(format!("requesting editor on the User: {e}")))?30			.map_err(|e| fdo::Error::Failed(format!("editor failed: {e}")))?;31		Ok(())32	}3334	async fn forward_tcp(&self, addr: String) -> fdo::Result<u16> {35		let local = self36			.editor37			.expose_tcp(addr)38			.await39			.map_err(|e| fdo::Error::Failed(format!("requesting tcp forward on the User: {e}")))?40			.map_err(|e| fdo::Error::Failed(format!("tcp forward failed: {e}")))?;41		Ok(local)42	}4344	async fn forward_udp(&self, addr: String) -> fdo::Result<u16> {45		let local = self46			.editor47			.expose_udp(addr)48			.await49			.map_err(|e| fdo::Error::Failed(format!("requesting udp forward on the User: {e}")))?50			.map_err(|e| fdo::Error::Failed(format!("udp forward failed: {e}")))?;51		Ok(local)52	}53}5455pub async fn serve(56	conn: &Connection,57	editor: EditorEndpointsClient<BifConfig>,58) -> anyhow::Result<()> {59	conn.object_server()60		.at(SERVICE_PATH, EditorService { editor })61		.await?;62	conn.request_name(BUS_NAME).await?;63	Ok(())64}6566#[proxy(interface = "lach.RemowtEditor")]67trait RemowtEditor {68	async fn edit(&self, socket_path: &str) -> fdo::Result<()>;69	async fn forward_tcp(&self, addr: &str) -> fdo::Result<u16>;70	async fn forward_udp(&self, addr: &str) -> fdo::Result<u16>;71}7273pub async fn forward(udp: bool, addr: String) -> anyhow::Result<()> {74	let conn = Connection::session()75		.await76		.context("connecting to the session bus (DBUS_SESSION_BUS_ADDRESS)")?;77	let proxy = RemowtEditorProxy::builder(&conn)78		.destination(BUS_NAME)?79		.path(SERVICE_PATH)?80		.build()81		.await?;82	let local = if udp {83		proxy.forward_udp(&addr).await?84	} else {85		proxy.forward_tcp(&addr).await?86	};87	println!("{local}");88	Ok(())89}9091pub async fn edit(path: String) -> anyhow::Result<()> {92	let path = Path::new(&path);93	let abs = if path.is_absolute() {94		path.to_path_buf()95	} else {96		current_dir()?.join(path)97	};9899	let sock = temp_dir().join(format!("remowt-nvim-{}.sock", uuid::Uuid::new_v4()));100	let sock_str = sock101		.to_str()102		.context("temp socket path is not utf-8")?103		.to_owned();104105	let mut child = Command::new("nvim");106	child107		.arg("--headless")108		.arg("--listen")109		.arg(&sock)110		.arg("--")111		.arg(&abs)112		.kill_on_drop(true);113	// SAFETY: only an async-signal-safe `prctl` call.114	unsafe {115		child.pre_exec(|| {116			if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL as libc::c_ulong) != 0 {117				return Err(io::Error::last_os_error());118			}119			Ok(())120		});121	}122	let mut child = child.spawn().context("spawning nvim")?;123124	wait_for_socket(&sock)125		.await126		.context("nvim did not start its server")?;127128	let conn = Connection::session()129		.await130		.context("connecting to the session bus (DBUS_SESSION_BUS_ADDRESS)")?;131	let proxy = RemowtEditorProxy::builder(&conn)132		.destination(BUS_NAME)?133		.path(SERVICE_PATH)?134		.build()135		.await?;136	let result = proxy.edit(&sock_str).await;137138	if tokio::time::timeout(Duration::from_secs(2), child.wait())139		.await140		.is_err()141	{142		let _ = child.kill().await;143	}144	let _ = fs::remove_file(&sock);145146	result?;147	Ok(())148}149150async fn wait_for_socket(path: &Path) -> anyhow::Result<()> {151	for _ in 0..200 {152		if tokio::fs::try_exists(path).await.unwrap_or(false) {153			return Ok(());154		}155		tokio::time::sleep(Duration::from_millis(50)).await;156	}157	bail!("timed out waiting for {}", path.display())158}