Implement sudo for linux
This commit is contained in:
Generated
+1
@@ -4250,6 +4250,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-channel",
|
||||
"libc",
|
||||
"libcosmic",
|
||||
"libredox",
|
||||
"pkgar",
|
||||
|
||||
+6
-1
@@ -5,7 +5,6 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
libredox = "0.1"
|
||||
pkgar = "0.2"
|
||||
pkgar-core = "0.2"
|
||||
pkgar-keys = "0.2"
|
||||
@@ -15,6 +14,12 @@ toml = "0.8"
|
||||
# TODO: remove after pkgutils update
|
||||
futures-channel = "0.3.32"
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
libredox = "0.1"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[dependencies.libcosmic]
|
||||
git = "https://github.com/pop-os/libcosmic.git"
|
||||
# use the same rev with other cosmic app
|
||||
|
||||
+71
-122
@@ -2,9 +2,12 @@ use anyhow::format_err;
|
||||
use cosmic::{
|
||||
app::{self, Task},
|
||||
iced::{
|
||||
self, executor, futures::sink::SinkExt, widget::row, window, Alignment, Size, Subscription,
|
||||
self, executor,
|
||||
futures::sink::SinkExt,
|
||||
widget::{row, text_input},
|
||||
window, Alignment, Size, Subscription,
|
||||
},
|
||||
widget::{button, column, progress_bar, radio, space, text, text_input},
|
||||
widget::{button, column, progress_bar, radio, space, text},
|
||||
Application, ApplicationExt, Core, Element,
|
||||
};
|
||||
use futures_channel::mpsc;
|
||||
@@ -21,6 +24,9 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
mod sys;
|
||||
pub use sys::*;
|
||||
|
||||
fn main() -> iced::Result {
|
||||
let mut settings = app::Settings::default();
|
||||
settings = settings.size(Size::new(608.0, 416.0));
|
||||
@@ -28,91 +34,6 @@ fn main() -> iced::Result {
|
||||
app::run::<Window>(settings, ())
|
||||
}
|
||||
|
||||
fn sudo(password: &str) -> Result<(), String> {
|
||||
let file = libredox::call::open("/scheme/sudo", libredox::flag::O_CLOEXEC, 0)
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
libredox::call::write(file, password.as_bytes()).map_err(|err| err.to_string())?;
|
||||
|
||||
// FIXME move to libredox
|
||||
unsafe extern "C" {
|
||||
safe fn redox_cur_procfd_v0() -> usize;
|
||||
}
|
||||
|
||||
// Elevate privileges of our own process with help from the sudo daemon
|
||||
syscall::sendfd(
|
||||
file,
|
||||
syscall::dup(redox_cur_procfd_v0(), &[]).map_err(|err| err.to_string())?,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn disk_paths() -> Result<Vec<(String, u64)>, String> {
|
||||
let mut schemes = Vec::new();
|
||||
match fs::read_dir("/scheme/") {
|
||||
Ok(entries) => {
|
||||
for entry_res in entries {
|
||||
if let Ok(entry) = entry_res {
|
||||
let path = entry.path();
|
||||
if let Ok(path_str) = path.into_os_string().into_string() {
|
||||
let scheme = path_str.trim_start_matches("/scheme/").trim_matches('/');
|
||||
if scheme.starts_with("disk") {
|
||||
if scheme == "disk/live" {
|
||||
// Skip live disks
|
||||
continue;
|
||||
}
|
||||
|
||||
schemes.push(format!("/scheme/{}", scheme));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!("failed to list schemes: {}", err));
|
||||
}
|
||||
}
|
||||
|
||||
let mut paths = Vec::new();
|
||||
for scheme in schemes {
|
||||
let is_dir = fs::metadata(&scheme).map(|x| x.is_dir()).unwrap_or(false);
|
||||
if is_dir {
|
||||
match fs::read_dir(&scheme) {
|
||||
Ok(entries) => {
|
||||
for entry_res in entries {
|
||||
if let Ok(entry) = entry_res {
|
||||
if let Ok(file_name) = entry.file_name().into_string() {
|
||||
if file_name.contains('p') {
|
||||
// Skip partitions
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Ok(path) = entry.path().into_os_string().into_string() {
|
||||
if let Ok(metadata) = entry.metadata() {
|
||||
let size = metadata.len();
|
||||
if size > 0 {
|
||||
paths.push((path, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!("failed to list '{}': {}", scheme, err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
const KIB: u64 = 1024;
|
||||
const MIB: u64 = 1024 * KIB;
|
||||
const GIB: u64 = 1024 * MIB;
|
||||
@@ -465,7 +386,7 @@ enum Message {
|
||||
struct Window {
|
||||
core: Core,
|
||||
page: Page,
|
||||
disk_paths: Vec<(String, u64)>,
|
||||
disk_paths: Vec<(String, bool, u64)>,
|
||||
worker_opt: Option<Worker>,
|
||||
}
|
||||
|
||||
@@ -526,15 +447,10 @@ impl Application for Window {
|
||||
const APP_ID: &'static str = "org.redox-os.InstallerGui";
|
||||
|
||||
fn init(core: Core, _flags: ()) -> (Self, Task<Message>) {
|
||||
let uid = libredox::call::geteuid().unwrap();
|
||||
let (page, disk_paths) = if uid == 0 {
|
||||
//TODO: load in background
|
||||
match disk_paths() {
|
||||
Ok(disk_paths) => (Page::Disk(None), disk_paths),
|
||||
Err(err) => (Page::Error(err), Vec::new()),
|
||||
}
|
||||
} else {
|
||||
(Page::Sudo(String::new()), Vec::new())
|
||||
//TODO: load in background
|
||||
let (page, disk_paths) = match disk_paths() {
|
||||
Ok(disk_paths) => (Page::Disk(None), disk_paths),
|
||||
Err(err) => (Page::Error(err), Vec::new()),
|
||||
};
|
||||
|
||||
let mut app = Self {
|
||||
@@ -565,28 +481,34 @@ impl Application for Window {
|
||||
self.page = Page::Sudo(password);
|
||||
}
|
||||
Message::SudoSubmit => {
|
||||
#[cfg(target_os = "redox")]
|
||||
if let Page::Sudo(password) = &self.page {
|
||||
//TODO: run async?
|
||||
match sudo(password) {
|
||||
match ask_root(&password) {
|
||||
Ok(()) => {
|
||||
(self.page, self.disk_paths) = match disk_paths() {
|
||||
Ok(disk_paths) => (Page::Disk(None), disk_paths),
|
||||
Err(err) => (Page::Error(err), Vec::new()),
|
||||
};
|
||||
self.page = Page::Disk(None);
|
||||
}
|
||||
Err(err) => {
|
||||
//TODO: show error in GUI
|
||||
eprintln!("{err}");
|
||||
self.page = Page::Sudo(String::new());
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
match ask_root() {
|
||||
Ok(()) => {
|
||||
if let Some(window_id) = self.core.main_window_id() {
|
||||
return window::close(window_id);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("{err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::DiskChoose(disk_i) => {
|
||||
self.page = Page::Disk(Some(disk_i));
|
||||
}
|
||||
Message::DiskConfirm(disk_i) => match self.disk_paths.get(disk_i) {
|
||||
Some((disk_path, _disk_size)) => match &self.worker_opt {
|
||||
Some((disk_path, _is_partition, _disk_size)) => match &self.worker_opt {
|
||||
Some(worker) => match worker.command_sender.send((disk_path.clone(), None)) {
|
||||
Ok(()) => self.page = Page::Install(0, format!("Starting install...")),
|
||||
Err(err) => {
|
||||
@@ -631,34 +553,61 @@ impl Application for Window {
|
||||
widgets.push(text("Enter your password:").into());
|
||||
widgets.push(
|
||||
text_input("", password)
|
||||
.password()
|
||||
.on_input(Message::SudoInput)
|
||||
.on_submit(|_| Message::SudoSubmit)
|
||||
.secure(true)
|
||||
.on_submit(Message::SudoSubmit)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
Page::Disk(disk_i_opt) => {
|
||||
if !self.disk_paths.is_empty() {
|
||||
widgets.push(text("Choose a drive:").size(24).into());
|
||||
if is_root() {
|
||||
widgets.push(text("Choose a drive:").size(24).into());
|
||||
|
||||
for (disk_i, (disk_path, disk_size)) in self.disk_paths.iter().enumerate() {
|
||||
widgets.push(
|
||||
row![
|
||||
radio(text(disk_path), disk_i, *disk_i_opt, Message::DiskChoose),
|
||||
space::horizontal(),
|
||||
text(format_size(*disk_size)),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
for (disk_i, (disk_path, is_partition, disk_size)) in
|
||||
self.disk_paths.iter().enumerate()
|
||||
{
|
||||
if !is_partition {
|
||||
widgets.push(
|
||||
row![
|
||||
radio(
|
||||
text(disk_path),
|
||||
disk_i,
|
||||
*disk_i_opt,
|
||||
Message::DiskChoose
|
||||
),
|
||||
space::horizontal(),
|
||||
text(format_size(*disk_size)),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(disk_i) = *disk_i_opt {
|
||||
widgets.push(space::vertical().into());
|
||||
widgets.push(
|
||||
row![
|
||||
space::horizontal(),
|
||||
button::destructive("Confirm")
|
||||
.on_press(Message::DiskConfirm(disk_i)),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
#[cfg(target_os = "linux")]
|
||||
let page = Message::SudoSubmit;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
let page = Message::SudoInput(String::new());
|
||||
|
||||
if let Some(disk_i) = *disk_i_opt {
|
||||
widgets.push(space::vertical().into());
|
||||
widgets.push(
|
||||
row![
|
||||
text("Ask superuser permission to install into drives"),
|
||||
space::horizontal(),
|
||||
button::destructive("Confirm")
|
||||
.on_press(Message::DiskConfirm(disk_i)),
|
||||
button::suggested("Ask root access").on_press(page),
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(target_os = "redox")]
|
||||
mod redox;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use linux::*;
|
||||
#[cfg(target_os = "redox")]
|
||||
pub use redox::*;
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "redox")))]
|
||||
compile_error!("Platform is not ported");
|
||||
@@ -0,0 +1,76 @@
|
||||
use std::{fs, os::unix::process::CommandExt, process::Command};
|
||||
|
||||
pub fn ask_root() -> Result<(), String> {
|
||||
let Ok(exe_path) = std::env::current_exe() else {
|
||||
return Err(format!("Could not determine current_exe"));
|
||||
};
|
||||
let mut cmd = Command::new("pkexec");
|
||||
cmd.arg("env");
|
||||
for (key, value) in std::env::vars() {
|
||||
if [
|
||||
"DISPLAY",
|
||||
"WAYLAND_DISPLAY",
|
||||
"XAUTHORITY",
|
||||
"XDG_RUNTIME_DIR",
|
||||
"DBUS_SESSION_BUS_ADDRESS",
|
||||
]
|
||||
.contains(&key.as_str())
|
||||
{
|
||||
cmd.arg(format!("{}={}", key, value));
|
||||
}
|
||||
}
|
||||
cmd.arg(exe_path);
|
||||
// will never return unless fail
|
||||
let e = cmd.exec();
|
||||
return Err(format!("Failed to escalate: {e}"));
|
||||
}
|
||||
|
||||
pub fn is_root() -> bool {
|
||||
let euid = unsafe { libc::geteuid() };
|
||||
euid == 0
|
||||
}
|
||||
|
||||
pub fn disk_paths() -> Result<Vec<(String, bool, u64)>, String> {
|
||||
let mut paths = Vec::new();
|
||||
|
||||
let entries = match fs::read_dir("/sys/class/block") {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => return Err(format!("failed to read /sys/class/block: {}", err)),
|
||||
};
|
||||
|
||||
for entry_res in entries {
|
||||
if let Ok(entry) = entry_res {
|
||||
let file_name = entry.file_name();
|
||||
let name = file_name.to_string_lossy();
|
||||
|
||||
if name.starts_with("loop")
|
||||
|| name.starts_with("ram")
|
||||
|| name.starts_with("sr")
|
||||
|| name.starts_with("zram")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let path = entry.path();
|
||||
let is_partition = path.join("partition").exists();
|
||||
let size_path = path.join("size");
|
||||
let Ok(size_str) = fs::read_to_string(&size_path) else {
|
||||
continue;
|
||||
};
|
||||
let Ok(sectors) = size_str.trim().parse::<u64>() else {
|
||||
continue;
|
||||
};
|
||||
// /sys/class/block/*/size is 512 (cmiiw)
|
||||
let size_bytes = sectors * 512;
|
||||
if size_bytes == 0 {
|
||||
continue;
|
||||
}
|
||||
paths.push((format!("/dev/{}", name), is_partition, size_bytes));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
paths.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
Ok(paths)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use std::fs;
|
||||
|
||||
pub fn ask_root(password: &str) -> Result<(), String> {
|
||||
let file = libredox::call::open("/scheme/sudo", libredox::flag::O_CLOEXEC, 0)
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
libredox::call::write(file, password.as_bytes()).map_err(|err| err.to_string())?;
|
||||
|
||||
// FIXME move to libredox
|
||||
unsafe extern "C" {
|
||||
safe fn redox_cur_procfd_v0() -> usize;
|
||||
}
|
||||
|
||||
// Elevate privileges of our own process with help from the sudo daemon
|
||||
syscall::sendfd(
|
||||
file,
|
||||
syscall::dup(redox_cur_procfd_v0(), &[]).map_err(|err| err.to_string())?,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_root() -> bool {
|
||||
let euid = libredox::call::geteuid().unwrap();
|
||||
euid == 0
|
||||
}
|
||||
|
||||
pub fn disk_paths() -> Result<Vec<(String, bool, u64)>, String> {
|
||||
let mut schemes = Vec::new();
|
||||
match fs::read_dir("/scheme/") {
|
||||
Ok(entries) => {
|
||||
for entry_res in entries {
|
||||
if let Ok(entry) = entry_res {
|
||||
let path = entry.path();
|
||||
if let Ok(path_str) = path.into_os_string().into_string() {
|
||||
let scheme = path_str.trim_start_matches("/scheme/").trim_matches('/');
|
||||
if scheme.starts_with("disk") {
|
||||
if scheme == "disk/live" {
|
||||
// Skip live disks
|
||||
continue;
|
||||
}
|
||||
|
||||
schemes.push(format!("/scheme/{}", scheme));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!("failed to list schemes: {}", err));
|
||||
}
|
||||
}
|
||||
|
||||
let mut paths = Vec::new();
|
||||
for scheme in schemes {
|
||||
let is_dir = fs::metadata(&scheme).map(|x| x.is_dir()).unwrap_or(false);
|
||||
if !is_dir {
|
||||
continue;
|
||||
}
|
||||
match fs::read_dir(&scheme) {
|
||||
Ok(entries) => {
|
||||
for entry_res in entries {
|
||||
let Ok(entry) = entry_res else {
|
||||
continue;
|
||||
};
|
||||
let Ok(file_name) = entry.file_name().into_string() else {
|
||||
continue;
|
||||
};
|
||||
let Ok(path) = entry.path().into_os_string().into_string() else {
|
||||
continue;
|
||||
};
|
||||
let Ok(metadata) = entry.metadata() else {
|
||||
continue;
|
||||
};
|
||||
let is_partition = file_name.contains(p);
|
||||
let size = metadata.len();
|
||||
if size == 0 {
|
||||
continue;
|
||||
}
|
||||
paths.push((path, is_partition, size));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!("failed to list {:?}: {}", scheme, err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(paths)
|
||||
}
|
||||
Reference in New Issue
Block a user