From 8e7c6bcf688077ab8ad9e563978a0bf3fa2f55f4 Mon Sep 17 00:00:00 2001 From: Hadi Chokr Date: Thu, 23 Jan 2025 21:47:16 +0100 Subject: [PATCH] Enable saving read-only/root files via pkexec (Fixes #249) (#304) * Enable saving read-only/root files via pkexec (Fixes #249) This update addresses the issue where users couldn't open Cosmic Edit as root. It allows users to save read-only or root files using pkexec for privilege escalation, all within the graphical interface of Cosmic Edit, eliminating the need to open the terminal. Changes: Implemented pkexec functionality for saving read-only/root files. Added a confirmation dialog for file modifications. This feature resolves issue #249 and streamlines the process, ensuring a smoother experience for users managing protected files directly within Cosmic Edit. * Secure pkexec handling with piped stdin and escape safety Enhanced permission handling by using `pkexec` with `tee` and piped stdin. This implementation avoids shell injection risks and ensures proper handling of special escape characters in the input. The approach securely writes text content to files with elevated privileges while maintaining robustness against potentially malicious inputs. * Update tab.rs * Log Errors but dont crash * Update tab.rs * Update tab.rs * Clean up after child (sounds weird...) --- src/tab.rs | 80 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/src/tab.rs b/src/tab.rs index 93e95e2..86c4524 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -9,11 +9,14 @@ use cosmic_text::{Attrs, Buffer, Cursor, Edit, Selection, Shaping, SyntaxEditor, use notify::Watcher; use regex::Regex; use std::{ + io::Write, fs, path::PathBuf, + process::{Command, Stdio}, sync::{Arc, Mutex}, }; + use crate::{fl, git::GitDiff, Config, SYNTAX_SYSTEM}; pub enum Tab { @@ -45,7 +48,7 @@ pub struct EditorTab { impl EditorTab { pub fn new(config: &Config) -> Self { //TODO: do not repeat, used in App::init - let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Monospace); + let attrs = Attrs::new().family(cosmic_text::Family::Monospace); let mut buffer = Buffer::new_empty(config.metrics()); buffer.set_text( @@ -144,26 +147,65 @@ impl EditorTab { } pub fn save(&mut self) { - if let Some(path) = &self.path_opt { - let mut editor = self.editor.lock().unwrap(); - let mut text = String::new(); - editor.with_buffer(|buffer| { - for line in buffer.lines.iter() { - text.push_str(line.text()); - text.push_str(line.ending().as_str()); - } - }); - match fs::write(path, text) { - Ok(()) => { - editor.save_point(); - log::info!("saved {:?}", path); - } - Err(err) => { - log::error!("failed to save {:?}: {}", path, err); + if let Some(path) = &self.path_opt { + let mut editor = self.editor.lock().unwrap(); + let mut text = String::new(); + + editor.with_buffer(|buffer| { + for line in buffer.lines.iter() { + text.push_str(line.text()); + text.push_str(line.ending().as_str()); + } + }); + + match fs::write(path, &text) { + Ok(()) => { + editor.save_point(); + log::info!("saved {:?}", path); + } + Err(err) => { + if err.kind() == std::io::ErrorKind::PermissionDenied { + log::warn!("Permission denied. Attempting to save with pkexec."); + + if let Ok(mut output) = Command::new("pkexec") + .arg("tee") + .arg(path) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) // Redirect stdout to /dev/null + .stderr(Stdio::inherit()) // Retain stderr for error visibility + .spawn() + { + if let Some(mut stdin) = output.stdin.take() { + if let Err(e) = stdin.write_all(text.as_bytes()) { + log::error!("Failed to write to stdin: {}", e); + } + } else { + log::error!("Failed to access stdin of pkexec process."); + } + + // Ensure the child process is reaped + match output.wait() { + Ok(status) => { + if status.success() { + // Mark the editor's state as saved if the process succeeds + editor.save_point(); + log::info!("File saved successfully with pkexec."); + } else { + log::error!("pkexec process exited with a non-zero status: {:?}", status); + } + } + Err(e) => { + log::error!("Failed to wait on pkexec process: {}", e); + } + } + } else { + log::error!("Failed to spawn pkexec process. Check permissions or path."); + } } } - } else { - log::warn!("tab has no path yet"); + } + } else { + log::warn!("tab has no path yet"); } }