From a4f56e0d5e127107bdcd433973b405095034da8b Mon Sep 17 00:00:00 2001 From: David Stancu Date: Tue, 18 May 2021 17:44:48 -0400 Subject: [PATCH 01/28] add csrinfo command --- launchk/src/tui/dialog.rs | 15 +++++++++++++++ launchk/src/tui/omnibox/command.rs | 8 +++++++- launchk/src/tui/omnibox/view.rs | 6 ++---- launchk/src/tui/root.rs | 8 ++++++++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/launchk/src/tui/dialog.rs b/launchk/src/tui/dialog.rs index cdb2b9e..2ed1d35 100644 --- a/launchk/src/tui/dialog.rs +++ b/launchk/src/tui/dialog.rs @@ -14,6 +14,7 @@ use crate::{ tui::omnibox::command::OmniboxCommand, }; use crate::launchd::entry_status::{get_entry_status, LaunchdEntryStatus}; +use xpc_sys::csr::{CsrConfig, csr_check}; /// The XPC error key sometimes contains information that is not necessarily a failure, /// so let's just call it "Notice" until we figure out what to do next? @@ -135,3 +136,17 @@ pub fn domain_session_prompt>( Box::new(cl) } + +pub fn show_csr_info() -> CbSinkMessage { + let csr_flags = (0..11).map(|s| { + let mask = CsrConfig::from_bits(1 << s).expect("Must be in CsrConfig"); + format!("{:?}: {}", mask, unsafe { csr_check(mask.bits()) } == 0) + }).collect::>(); + + Box::new(move |siv| siv.add_layer( + Dialog::new() + .title("CSR Info") + .content(TextView::new(csr_flags.join("\n"))) + .dismiss_button("OK") + )) +} \ No newline at end of file diff --git a/launchk/src/tui/omnibox/command.rs b/launchk/src/tui/omnibox/command.rs index 9c30df8..06755ea 100644 --- a/launchk/src/tui/omnibox/command.rs +++ b/launchk/src/tui/omnibox/command.rs @@ -26,6 +26,7 @@ pub enum OmniboxCommand { fn(DomainType, Option) -> Vec, ), FocusServiceList, + CSRInfo, Quit, } @@ -35,7 +36,7 @@ impl fmt::Display for OmniboxCommand { } } -pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 7] = [ +pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 8] = [ ( "load", "▶️ Load highlighted job", @@ -67,5 +68,10 @@ pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 7] = [ "🔄 Reload highlighted job", OmniboxCommand::Reload, ), + ( + "csrinfo", + "ℹ️ See all CSR flags", + OmniboxCommand::CSRInfo, + ), ("exit", "🚪 see ya!", OmniboxCommand::Quit), ]; diff --git a/launchk/src/tui/omnibox/view.rs b/launchk/src/tui/omnibox/view.rs index f8d8a26..60545ee 100644 --- a/launchk/src/tui/omnibox/view.rs +++ b/launchk/src/tui/omnibox/view.rs @@ -290,11 +290,9 @@ impl OmniboxView { } = &*read; let jtf_ofs = if *mode != OmniboxMode::JobTypeFilter { - // "[sguadl]" - 8 + "[sguadl]".len() } else { - // "[system global user agent daemon loaded]" - 40 + "[system global user agent daemon loaded]".len() }; let mut jtf_ofs = self.last_size.borrow().x - jtf_ofs; diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index be9a74e..f63d5d0 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -20,6 +20,7 @@ use crate::tui::omnibox::subscribed_view::{ use crate::tui::omnibox::view::{OmniboxError, OmniboxEvent, OmniboxView}; use crate::tui::service_list::view::ServiceListView; use crate::tui::sysinfo::SysInfo; +use crate::tui::dialog::show_csr_info; pub type CbSinkMessage = Box; @@ -262,6 +263,13 @@ impl OmniboxSubscriber for RootLayout { .expect("Must show prompt"); Ok(None) } + OmniboxEvent::Command(OmniboxCommand::CSRInfo) => { + self.cbsink_channel + .send(show_csr_info()) + .expect("Must show prompt"); + + Ok(None) + } _ => Ok(None), } } From ef7ece08784d27fcec5224e2763291376788ed8d Mon Sep 17 00:00:00 2001 From: David Stancu Date: Tue, 18 May 2021 20:16:56 -0400 Subject: [PATCH 02/28] add help --- launchk/src/tui/dialog.rs | 17 +++++++++++++++++ launchk/src/tui/omnibox/command.rs | 4 +++- launchk/src/tui/root.rs | 9 ++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/launchk/src/tui/dialog.rs b/launchk/src/tui/dialog.rs index 2ed1d35..b58e402 100644 --- a/launchk/src/tui/dialog.rs +++ b/launchk/src/tui/dialog.rs @@ -15,6 +15,7 @@ use crate::{ }; use crate::launchd::entry_status::{get_entry_status, LaunchdEntryStatus}; use xpc_sys::csr::{CsrConfig, csr_check}; +use crate::tui::omnibox::command::OMNIBOX_COMMANDS; /// The XPC error key sometimes contains information that is not necessarily a failure, /// so let's just call it "Notice" until we figure out what to do next? @@ -148,5 +149,21 @@ pub fn show_csr_info() -> CbSinkMessage { .title("CSR Info") .content(TextView::new(csr_flags.join("\n"))) .dismiss_button("OK") + .padding(Margins::trbl(2, 2, 2, 2)) + )) +} + +pub fn show_help() -> CbSinkMessage { + let commands = OMNIBOX_COMMANDS + .iter() + .map(|(cmd, desc, _)| format!("{}: {}", cmd, desc)) + .collect::>(); + + Box::new(move |siv| siv.add_layer( + Dialog::new() + .title("Help") + .content(TextView::new(commands.join("\n"))) + .dismiss_button("OK") + .padding(Margins::trbl(2, 2, 2, 2)) )) } \ No newline at end of file diff --git a/launchk/src/tui/omnibox/command.rs b/launchk/src/tui/omnibox/command.rs index 06755ea..c62dbe8 100644 --- a/launchk/src/tui/omnibox/command.rs +++ b/launchk/src/tui/omnibox/command.rs @@ -27,6 +27,7 @@ pub enum OmniboxCommand { ), FocusServiceList, CSRInfo, + Help, Quit, } @@ -36,7 +37,7 @@ impl fmt::Display for OmniboxCommand { } } -pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 8] = [ +pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 9] = [ ( "load", "▶️ Load highlighted job", @@ -73,5 +74,6 @@ pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 8] = [ "ℹ️ See all CSR flags", OmniboxCommand::CSRInfo, ), + ("help", "🤔 Show all commands", OmniboxCommand::Help), ("exit", "🚪 see ya!", OmniboxCommand::Quit), ]; diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index f63d5d0..35c9453 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -20,7 +20,7 @@ use crate::tui::omnibox::subscribed_view::{ use crate::tui::omnibox::view::{OmniboxError, OmniboxEvent, OmniboxView}; use crate::tui::service_list::view::ServiceListView; use crate::tui::sysinfo::SysInfo; -use crate::tui::dialog::show_csr_info; +use crate::tui::dialog::{show_csr_info, show_help}; pub type CbSinkMessage = Box; @@ -270,6 +270,13 @@ impl OmniboxSubscriber for RootLayout { Ok(None) } + OmniboxEvent::Command(OmniboxCommand::Help) => { + self.cbsink_channel + .send(show_help()) + .expect("Must show prompt"); + + Ok(None) + } _ => Ok(None), } } From 81df08ab841f865bb1707c6c0c14a4da864256e3 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Wed, 19 May 2021 09:33:04 -0400 Subject: [PATCH 03/28] xpc doc: dumpstate, procinfo --- doc/launchctl_messages.md | 185 ++++++++++++++++++++++++++++- launchk/src/tui/omnibox/command.rs | 1 - 2 files changed, 184 insertions(+), 2 deletions(-) diff --git a/doc/launchctl_messages.md b/doc/launchctl_messages.md index 702c695..ba1d8cb 100644 --- a/doc/launchctl_messages.md +++ b/doc/launchctl_messages.md @@ -351,4 +351,187 @@ Using a `gui/` domain target: 1: System 2: User -8: Login (GUI)? \ No newline at end of file +8: Login (GUI)? + +#### `launchctl dumpstate` + +``` +(lldb) p printf("%s",(char*) xpc_copy_description($rsi)) + { count = 5, transaction: 0, voucher = 0x0, contents = + "subsystem" => : 3 + "handle" => : 0 + "shmem" => : 20971520 bytes (5121 pages) + "routine" => : 834 + "type" => : 1 +(int) $0 = 328 +``` + +Get the shmem key from the dictionary, map the shmem region, then continue so it can be filled, then it can be read from: + +``` +expr void * $my_shmem = ((void *) xpc_dictionary_get_value($rsi, "shmem")); +expr void * $my_region = 0; +expr size_t $my_shsize = (size_t) xpc_shmem_map($my_shmem, &$my_region); +c +(lldb) mem read $my_region $my_region+100 +0x106580000: 63 6f 6d 2e 61 70 70 6c 65 2e 78 70 63 2e 6c 61 com.apple.xpc.la +0x106580010: 75 6e 63 68 64 2e 64 6f 6d 61 69 6e 2e 73 79 73 unchd.domain.sys +0x106580020: 74 65 6d 20 3d 20 7b 0a 09 74 79 70 65 20 3d 20 tem = {..type = +0x106580030: 73 79 73 74 65 6d 0a 09 68 61 6e 64 6c 65 20 3d system..handle = +0x106580040: 20 30 0a 09 61 63 74 69 76 65 20 63 6f 75 6e 74 0..active count +0x106580050: 20 3d 20 35 37 35 0a 09 6f 6e 2d 64 65 6d 61 6e = 575..on-deman +0x106580060: 64 20 63 6f d co +``` + +#### `launchctl procinfo 7578` + +This makes a whole _bunch_ of XPC calls! First it enumerates some ports: + +``` +2021-05-18 20:44:57.098359-0400 launchctl[7578:42112] [All] launchctl procinfo: launchctl procinfo 7475 +program path = /usr/local/Cellar/redis/6.2.1/bin/redis-server +mach info = { +(lldb) p printf("%s",(char*) xpc_copy_description($rsi)) + { count = 6, transaction: 0, voucher = 0x0, contents = + "subsystem" => : 3 + "handle" => : 0 + "routine" => : 822 + "process" => : 7578 + "name" => : 3335 + "type" => : 1 +(int) $6 = 360 +``` + +``` +} task-kernel port = 0xd07 (unknown) +(lldb) p printf("%s",(char*) xpc_copy_description($rsi)) + { count = 6, transaction: 0, voucher = 0x0, contents = + "subsystem" => : 3 + "handle" => : 0 + "routine" => : 822 + "process" => : 7578 + "name" => : 4611 + "type" => : 1 +(int) $7 = 360 +``` + +``` +} task-host port = 0x1203 (unknown) +Process 7578 stopped +* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.3 + frame #0: 0x00007fff2005e841 libxpc.dylib`xpc_pipe_routine_with_flags +(lldb) p printf("%s",(char*) xpc_copy_description($rsi)) + { count = 6, transaction: 0, voucher = 0x0, contents = + "subsystem" => : 3 + "handle" => : 0 + "routine" => : 822 + "process" => : 7578 + "name" => : 5635 + "type" => : 1 +``` + +``` +} task-name port = 0x1603 (unknown) +(lldb) p printf("%s",(char*) xpc_copy_description($rsi)) + { count = 6, transaction: 0, voucher = 0x0, contents = + "subsystem" => : 3 + "handle" => : 0 + "routine" => : 822 + "process" => : 7578 + "name" => : 5123 + "type" => : 1 +(int) $9 = 360 +``` + +``` +} task-bootstrap port = 0x1403 (unknown) +(lldb) p printf("%s",(char*) xpc_copy_description($rsi)) + { count = 6, transaction: 0, voucher = 0x0, contents = + "subsystem" => : 3 + "handle" => : 0 + "routine" => : 822 + "process" => : 7578 + "name" => : 5639 + "type" => : 1 +``` + +``` +} task-(null) port = 0x1607 (unknown) +(lldb) p printf("%s",(char*) xpc_copy_description($rsi)) + { count = 6, transaction: 0, voucher = 0x0, contents = + "subsystem" => : 3 + "handle" => : 0 + "routine" => : 822 + "process" => : 7578 + "name" => : 5643 + "type" => : 1 +``` + +Now for our old shmem / stdout friend: + +``` +argument count = 3 +argument vector = { + [0] = /usr/local/opt/redis/bin/redis-server 127.0.0.1:6379 + [1] = XPC_FLAGS=1 + [2] = LOGNAME=mach +} +environment vector = { + USER => mach + HOME => /Users/mach + SHELL => /bin/zsh + TMPDIR => /var/folders/sl/4tlmgdgj60j2wgykq7q10pdw0000gn/T/ +} +bsd proc info = { + pid = 7475 + unique pid = 7475 + ppid = 1 + pgid = 7475 + status = stopped + flags = 64-bit + uid = 501 + svuid = 501 + ruid = 501 + gid = 20 + svgid = 20 + rgid = 20 + comm name = redis-server + long name = redis-server + controlling tty devnode = 0xffffffff + controlling tty pgid = 0 +} +audit info + session id = 100006 + uid = 501 + success mask = 0x3000 + failure mask = 0x3000 + flags = has_graphic_access,has_tty,has_console_access,has_authenticated +sandboxed = no +container = (no container) + +responsible pid = 7475 +responsible unique pid = 7475 +responsible path = /usr/local/Cellar/redis/6.2.1/bin/redis-server + +pressured exit info = { + dirty state tracked = 0 + dirty = 0 + pressured-exit capable = 0 +} + +jetsam priority = 3: background +jetsam memory limit = -1 +jetsam state = (normal memory state) + +entitlements = (no entitlements) + +code signing info = (none) + +(lldb) p printf("%s",(char*) xpc_copy_description($rsi)) + { count = 4, transaction: 0, voucher = 0x0, contents = + "subsystem" => : 2 + "fd" => { type = (invalid descriptor), path = /dev/ttys003 } + "routine" => : 708 + "pid" => : 7475 +(int) $14 = 302 +``` diff --git a/launchk/src/tui/omnibox/command.rs b/launchk/src/tui/omnibox/command.rs index c62dbe8..607dbd6 100644 --- a/launchk/src/tui/omnibox/command.rs +++ b/launchk/src/tui/omnibox/command.rs @@ -47,7 +47,6 @@ pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 9] = [ "unload", "⏏️ Unload highlighted job", OmniboxCommand::UnloadRequest, - // OmniboxCommand::DomainSessionPrompt(false, |dt, _| vec![OmniboxCommand::Unload(dt, None)]), ), ( "enable", From 2824426e4ba17bd832a4da5a13d0c44648d503f0 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sat, 22 May 2021 10:23:26 -0400 Subject: [PATCH 04/28] attempt xpc shmem wrapper with vm_allocate + drop --- xpc-sys/src/objects/mod.rs | 1 + xpc-sys/src/objects/xpc_error.rs | 5 ++- xpc-sys/src/objects/xpc_shmem.rs | 68 ++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 xpc-sys/src/objects/xpc_shmem.rs diff --git a/xpc-sys/src/objects/mod.rs b/xpc-sys/src/objects/mod.rs index 5129a30..3507b04 100644 --- a/xpc-sys/src/objects/mod.rs +++ b/xpc-sys/src/objects/mod.rs @@ -11,3 +11,4 @@ pub mod xpc_type; pub mod xpc_pipe; pub mod xpc_error; +pub mod xpc_shmem; diff --git a/xpc-sys/src/objects/xpc_error.rs b/xpc-sys/src/objects/xpc_error.rs index f61b4e0..a0baf15 100644 --- a/xpc-sys/src/objects/xpc_error.rs +++ b/xpc-sys/src/objects/xpc_error.rs @@ -1,4 +1,4 @@ -use crate::objects::xpc_error::XPCError::{DictionaryError, PipeError, QueryError}; +use crate::objects::xpc_error::XPCError::{DictionaryError, PipeError, QueryError, ShmemError, ValueError}; use std::error::Error; use std::fmt::{Display, Formatter}; @@ -8,6 +8,7 @@ pub enum XPCError { PipeError(String), ValueError(String), QueryError(String), + ShmemError(String), StandardError, NotFound, } @@ -18,6 +19,8 @@ impl Display for XPCError { DictionaryError(e) => e, PipeError(e) => e, QueryError(e) => e, + ValueError(e) => e, + ShmemError(e) => e, _ => "", }; diff --git a/xpc-sys/src/objects/xpc_shmem.rs b/xpc-sys/src/objects/xpc_shmem.rs new file mode 100644 index 0000000..d5c9d2e --- /dev/null +++ b/xpc-sys/src/objects/xpc_shmem.rs @@ -0,0 +1,68 @@ +use crate::{vm_address_t, mach_port_t, vm_size_t, vm_allocate, rs_strerror, vm_deallocate, mach_task_self_}; +use std::sync::Arc; +use crate::objects::xpc_object::XPCObject; +use std::os::raw::c_int; +use crate::objects::xpc_error::XPCError; +use std::ptr::null_mut; + +pub struct XPCShmem { + pub task: mach_port_t, + pub size: vm_size_t, + pub region: Arc<*mut vm_address_t>, + pub xpc_object: XPCObject, +} + +impl XPCShmem { + pub fn new( + task: mach_port_t, + size: vm_size_t, + flags: c_int, + ) -> Result { + let mut region: *mut vm_address_t = null_mut(); + let err = unsafe { + vm_allocate( + task, + region, + size, + flags, + ) + }; + + if err > 0 { + Err(XPCError::ShmemError(rs_strerror(err))) + } else { + Ok(XPCShmem { + task, + size, + region: Arc::new(region), + xpc_object: Default::default() + }) + } + } + + pub fn new_task_self( + size: vm_size_t, + flags: c_int, + ) -> Result { + unsafe { Self::new(mach_task_self_, size, flags) } + } +} + +impl Drop for XPCShmem { + fn drop(&mut self) { + let XPCShmem { size, task, region, .. } = self; + if **region == null_mut() { + return; + } + + let refs = Arc::strong_count(®ion); + if refs > 1 { + log::warn!("vm_allocated region {:p} still has {} refs, cannot vm_deallocate", *region, refs); + return; + } + + unsafe { + vm_deallocate(*task, ***region, *size) + }; + } +} \ No newline at end of file From 1c1b7cb104bddcb1fe317e1f2218882f6f154884 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sat, 22 May 2021 11:39:10 -0400 Subject: [PATCH 05/28] query proto, but need to figure out how to deal with vm_deallocate/drop because this is wrong --- launchk/src/launchd/message.rs | 6 ++++++ launchk/src/launchd/query.rs | 14 ++++++++++++-- launchk/src/launchd/query_builder.rs | 4 ++-- xpc-sys/src/objects/xpc_shmem.rs | 8 +++++--- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/launchk/src/launchd/message.rs b/launchk/src/launchd/message.rs index 0270728..6963fd2 100644 --- a/launchk/src/launchd/message.rs +++ b/launchk/src/launchd/message.rs @@ -45,4 +45,10 @@ lazy_static! { // .entry("handle", UID or ASID) .entry("routine", 809 as u64) .entry("subsystem", 3 as u64); + + pub static ref DUMPSTATE: XPCDictionary = XPCDictionary::new() + .entry("subsystem", 3 as u64) + .entry("routine", 834 as u64) + .entry("type", 1 as u64) + .with_handle_or_default(None); } diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index 03b02fc..e918298 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -1,9 +1,9 @@ use crate::launchd::message::{ - DISABLE_NAMES, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, + DISABLE_NAMES, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, DUMPSTATE }; use std::collections::HashSet; -use xpc_sys::traits::xpc_pipeable::XPCPipeable; +use xpc_sys::{objects::xpc_shmem::XPCShmem, traits::xpc_pipeable::XPCPipeable}; use crate::launchd::entry_status::ENTRY_STATUS_CACHE; use std::iter::FromIterator; @@ -126,3 +126,13 @@ pub fn disable>( .with_handle_or_default(None) .pipe_routine_with_error_handling() } + +pub fn dumpstate() -> Result { + let shmem = XPCShmem::new_task_self(0x1400000, 0)?; + + XPCDictionary::new() + .extend(&DUMPSTATE) + // this is going to vm_deallocate when this function exits. need better solve. + .entry("shmem", shmem.xpc_object.clone()) + .pipe_routine_with_error_handling() +} diff --git a/launchk/src/launchd/query_builder.rs b/launchk/src/launchd/query_builder.rs index dda5cf0..7a70bc8 100644 --- a/launchk/src/launchd/query_builder.rs +++ b/launchk/src/launchd/query_builder.rs @@ -27,11 +27,11 @@ pub trait QueryBuilder { self.entry("session", session.unwrap_or(SessionType::Aqua).to_string()) } - fn with_handle_or_default(self, session: Option) -> XPCDictionary + fn with_handle_or_default(self, handle: Option) -> XPCDictionary where Self: Sized, { - self.entry("handle", session.unwrap_or(0)) + self.entry("handle", handle.unwrap_or(0)) } fn with_domain_type_or_default(self, t: Option) -> XPCDictionary diff --git a/xpc-sys/src/objects/xpc_shmem.rs b/xpc-sys/src/objects/xpc_shmem.rs index d5c9d2e..3ec3ce8 100644 --- a/xpc-sys/src/objects/xpc_shmem.rs +++ b/xpc-sys/src/objects/xpc_shmem.rs @@ -1,5 +1,5 @@ -use crate::{vm_address_t, mach_port_t, vm_size_t, vm_allocate, rs_strerror, vm_deallocate, mach_task_self_}; -use std::sync::Arc; +use crate::{vm_address_t, mach_port_t, vm_size_t, vm_allocate, rs_strerror, vm_deallocate, mach_task_self_, xpc_shmem_create}; +use std::{ffi::c_void, sync::Arc}; use crate::objects::xpc_object::XPCObject; use std::os::raw::c_int; use crate::objects::xpc_error::XPCError; @@ -35,7 +35,9 @@ impl XPCShmem { task, size, region: Arc::new(region), - xpc_object: Default::default() + xpc_object: unsafe { + xpc_shmem_create(region as *mut c_void, size as u64).into() + } }) } } From b0482c5bfdd575d273f2553ef2ebcc40c91cf9f4 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sun, 23 May 2021 11:39:51 -0400 Subject: [PATCH 06/28] sort of solve the drop issue -- for our use case if we make the rs shmem struct, then grab the xpc_object inside it, clone for the xpc dictionary, drop the dictionary then the shmem struct -- in theory it should run vm_deallocate at the correct time. if there are other xpc_object references it will not vm_deallocate and leave that as your problem to clean up. implicit memory leak but i can't figure out a nicer way hook up command + need to figure out why "operation not permitted" --- launchk/src/launchd/query.rs | 9 +++++---- launchk/src/tui/omnibox/command.rs | 8 +++++++- launchk/src/tui/root.rs | 7 ++++++- xpc-sys/src/objects/xpc_object.rs | 7 +++++++ xpc-sys/src/objects/xpc_shmem.rs | 25 +++++++++++++------------ 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index e918298..a325564 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -1,9 +1,9 @@ use crate::launchd::message::{ DISABLE_NAMES, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, DUMPSTATE }; -use std::collections::HashSet; +use std::{collections::HashSet, sync::Arc}; -use xpc_sys::{objects::xpc_shmem::XPCShmem, traits::xpc_pipeable::XPCPipeable}; +use xpc_sys::{objects::{xpc_object::XPCObject, xpc_shmem::XPCShmem}, traits::xpc_pipeable::XPCPipeable}; use crate::launchd::entry_status::ENTRY_STATUS_CACHE; use std::iter::FromIterator; @@ -130,9 +130,10 @@ pub fn disable>( pub fn dumpstate() -> Result { let shmem = XPCShmem::new_task_self(0x1400000, 0)?; + log::info!("Made shmem {:?}", shmem); + XPCDictionary::new() .extend(&DUMPSTATE) - // this is going to vm_deallocate when this function exits. need better solve. - .entry("shmem", shmem.xpc_object.clone()) + .entry("shmem", XPCObject::from(&shmem.xpc_object)) .pipe_routine_with_error_handling() } diff --git a/launchk/src/tui/omnibox/command.rs b/launchk/src/tui/omnibox/command.rs index 607dbd6..34485a8 100644 --- a/launchk/src/tui/omnibox/command.rs +++ b/launchk/src/tui/omnibox/command.rs @@ -27,6 +27,7 @@ pub enum OmniboxCommand { ), FocusServiceList, CSRInfo, + DumpState, Help, Quit, } @@ -37,7 +38,7 @@ impl fmt::Display for OmniboxCommand { } } -pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 9] = [ +pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 10] = [ ( "load", "▶️ Load highlighted job", @@ -73,6 +74,11 @@ pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 9] = [ "ℹ️ See all CSR flags", OmniboxCommand::CSRInfo, ), + ( + "dumpstate", + "ℹ️ launchctl dumpstate", + OmniboxCommand::DumpState, + ), ("help", "🤔 Show all commands", OmniboxCommand::Help), ("exit", "🚪 see ya!", OmniboxCommand::Quit), ]; diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 35c9453..421ed98 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -12,7 +12,7 @@ use cursive::{Cursive, Vec2, View, Printer}; use tokio::runtime::Handle; use tokio::time::interval; -use crate::tui::dialog; +use crate::{launchd::query::dumpstate, tui::dialog}; use crate::tui::omnibox::command::OmniboxCommand; use crate::tui::omnibox::subscribed_view::{ OmniboxResult, OmniboxSubscribedView, OmniboxSubscriber, Subscribable, @@ -270,6 +270,11 @@ impl OmniboxSubscriber for RootLayout { Ok(None) } + OmniboxEvent::Command(OmniboxCommand::DumpState) => { + let res = dumpstate(); + log::info!("dumpstate! {:?}", res); + Ok(None) + } OmniboxEvent::Command(OmniboxCommand::Help) => { self.cbsink_channel .send(show_help()) diff --git a/xpc-sys/src/objects/xpc_object.rs b/xpc-sys/src/objects/xpc_object.rs index 18afed6..2624878 100644 --- a/xpc-sys/src/objects/xpc_object.rs +++ b/xpc-sys/src/objects/xpc_object.rs @@ -130,6 +130,13 @@ impl From for XPCObject { } } +impl From<&Arc> for XPCObject { + fn from(value: &Arc) -> Self { + let XPCObject(ref arc, ref xpc_type) = **value; + XPCObject(arc.clone(), xpc_type.clone()) + } +} + /// Cloning an XPC object will clone the underlying Arc -- we will /// call xpc_release() only if we are the last valid reference /// (and underlying data is not null) diff --git a/xpc-sys/src/objects/xpc_shmem.rs b/xpc-sys/src/objects/xpc_shmem.rs index 3ec3ce8..82142b1 100644 --- a/xpc-sys/src/objects/xpc_shmem.rs +++ b/xpc-sys/src/objects/xpc_shmem.rs @@ -1,15 +1,16 @@ -use crate::{vm_address_t, mach_port_t, vm_size_t, vm_allocate, rs_strerror, vm_deallocate, mach_task_self_, xpc_shmem_create}; +use crate::{vm_address_t, mach_port_t, vm_size_t, vm_allocate, rs_strerror, vm_deallocate, mach_task_self_, xpc_shmem_create, xpc_object_t}; use std::{ffi::c_void, sync::Arc}; use crate::objects::xpc_object::XPCObject; use std::os::raw::c_int; use crate::objects::xpc_error::XPCError; use std::ptr::null_mut; +#[derive(Debug, Clone)] pub struct XPCShmem { pub task: mach_port_t, pub size: vm_size_t, - pub region: Arc<*mut vm_address_t>, - pub xpc_object: XPCObject, + pub region: *mut c_void, + pub xpc_object: Arc, } impl XPCShmem { @@ -18,11 +19,11 @@ impl XPCShmem { size: vm_size_t, flags: c_int, ) -> Result { - let mut region: *mut vm_address_t = null_mut(); + let mut region: *mut c_void = null_mut(); let err = unsafe { vm_allocate( task, - region, + &mut region as *const _ as *mut vm_address_t, size, flags, ) @@ -34,10 +35,10 @@ impl XPCShmem { Ok(XPCShmem { task, size, - region: Arc::new(region), - xpc_object: unsafe { + region, + xpc_object: Arc::new(unsafe { xpc_shmem_create(region as *mut c_void, size as u64).into() - } + }) }) } } @@ -52,19 +53,19 @@ impl XPCShmem { impl Drop for XPCShmem { fn drop(&mut self) { - let XPCShmem { size, task, region, .. } = self; - if **region == null_mut() { + let XPCShmem { size, task, region, xpc_object } = self; + if *region == null_mut() { return; } - let refs = Arc::strong_count(®ion); + let refs = Arc::strong_count(xpc_object); if refs > 1 { log::warn!("vm_allocated region {:p} still has {} refs, cannot vm_deallocate", *region, refs); return; } unsafe { - vm_deallocate(*task, ***region, *size) + vm_deallocate(*task, *region as vm_address_t, *size) }; } } \ No newline at end of file From 4a0e21ee7b2618c593bad86797c01713fe40640b Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sun, 23 May 2021 12:05:50 -0400 Subject: [PATCH 07/28] oops, need MAP_SHARED! -- made a shmem! --- launchk/src/launchd/query.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index a325564..fdaad82 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -1,9 +1,10 @@ use crate::launchd::message::{ DISABLE_NAMES, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, DUMPSTATE }; -use std::{collections::HashSet, sync::Arc}; +use std::{collections::HashSet, convert::TryInto, sync::Arc}; +use std::convert::TryFrom; -use xpc_sys::{objects::{xpc_object::XPCObject, xpc_shmem::XPCShmem}, traits::xpc_pipeable::XPCPipeable}; +use xpc_sys::{MAP_SHARED, objects::{xpc_object::XPCObject, xpc_shmem::XPCShmem}, traits::xpc_pipeable::XPCPipeable}; use crate::launchd::entry_status::ENTRY_STATUS_CACHE; use std::iter::FromIterator; @@ -128,7 +129,7 @@ pub fn disable>( } pub fn dumpstate() -> Result { - let shmem = XPCShmem::new_task_self(0x1400000, 0)?; + let shmem = XPCShmem::new_task_self(0x1400000, i32::try_from(MAP_SHARED).expect("Must conv flags"))?; log::info!("Made shmem {:?}", shmem); From 5d035e75bad7ac565c4002208068897448476d5f Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sun, 23 May 2021 19:14:19 -0400 Subject: [PATCH 08/28] can display xpc dumpstate response but needs progress bar and memory leak fix --- launchk/src/launchd/query.rs | 17 ++++++++++++----- launchk/src/tui/dialog.rs | 13 ++++++++++++- launchk/src/tui/root.rs | 20 +++++++++++++++++--- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index fdaad82..be171e3 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -1,10 +1,10 @@ use crate::launchd::message::{ DISABLE_NAMES, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, DUMPSTATE }; -use std::{collections::HashSet, convert::TryInto, sync::Arc}; +use std::{collections::HashSet}; use std::convert::TryFrom; -use xpc_sys::{MAP_SHARED, objects::{xpc_object::XPCObject, xpc_shmem::XPCShmem}, traits::xpc_pipeable::XPCPipeable}; +use xpc_sys::{MAP_SHARED, objects::{xpc_object::XPCObject, xpc_shmem::XPCShmem}, traits::{xpc_pipeable::XPCPipeable, xpc_value::TryXPCValue}}; use crate::launchd::entry_status::ENTRY_STATUS_CACHE; use std::iter::FromIterator; @@ -128,13 +128,20 @@ pub fn disable>( .pipe_routine_with_error_handling() } -pub fn dumpstate() -> Result { +/// Create a shared shmem region for the XPC routine to write +/// dumpstate contents into, and return the bytes written and +/// shmem region +pub fn dumpstate() -> Result<(usize, XPCShmem), XPCError> { let shmem = XPCShmem::new_task_self(0x1400000, i32::try_from(MAP_SHARED).expect("Must conv flags"))?; log::info!("Made shmem {:?}", shmem); - XPCDictionary::new() + let response = XPCDictionary::new() .extend(&DUMPSTATE) .entry("shmem", XPCObject::from(&shmem.xpc_object)) - .pipe_routine_with_error_handling() + .pipe_routine_with_error_handling()?; + + let bytes_written: u64 = response.get(&["bytes-written"])?.xpc_value()?; + + Ok((usize::try_from(bytes_written).unwrap(), shmem)) } diff --git a/launchk/src/tui/dialog.rs b/launchk/src/tui/dialog.rs index b58e402..c78b697 100644 --- a/launchk/src/tui/dialog.rs +++ b/launchk/src/tui/dialog.rs @@ -1,6 +1,6 @@ use std::sync::mpsc::Sender; -use cursive::Cursive; +use cursive::{Cursive, traits::Scrollable}; use cursive::{ theme::Effect, view::Margins, @@ -166,4 +166,15 @@ pub fn show_help() -> CbSinkMessage { .dismiss_button("OK") .padding(Margins::trbl(2, 2, 2, 2)) )) +} + +pub fn show_dumpstate>(text: S) -> CbSinkMessage { + let text = text.into(); + + Box::new(move |siv| siv.add_layer( + Dialog::new() + .title("dumpstate") + .content(TextView::new(text).scrollable()) + .dismiss_button("OK") + )) } \ No newline at end of file diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 421ed98..73c6969 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -1,4 +1,4 @@ -use std::cell::RefCell; +use std::{cell::RefCell, ffi::{CStr, CString}}; use std::collections::VecDeque; use std::sync::mpsc::{channel, Receiver, Sender}; use std::time::Duration; @@ -22,6 +22,8 @@ use crate::tui::service_list::view::ServiceListView; use crate::tui::sysinfo::SysInfo; use crate::tui::dialog::{show_csr_info, show_help}; +use super::dialog::show_dumpstate; + pub type CbSinkMessage = Box; pub struct RootLayout { @@ -271,8 +273,20 @@ impl OmniboxSubscriber for RootLayout { Ok(None) } OmniboxEvent::Command(OmniboxCommand::DumpState) => { - let res = dumpstate(); - log::info!("dumpstate! {:?}", res); + let (size, shmem) = dumpstate() + .map_err(|e| OmniboxError::CommandError(e.to_string()))?; + + log::info!("shmem response {}: {:?}", size, shmem); + + // It'll probably do the right thing + let c_str = unsafe { + CStr::from_ptr(shmem.region as *mut _) + }; + + self.cbsink_channel + .send(show_dumpstate(c_str.to_string_lossy())) + .expect("Must show prompt"); + Ok(None) } OmniboxEvent::Command(OmniboxCommand::Help) => { From 5db86f1e103ca1195cdb7d738391dea83eb69002 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sun, 23 May 2021 22:30:52 -0400 Subject: [PATCH 09/28] finish dumpstate: fix memory leak and use $PAGER instead :) --- launchk/src/launchd/entry_status.rs | 2 +- launchk/src/launchd/query.rs | 17 ++++-- launchk/src/main.rs | 4 +- launchk/src/tui/dialog.rs | 66 ++++++++++----------- launchk/src/tui/omnibox/command.rs | 8 +-- launchk/src/tui/root.rs | 86 +++++++++++++++++++--------- launchk/src/tui/service_list/view.rs | 64 ++++++++++----------- xpc-sys/src/objects/xpc_error.rs | 4 +- xpc-sys/src/objects/xpc_shmem.rs | 43 ++++++++------ 9 files changed, 163 insertions(+), 131 deletions(-) diff --git a/launchk/src/launchd/entry_status.rs b/launchk/src/launchd/entry_status.rs index 3d6b67f..7882982 100644 --- a/launchk/src/launchd/entry_status.rs +++ b/launchk/src/launchd/entry_status.rs @@ -3,7 +3,7 @@ use std::convert::TryInto; use std::sync::Mutex; use std::time::{Duration, SystemTime}; -use crate::launchd::enums::{SessionType, DomainType}; +use crate::launchd::enums::{DomainType, SessionType}; use crate::launchd::plist::LaunchdPlist; use crate::launchd::query::find_in_all; use xpc_sys::traits::xpc_value::TryXPCValue; diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index be171e3..c0261dd 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -1,10 +1,14 @@ use crate::launchd::message::{ - DISABLE_NAMES, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, DUMPSTATE + DISABLE_NAMES, DUMPSTATE, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, }; -use std::{collections::HashSet}; +use std::collections::HashSet; use std::convert::TryFrom; -use xpc_sys::{MAP_SHARED, objects::{xpc_object::XPCObject, xpc_shmem::XPCShmem}, traits::{xpc_pipeable::XPCPipeable, xpc_value::TryXPCValue}}; +use xpc_sys::{ + objects::{xpc_object::XPCObject, xpc_shmem::XPCShmem}, + traits::{xpc_pipeable::XPCPipeable, xpc_value::TryXPCValue}, + MAP_SHARED, +}; use crate::launchd::entry_status::ENTRY_STATUS_CACHE; use std::iter::FromIterator; @@ -129,10 +133,13 @@ pub fn disable>( } /// Create a shared shmem region for the XPC routine to write -/// dumpstate contents into, and return the bytes written and +/// dumpstate contents into, and return the bytes written and /// shmem region pub fn dumpstate() -> Result<(usize, XPCShmem), XPCError> { - let shmem = XPCShmem::new_task_self(0x1400000, i32::try_from(MAP_SHARED).expect("Must conv flags"))?; + let shmem = XPCShmem::new_task_self( + 0x1400000, + i32::try_from(MAP_SHARED).expect("Must conv flags"), + )?; log::info!("Made shmem {:?}", shmem); diff --git a/launchk/src/main.rs b/launchk/src/main.rs index 82ebdc9..0b008d8 100644 --- a/launchk/src/main.rs +++ b/launchk/src/main.rs @@ -9,8 +9,8 @@ extern crate bitflags; extern crate plist; -use cursive::view::{Resizable, AnyView}; -use cursive::views::{Panel, NamedView}; +use cursive::view::Resizable; +use cursive::views::{NamedView, Panel}; use cursive::Cursive; use std::process::exit; diff --git a/launchk/src/tui/dialog.rs b/launchk/src/tui/dialog.rs index c78b697..88ab4f0 100644 --- a/launchk/src/tui/dialog.rs +++ b/launchk/src/tui/dialog.rs @@ -1,21 +1,21 @@ use std::sync::mpsc::Sender; -use cursive::{Cursive, traits::Scrollable}; +use cursive::Cursive; use cursive::{ theme::Effect, view::Margins, views::{Dialog, DummyView, LinearLayout, RadioGroup, TextView}, }; +use crate::launchd::entry_status::{get_entry_status, LaunchdEntryStatus}; +use crate::tui::omnibox::command::OMNIBOX_COMMANDS; use crate::tui::omnibox::view::OmniboxEvent; use crate::tui::root::CbSinkMessage; use crate::{ launchd::enums::{DomainType, SessionType}, tui::omnibox::command::OmniboxCommand, }; -use crate::launchd::entry_status::{get_entry_status, LaunchdEntryStatus}; -use xpc_sys::csr::{CsrConfig, csr_check}; -use crate::tui::omnibox::command::OMNIBOX_COMMANDS; +use xpc_sys::csr::{csr_check, CsrConfig}; /// The XPC error key sometimes contains information that is not necessarily a failure, /// so let's just call it "Notice" until we figure out what to do next? @@ -81,7 +81,8 @@ pub fn domain_session_prompt>( for d in DomainType::System as u64..DomainType::Unknown as u64 { let as_domain: DomainType = d.into(); - let mut button = domain_group.button(as_domain.clone(), format!("{}: {}", d, &as_domain)); + let mut button = + domain_group.button(as_domain.clone(), format!("{}: {}", d, &as_domain)); if as_domain == domain { button = button.selected(); } @@ -139,18 +140,22 @@ pub fn domain_session_prompt>( } pub fn show_csr_info() -> CbSinkMessage { - let csr_flags = (0..11).map(|s| { - let mask = CsrConfig::from_bits(1 << s).expect("Must be in CsrConfig"); - format!("{:?}: {}", mask, unsafe { csr_check(mask.bits()) } == 0) - }).collect::>(); - - Box::new(move |siv| siv.add_layer( - Dialog::new() - .title("CSR Info") - .content(TextView::new(csr_flags.join("\n"))) - .dismiss_button("OK") - .padding(Margins::trbl(2, 2, 2, 2)) - )) + let csr_flags = (0..11) + .map(|s| { + let mask = CsrConfig::from_bits(1 << s).expect("Must be in CsrConfig"); + format!("{:?}: {}", mask, unsafe { csr_check(mask.bits()) } == 0) + }) + .collect::>(); + + Box::new(move |siv| { + siv.add_layer( + Dialog::new() + .title("CSR Info") + .content(TextView::new(csr_flags.join("\n"))) + .dismiss_button("OK") + .padding(Margins::trbl(2, 2, 2, 2)), + ) + }) } pub fn show_help() -> CbSinkMessage { @@ -159,22 +164,13 @@ pub fn show_help() -> CbSinkMessage { .map(|(cmd, desc, _)| format!("{}: {}", cmd, desc)) .collect::>(); - Box::new(move |siv| siv.add_layer( - Dialog::new() - .title("Help") - .content(TextView::new(commands.join("\n"))) - .dismiss_button("OK") - .padding(Margins::trbl(2, 2, 2, 2)) - )) + Box::new(move |siv| { + siv.add_layer( + Dialog::new() + .title("Help") + .content(TextView::new(commands.join("\n"))) + .dismiss_button("OK") + .padding(Margins::trbl(2, 2, 2, 2)), + ) + }) } - -pub fn show_dumpstate>(text: S) -> CbSinkMessage { - let text = text.into(); - - Box::new(move |siv| siv.add_layer( - Dialog::new() - .title("dumpstate") - .content(TextView::new(text).scrollable()) - .dismiss_button("OK") - )) -} \ No newline at end of file diff --git a/launchk/src/tui/omnibox/command.rs b/launchk/src/tui/omnibox/command.rs index 34485a8..96c9e56 100644 --- a/launchk/src/tui/omnibox/command.rs +++ b/launchk/src/tui/omnibox/command.rs @@ -57,7 +57,7 @@ pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 10] = [ ( "disable", "⏏️ Disable highlighted job (prevents load)", - OmniboxCommand::DisableRequest + OmniboxCommand::DisableRequest, ), ( "edit", @@ -69,11 +69,7 @@ pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 10] = [ "🔄 Reload highlighted job", OmniboxCommand::Reload, ), - ( - "csrinfo", - "ℹ️ See all CSR flags", - OmniboxCommand::CSRInfo, - ), + ("csrinfo", "ℹ️ See all CSR flags", OmniboxCommand::CSRInfo), ( "dumpstate", "ℹ️ launchctl dumpstate", diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 73c6969..034af93 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -1,18 +1,24 @@ -use std::{cell::RefCell, ffi::{CStr, CString}}; use std::collections::VecDeque; use std::sync::mpsc::{channel, Receiver, Sender}; -use std::time::Duration; + +use std::{ + cell::RefCell, + ffi::{CStr, CString}, + io::Write, + os::raw::c_char, + process::{Command, Stdio}, + ptr::slice_from_raw_parts, +}; use cursive::event::{Event, EventResult, Key}; use cursive::traits::{Resizable, Scrollable}; -use cursive::view::{ViewWrapper, AnyView}; +use cursive::view::{AnyView, ViewWrapper}; use cursive::views::{LinearLayout, NamedView, Panel}; -use cursive::{Cursive, Vec2, View, Printer}; +use cursive::{Cursive, Vec2, View}; use tokio::runtime::Handle; -use tokio::time::interval; -use crate::{launchd::query::dumpstate, tui::dialog}; +use crate::tui::dialog::{show_csr_info, show_help}; use crate::tui::omnibox::command::OmniboxCommand; use crate::tui::omnibox::subscribed_view::{ OmniboxResult, OmniboxSubscribedView, OmniboxSubscriber, Subscribable, @@ -20,9 +26,11 @@ use crate::tui::omnibox::subscribed_view::{ use crate::tui::omnibox::view::{OmniboxError, OmniboxEvent, OmniboxView}; use crate::tui::service_list::view::ServiceListView; use crate::tui::sysinfo::SysInfo; -use crate::tui::dialog::{show_csr_info, show_help}; +use crate::{launchd::query::dumpstate, tui::dialog}; -use super::dialog::show_dumpstate; +lazy_static! { + static ref PAGER: &'static str = option_env!("PAGER").unwrap_or("less"); +} pub type CbSinkMessage = Box; @@ -44,17 +52,17 @@ enum RootLayoutChildren { async fn poll_omnibox(cb_sink: Sender, rx: Receiver) { loop { - let recv = rx - .recv() - .expect("Must receive event"); + let recv = rx.recv().expect("Must receive event"); log::info!("[root_layout/poll_omnibox]: RECV {:?}", recv); - cb_sink.send(Box::new(|siv| { - siv.call_on_name("root_layout", |v: &mut NamedView| { - v.get_mut().handle_omnibox_event(recv); - }); - })); + cb_sink + .send(Box::new(|siv| { + siv.call_on_name("root_layout", |v: &mut NamedView| { + v.get_mut().handle_omnibox_event(recv); + }); + })) + .expect("Must forward to root") } } @@ -183,7 +191,7 @@ impl RootLayout { impl ViewWrapper for RootLayout { wrap_impl!(self.layout: LinearLayout); - + fn wrap_on_event(&mut self, event: Event) -> EventResult { log::debug!("[root/event]: {:?}", event); @@ -273,21 +281,45 @@ impl OmniboxSubscriber for RootLayout { Ok(None) } OmniboxEvent::Command(OmniboxCommand::DumpState) => { - let (size, shmem) = dumpstate() + let (size, shmem) = + dumpstate().map_err(|e| OmniboxError::CommandError(e.to_string()))?; + + log::info!("shmem response sz {}", size); + + let mut pager = Command::new(*PAGER) + .stdin(Stdio::piped()) + .spawn() .map_err(|e| OmniboxError::CommandError(e.to_string()))?; - log::info!("shmem response {}: {:?}", size, shmem); + let pager_stdin = pager.stdin.take(); - // It'll probably do the right thing - let c_str = unsafe { - CStr::from_ptr(shmem.region as *mut _) - }; + self.runtime_handle.spawn(async move { + let raw_slice = slice_from_raw_parts(shmem.region as *mut u8, size); - self.cbsink_channel - .send(show_dumpstate(c_str.to_string_lossy())) - .expect("Must show prompt"); + unsafe { + pager_stdin + .expect("Must have pager stdin") + .write_all(&*raw_slice) + .expect("Must write dumpstate") + }; + }); - Ok(None) + let res = pager + .wait() + .map_err(|e| OmniboxError::CommandError(e.to_string()))?; + + self.cbsink_channel + .send(Box::new(Cursive::clear)) + .expect("Must clear"); + + if !res.success() { + Err(OmniboxError::CommandError(format!( + "{} exited {:?}", + *PAGER, res + ))) + } else { + Ok(None) + } } OmniboxEvent::Command(OmniboxCommand::Help) => { self.cbsink_channel diff --git a/launchk/src/tui/service_list/view.rs b/launchk/src/tui/service_list/view.rs index d6de41d..8796bee 100644 --- a/launchk/src/tui/service_list/view.rs +++ b/launchk/src/tui/service_list/view.rs @@ -13,6 +13,7 @@ use cursive::{Cursive, View, XY}; use tokio::runtime::Handle; use tokio::time::interval; +use crate::launchd::enums::{DomainType, SessionType}; use crate::launchd::job_type_filter::JobTypeFilter; use crate::launchd::plist::{edit_and_replace, LABEL_TO_ENTRY_CONFIG}; use crate::launchd::query::{disable, enable, list_all, load, unload}; @@ -20,14 +21,13 @@ use crate::launchd::{ entry_status::get_entry_status, entry_status::LaunchdEntryStatus, plist::LaunchdPlist, }; use crate::tui::omnibox::command::OmniboxCommand; + use crate::tui::omnibox::state::OmniboxState; use crate::tui::omnibox::subscribed_view::{OmniboxResult, OmniboxSubscriber}; use crate::tui::omnibox::view::{OmniboxError, OmniboxEvent, OmniboxMode}; use crate::tui::root::CbSinkMessage; use crate::tui::service_list::list_item::ServiceListItem; use crate::tui::table::table_list_view::TableListView; -use crate::launchd::enums::{SessionType, DomainType}; -use crate::tui::omnibox::command::OmniboxCommand::DomainSessionPrompt; /// Polls XPC for job list async fn poll_running_jobs(svcs: Arc>>, cb_sink: Sender) { @@ -194,24 +194,20 @@ impl ServiceListView { } = get_entry_status(&name); match (limit_load_to_session_type, domain) { - (_, DomainType::Unknown) | (SessionType::Unknown, _) => { - Ok(Some(OmniboxCommand::DomainSessionPrompt( - name.clone(), - false, - |dt, st| { - vec![ - OmniboxCommand::Unload(dt.clone(), None), - OmniboxCommand::Load(st.expect("Must provide"), dt, None), - ] - }, - ))) - }, + (_, DomainType::Unknown) | (SessionType::Unknown, _) => Ok(Some( + OmniboxCommand::DomainSessionPrompt(name.clone(), false, |dt, st| { + vec![ + OmniboxCommand::Unload(dt.clone(), None), + OmniboxCommand::Load(st.expect("Must provide"), dt, None), + ] + }), + )), (st, dt) => Ok(Some(OmniboxCommand::Chain(vec![ OmniboxCommand::Unload(dt.clone(), None), OmniboxCommand::Load(st, dt, None), - ]))) + ]))), } - }, + } OmniboxCommand::LoadRequest => { let (ServiceListItem { name, .. }, ..) = self.with_active_item_plist()?; Ok(Some(OmniboxCommand::DomainSessionPrompt( @@ -223,45 +219,43 @@ impl ServiceListView { dt, None, )] - } + }, ))) - }, + } OmniboxCommand::UnloadRequest => { let (ServiceListItem { name, .. }, ..) = self.with_active_item_plist()?; - let LaunchdEntryStatus { - domain, - .. - } = get_entry_status(&name); + let LaunchdEntryStatus { domain, .. } = get_entry_status(&name); match domain { - DomainType::Unknown => Ok(Some(OmniboxCommand::DomainSessionPrompt(name.clone(), true, |dt, _| vec![OmniboxCommand::Unload(dt, None)]))), - _ => Ok(Some(OmniboxCommand::Unload(domain, None))) + DomainType::Unknown => Ok(Some(OmniboxCommand::DomainSessionPrompt( + name.clone(), + true, + |dt, _| vec![OmniboxCommand::Unload(dt, None)], + ))), + _ => Ok(Some(OmniboxCommand::Unload(domain, None))), } - }, + } OmniboxCommand::EnableRequest => { let (ServiceListItem { name, .. }, ..) = self.with_active_item_plist()?; Ok(Some(OmniboxCommand::DomainSessionPrompt( name.clone(), true, - |dt, _| vec![OmniboxCommand::Enable(dt)] + |dt, _| vec![OmniboxCommand::Enable(dt)], ))) } OmniboxCommand::DisableRequest => { let (ServiceListItem { name, .. }, ..) = self.with_active_item_plist()?; - let LaunchdEntryStatus { - domain, - .. - } = get_entry_status(&name); + let LaunchdEntryStatus { domain, .. } = get_entry_status(&name); match domain { DomainType::Unknown => Ok(Some(OmniboxCommand::DomainSessionPrompt( name.clone(), true, - |dt, _| vec![OmniboxCommand::Disable(dt)] + |dt, _| vec![OmniboxCommand::Disable(dt)], ))), - _ => Ok(Some(OmniboxCommand::Chain(vec![ - OmniboxCommand::Disable(domain) - ]))) + _ => Ok(Some(OmniboxCommand::Chain(vec![OmniboxCommand::Disable( + domain, + )]))), } } OmniboxCommand::Edit => { @@ -283,7 +277,7 @@ impl ServiceListView { load(name, plist.plist_path, Some(dt), Some(st), None) .map(|_| None) .map_err(|e| OmniboxError::CommandError(e.to_string())) - }, + } OmniboxCommand::Unload(dt, _handle) => { let (ServiceListItem { name, .. }, plist) = self.with_active_item_plist()?; let LaunchdEntryStatus { diff --git a/xpc-sys/src/objects/xpc_error.rs b/xpc-sys/src/objects/xpc_error.rs index a0baf15..b910d94 100644 --- a/xpc-sys/src/objects/xpc_error.rs +++ b/xpc-sys/src/objects/xpc_error.rs @@ -1,4 +1,6 @@ -use crate::objects::xpc_error::XPCError::{DictionaryError, PipeError, QueryError, ShmemError, ValueError}; +use crate::objects::xpc_error::XPCError::{ + DictionaryError, PipeError, QueryError, ShmemError, ValueError, +}; use std::error::Error; use std::fmt::{Display, Formatter}; diff --git a/xpc-sys/src/objects/xpc_shmem.rs b/xpc-sys/src/objects/xpc_shmem.rs index 82142b1..8b4ca15 100644 --- a/xpc-sys/src/objects/xpc_shmem.rs +++ b/xpc-sys/src/objects/xpc_shmem.rs @@ -1,9 +1,12 @@ -use crate::{vm_address_t, mach_port_t, vm_size_t, vm_allocate, rs_strerror, vm_deallocate, mach_task_self_, xpc_shmem_create, xpc_object_t}; -use std::{ffi::c_void, sync::Arc}; +use crate::objects::xpc_error::XPCError; use crate::objects::xpc_object::XPCObject; +use crate::{ + mach_port_t, mach_task_self_, rs_strerror, vm_address_t, vm_allocate, vm_deallocate, vm_size_t, + xpc_shmem_create, +}; use std::os::raw::c_int; -use crate::objects::xpc_error::XPCError; use std::ptr::null_mut; +use std::{ffi::c_void, sync::Arc}; #[derive(Debug, Clone)] pub struct XPCShmem { @@ -13,12 +16,10 @@ pub struct XPCShmem { pub xpc_object: Arc, } +unsafe impl Send for XPCShmem {} + impl XPCShmem { - pub fn new( - task: mach_port_t, - size: vm_size_t, - flags: c_int, - ) -> Result { + pub fn new(task: mach_port_t, size: vm_size_t, flags: c_int) -> Result { let mut region: *mut c_void = null_mut(); let err = unsafe { vm_allocate( @@ -38,34 +39,38 @@ impl XPCShmem { region, xpc_object: Arc::new(unsafe { xpc_shmem_create(region as *mut c_void, size as u64).into() - }) + }), }) } } - pub fn new_task_self( - size: vm_size_t, - flags: c_int, - ) -> Result { + pub fn new_task_self(size: vm_size_t, flags: c_int) -> Result { unsafe { Self::new(mach_task_self_, size, flags) } } } impl Drop for XPCShmem { fn drop(&mut self) { - let XPCShmem { size, task, region, xpc_object } = self; + let XPCShmem { + size, + task, + region, + xpc_object, + } = self; if *region == null_mut() { return; } let refs = Arc::strong_count(xpc_object); if refs > 1 { - log::warn!("vm_allocated region {:p} still has {} refs, cannot vm_deallocate", *region, refs); + log::warn!( + "vm_allocated region {:p} still has {} refs, cannot vm_deallocate", + *region, + refs + ); return; } - unsafe { - vm_deallocate(*task, *region as vm_address_t, *size) - }; + unsafe { vm_deallocate(*task, *region as vm_address_t, *size) }; } -} \ No newline at end of file +} From e73c480b371ff2f8699c6a38ccbb1612c23e29e3 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Tue, 25 May 2021 23:20:48 -0400 Subject: [PATCH 10/28] implement dumpjpstate, but have pebkac with unix pipe() i can't currently see add libc crate rename shmemerror to ioerror for clarity add xpc_fd_create functionality for std::os::unix::prelude::RawFd --- Cargo.lock | 5 +-- launchk/Cargo.toml | 3 +- launchk/src/launchd/message.rs | 6 ++++ launchk/src/launchd/query.rs | 57 ++++++++++++++++++++++++++---- launchk/src/tui/dialog.rs | 16 +++++++++ launchk/src/tui/omnibox/command.rs | 8 ++++- launchk/src/tui/root.rs | 17 ++++++++- xpc-sys/src/objects/xpc_error.rs | 6 ++-- xpc-sys/src/objects/xpc_object.rs | 14 +++++++- xpc-sys/src/objects/xpc_shmem.rs | 2 +- 10 files changed, 117 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce9d3c9..a825312 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -630,6 +630,7 @@ dependencies = [ "env_logger 0.8.3", "futures", "lazy_static", + "libc", "log", "notify", "plist", @@ -651,9 +652,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.86" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" +checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" [[package]] name = "libloading" diff --git a/launchk/Cargo.toml b/launchk/Cargo.toml index 1e318bc..696411d 100644 --- a/launchk/Cargo.toml +++ b/launchk/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] xpc-sys = { path= "../xpc-sys" } +libc = "0.2.94" lazy_static = "1.4.0" cursive = { version = "0.15.0", features = ["toml"] } tokio = { version = "1", features = ["full"] } @@ -16,4 +17,4 @@ plist = "1.1.0" bitflags = "1.2.1" notify = "4.0.16" log = "0.4.14" -env_logger = "0.8.3" \ No newline at end of file +env_logger = "0.8.3" diff --git a/launchk/src/launchd/message.rs b/launchk/src/launchd/message.rs index 6963fd2..4f7f84a 100644 --- a/launchk/src/launchd/message.rs +++ b/launchk/src/launchd/message.rs @@ -51,4 +51,10 @@ lazy_static! { .entry("routine", 834 as u64) .entry("type", 1 as u64) .with_handle_or_default(None); + + pub static ref DUMPJPCATEGORY: XPCDictionary = XPCDictionary::new() + .entry("subsystem", 3 as u64) + .entry("routine", 837 as u64) + .entry("type", 1 as u64) + .with_handle_or_default(None); } diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index c0261dd..091f688 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -1,14 +1,10 @@ use crate::launchd::message::{ - DISABLE_NAMES, DUMPSTATE, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, + DISABLE_NAMES, DUMPSTATE, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, DUMPJPCATEGORY }; -use std::collections::HashSet; +use std::{collections::HashSet, ffi::{CStr, CString, OsStr}, fs::File, io::Read, os::unix::prelude::{FromRawFd, RawFd}, path::Path, ptr::{null, null_mut}}; use std::convert::TryFrom; -use xpc_sys::{ - objects::{xpc_object::XPCObject, xpc_shmem::XPCShmem}, - traits::{xpc_pipeable::XPCPipeable, xpc_value::TryXPCValue}, - MAP_SHARED, -}; +use xpc_sys::{MAP_SHARED, errno, objects::{xpc_object::{XPCObject}, xpc_shmem::XPCShmem}, rs_strerror, traits::{xpc_pipeable::XPCPipeable, xpc_value::TryXPCValue}}; use crate::launchd::entry_status::ENTRY_STATUS_CACHE; use std::iter::FromIterator; @@ -18,6 +14,10 @@ use xpc_sys::objects::xpc_error::XPCError; use crate::launchd::enums::{DomainType, SessionType}; use crate::launchd::query_builder::QueryBuilder; +use libc::{self, O_NONBLOCK, close, fdopen, fileno, fopen, mkfifo, pipe, tmpnam}; + +use std::os::raw::c_int; + pub fn find_in_all>(label: S) -> Result<(DomainType, XPCDictionary), XPCError> { let label_string = label.into(); @@ -152,3 +152,46 @@ pub fn dumpstate() -> Result<(usize, XPCShmem), XPCError> { Ok((usize::try_from(bytes_written).unwrap(), shmem)) } + +pub fn dumpjpcategory() -> Result { + // [r, w] + let mut pipes: [c_int; 2] = [-1; 2]; + let err = unsafe { + pipe(pipes.as_mut_ptr()) + }; + + log::info!("Made pipe [r,w]: {:?}", pipes); + + if err != 0 { + return Err(XPCError::IOError(unsafe { + rs_strerror(errno) + })); + } + + XPCDictionary::new() + .extend(&DUMPJPCATEGORY) + .entry("fd", pipes[1] as RawFd) + .pipe_routine_with_error_handling()?; + + log::info!("Made XPC query, closing pipe"); + + let err = unsafe { + close(pipes[1]) + }; + + if err != 0 { + return Err(XPCError::IOError(unsafe { + rs_strerror(errno) + })); + } + + let mut fifo_read = unsafe { + File::from_raw_fd(pipes[0]) + }; + + let mut buf = String::new(); + fifo_read.read_to_string(&mut buf) + .map_err(|e| XPCError::IOError(e.to_string()))?; + + Ok(buf) +} \ No newline at end of file diff --git a/launchk/src/tui/dialog.rs b/launchk/src/tui/dialog.rs index 88ab4f0..3fea4d7 100644 --- a/launchk/src/tui/dialog.rs +++ b/launchk/src/tui/dialog.rs @@ -1,6 +1,7 @@ use std::sync::mpsc::Sender; use cursive::Cursive; +use cursive::traits::Scrollable; use cursive::{ theme::Effect, view::Margins, @@ -174,3 +175,18 @@ pub fn show_help() -> CbSinkMessage { ) }) } + +pub fn scrollable_dialog>(title: S, content: S) -> CbSinkMessage { + let title = title.into(); + let content = content.into(); + + Box::new(move |siv| { + siv.add_layer( + Dialog::new() + .title(title) + .content(TextView::new(content).scrollable()) + .dismiss_button("OK") + .padding(Margins::trbl(2, 2, 2, 2)), + ) + }) +} diff --git a/launchk/src/tui/omnibox/command.rs b/launchk/src/tui/omnibox/command.rs index 96c9e56..0490831 100644 --- a/launchk/src/tui/omnibox/command.rs +++ b/launchk/src/tui/omnibox/command.rs @@ -28,6 +28,7 @@ pub enum OmniboxCommand { FocusServiceList, CSRInfo, DumpState, + DumpJetsamPropertiesCategory, Help, Quit, } @@ -38,7 +39,7 @@ impl fmt::Display for OmniboxCommand { } } -pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 10] = [ +pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 11] = [ ( "load", "▶️ Load highlighted job", @@ -75,6 +76,11 @@ pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 10] = [ "ℹ️ launchctl dumpstate", OmniboxCommand::DumpState, ), + ( + "dumpjpcategory", + "ℹ️ launchctl dumpjpcategory", + OmniboxCommand::DumpJetsamPropertiesCategory, + ), ("help", "🤔 Show all commands", OmniboxCommand::Help), ("exit", "🚪 see ya!", OmniboxCommand::Quit), ]; diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 034af93..8d7b415 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -18,7 +18,8 @@ use cursive::{Cursive, Vec2, View}; use tokio::runtime::Handle; -use crate::tui::dialog::{show_csr_info, show_help}; +use crate::tui::dialog::scrollable_dialog; +use crate::{launchd::query::dumpjpcategory, tui::dialog::{show_csr_info, show_help}}; use crate::tui::omnibox::command::OmniboxCommand; use crate::tui::omnibox::subscribed_view::{ OmniboxResult, OmniboxSubscribedView, OmniboxSubscriber, Subscribable, @@ -321,6 +322,20 @@ impl OmniboxSubscriber for RootLayout { Ok(None) } } + OmniboxEvent::Command(OmniboxCommand::DumpJetsamPropertiesCategory) => { + + let res = dumpjpcategory(); + log::info!("XPCR: {:?}", res); + Ok(None) + // let res = dumpjpcategory() + // .map_err(|e| OmniboxError::CommandError(e.to_string()))?; + + // self.cbsink_channel + // .send(scrollable_dialog("dumpjpstate", &res)) + // .expect("Must show dialog"); + + // Ok(None) + } OmniboxEvent::Command(OmniboxCommand::Help) => { self.cbsink_channel .send(show_help()) diff --git a/xpc-sys/src/objects/xpc_error.rs b/xpc-sys/src/objects/xpc_error.rs index b910d94..4446cc9 100644 --- a/xpc-sys/src/objects/xpc_error.rs +++ b/xpc-sys/src/objects/xpc_error.rs @@ -1,5 +1,5 @@ use crate::objects::xpc_error::XPCError::{ - DictionaryError, PipeError, QueryError, ShmemError, ValueError, + DictionaryError, PipeError, QueryError, IOError, ValueError, }; use std::error::Error; use std::fmt::{Display, Formatter}; @@ -10,7 +10,7 @@ pub enum XPCError { PipeError(String), ValueError(String), QueryError(String), - ShmemError(String), + IOError(String), StandardError, NotFound, } @@ -22,7 +22,7 @@ impl Display for XPCError { PipeError(e) => e, QueryError(e) => e, ValueError(e) => e, - ShmemError(e) => e, + IOError(e) => e, _ => "", }; diff --git a/xpc-sys/src/objects/xpc_object.rs b/xpc-sys/src/objects/xpc_object.rs index 2624878..ac66d7d 100644 --- a/xpc-sys/src/objects/xpc_object.rs +++ b/xpc-sys/src/objects/xpc_object.rs @@ -2,9 +2,10 @@ use crate::objects::xpc_type::XPCType; use crate::{ mach_port_t, xpc_array_append_value, xpc_array_create, xpc_bool_create, xpc_copy_description, xpc_double_create, xpc_int64_create, xpc_mach_send_create, xpc_object_t, xpc_release, - xpc_string_create, xpc_uint64_create, + xpc_string_create, xpc_uint64_create, xpc_fd_create }; use std::ffi::{CStr, CString}; +use std::os::unix::prelude::RawFd; use std::ptr::null_mut; use std::sync::Arc; @@ -137,6 +138,17 @@ impl From<&Arc> for XPCObject { } } +impl From for XPCObject { + /// Use std::os::unix::prelude type for xpc_fd_create + fn from(value: RawFd) -> Self { + unsafe { + XPCObject::new( + xpc_fd_create(value) + ) + } + } +} + /// Cloning an XPC object will clone the underlying Arc -- we will /// call xpc_release() only if we are the last valid reference /// (and underlying data is not null) diff --git a/xpc-sys/src/objects/xpc_shmem.rs b/xpc-sys/src/objects/xpc_shmem.rs index 8b4ca15..c0fb05d 100644 --- a/xpc-sys/src/objects/xpc_shmem.rs +++ b/xpc-sys/src/objects/xpc_shmem.rs @@ -31,7 +31,7 @@ impl XPCShmem { }; if err > 0 { - Err(XPCError::ShmemError(rs_strerror(err))) + Err(XPCError::IOError(rs_strerror(err))) } else { Ok(XPCShmem { task, From 644f433ad197bc77bcf9b0768e0030977126a81c Mon Sep 17 00:00:00 2001 From: David Stancu Date: Wed, 26 May 2021 10:58:38 -0400 Subject: [PATCH 11/28] working with whatever default fifo buf is, much larger, needs pager too --- launchk/src/launchd/query.rs | 42 +++++++++++++++---------------- launchk/src/tui/root.rs | 18 ++++++------- xpc-sys/src/objects/xpc_object.rs | 1 + 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index 091f688..c40ae0f 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -14,7 +14,7 @@ use xpc_sys::objects::xpc_error::XPCError; use crate::launchd::enums::{DomainType, SessionType}; use crate::launchd::query_builder::QueryBuilder; -use libc::{self, O_NONBLOCK, close, fdopen, fileno, fopen, mkfifo, pipe, tmpnam}; +use libc::{self, O_NONBLOCK, O_RDONLY, O_WRONLY, close, fdopen, fileno, fopen, mkfifo, open, pipe, tmpnam}; use std::os::raw::c_int; @@ -154,39 +154,39 @@ pub fn dumpstate() -> Result<(usize, XPCShmem), XPCError> { } pub fn dumpjpcategory() -> Result { - // [r, w] - let mut pipes: [c_int; 2] = [-1; 2]; - let err = unsafe { - pipe(pipes.as_mut_ptr()) + let fifo_name = unsafe { + CStr::from_ptr(tmpnam(null_mut())) }; - log::info!("Made pipe [r,w]: {:?}", pipes); + let err = unsafe { + mkfifo(fifo_name.as_ptr(), 0o777) + }; if err != 0 { - return Err(XPCError::IOError(unsafe { - rs_strerror(errno) - })); + return Err(XPCError::IOError(rs_strerror(err))); } + let fifo_fd_read = unsafe { + open(fifo_name.as_ptr(), O_RDONLY | O_NONBLOCK) + }; + + let fifo_fd_write = unsafe { + open(fifo_name.as_ptr(), O_WRONLY | O_NONBLOCK) + }; + + log::info!("Named FIFO: {}, {}", fifo_name.to_string_lossy(), fifo_fd_write); + XPCDictionary::new() .extend(&DUMPJPCATEGORY) - .entry("fd", pipes[1] as RawFd) + .entry("fd", fifo_fd_write as RawFd) .pipe_routine_with_error_handling()?; - log::info!("Made XPC query, closing pipe"); - - let err = unsafe { - close(pipes[1]) + unsafe { + close(fifo_fd_write) }; - if err != 0 { - return Err(XPCError::IOError(unsafe { - rs_strerror(errno) - })); - } - let mut fifo_read = unsafe { - File::from_raw_fd(pipes[0]) + File::from_raw_fd(fifo_fd_read) }; let mut buf = String::new(); diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 8d7b415..42eabe4 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -324,17 +324,17 @@ impl OmniboxSubscriber for RootLayout { } OmniboxEvent::Command(OmniboxCommand::DumpJetsamPropertiesCategory) => { - let res = dumpjpcategory(); - log::info!("XPCR: {:?}", res); - Ok(None) - // let res = dumpjpcategory() - // .map_err(|e| OmniboxError::CommandError(e.to_string()))?; + // let res = dumpjpcategory(); + // log::info!("XPCR: {:?}", res); + // Ok(None) + let res = dumpjpcategory() + .map_err(|e| OmniboxError::CommandError(e.to_string()))?; - // self.cbsink_channel - // .send(scrollable_dialog("dumpjpstate", &res)) - // .expect("Must show dialog"); + self.cbsink_channel + .send(scrollable_dialog("dumpjpstate", &res)) + .expect("Must show dialog"); - // Ok(None) + Ok(None) } OmniboxEvent::Command(OmniboxCommand::Help) => { self.cbsink_channel diff --git a/xpc-sys/src/objects/xpc_object.rs b/xpc-sys/src/objects/xpc_object.rs index ac66d7d..e04ce65 100644 --- a/xpc-sys/src/objects/xpc_object.rs +++ b/xpc-sys/src/objects/xpc_object.rs @@ -141,6 +141,7 @@ impl From<&Arc> for XPCObject { impl From for XPCObject { /// Use std::os::unix::prelude type for xpc_fd_create fn from(value: RawFd) -> Self { + log::info!("Making FD from {}", value); unsafe { XPCObject::new( xpc_fd_create(value) From c0e59327c6d745843b987334f5a46ab8aa29b0f0 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Wed, 26 May 2021 12:02:32 -0400 Subject: [PATCH 12/28] fifo to reader thread to pager stdin working -- need to clean up fifo + fix piperror on write --- launchk/src/launchd/query.rs | 42 ++-------------- launchk/src/tui/dialog.rs | 15 ------ launchk/src/tui/root.rs | 92 ++++++++++++++++++++++++++++++++---- 3 files changed, 87 insertions(+), 62 deletions(-) diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index c40ae0f..2cc554c 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -153,45 +153,9 @@ pub fn dumpstate() -> Result<(usize, XPCShmem), XPCError> { Ok((usize::try_from(bytes_written).unwrap(), shmem)) } -pub fn dumpjpcategory() -> Result { - let fifo_name = unsafe { - CStr::from_ptr(tmpnam(null_mut())) - }; - - let err = unsafe { - mkfifo(fifo_name.as_ptr(), 0o777) - }; - - if err != 0 { - return Err(XPCError::IOError(rs_strerror(err))); - } - - let fifo_fd_read = unsafe { - open(fifo_name.as_ptr(), O_RDONLY | O_NONBLOCK) - }; - - let fifo_fd_write = unsafe { - open(fifo_name.as_ptr(), O_WRONLY | O_NONBLOCK) - }; - - log::info!("Named FIFO: {}, {}", fifo_name.to_string_lossy(), fifo_fd_write); - +pub fn dumpjpcategory(fd: RawFd) -> Result { XPCDictionary::new() .extend(&DUMPJPCATEGORY) - .entry("fd", fifo_fd_write as RawFd) - .pipe_routine_with_error_handling()?; - - unsafe { - close(fifo_fd_write) - }; - - let mut fifo_read = unsafe { - File::from_raw_fd(fifo_fd_read) - }; - - let mut buf = String::new(); - fifo_read.read_to_string(&mut buf) - .map_err(|e| XPCError::IOError(e.to_string()))?; - - Ok(buf) + .entry("fd", fd) + .pipe_routine_with_error_handling() } \ No newline at end of file diff --git a/launchk/src/tui/dialog.rs b/launchk/src/tui/dialog.rs index 3fea4d7..5378e69 100644 --- a/launchk/src/tui/dialog.rs +++ b/launchk/src/tui/dialog.rs @@ -175,18 +175,3 @@ pub fn show_help() -> CbSinkMessage { ) }) } - -pub fn scrollable_dialog>(title: S, content: S) -> CbSinkMessage { - let title = title.into(); - let content = content.into(); - - Box::new(move |siv| { - siv.add_layer( - Dialog::new() - .title(title) - .content(TextView::new(content).scrollable()) - .dismiss_button("OK") - .padding(Margins::trbl(2, 2, 2, 2)), - ) - }) -} diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 42eabe4..1d3ea92 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -1,4 +1,8 @@ use std::collections::VecDeque; +use std::fs::File; +use std::io::Read; +use std::os::unix::prelude::FromRawFd; +use std::ptr::null_mut; use std::sync::mpsc::{channel, Receiver, Sender}; use std::{ @@ -16,7 +20,15 @@ use cursive::view::{AnyView, ViewWrapper}; use cursive::views::{LinearLayout, NamedView, Panel}; use cursive::{Cursive, Vec2, View}; +use libc::O_NONBLOCK; +use libc::O_RDONLY; +use libc::O_WRONLY; +use libc::mkfifo; +use libc::open; +use libc::tmpnam; use tokio::runtime::Handle; +use tokio::task::block_in_place; +use xpc_sys::rs_strerror; use crate::tui::dialog::scrollable_dialog; use crate::{launchd::query::dumpjpcategory, tui::dialog::{show_csr_info, show_help}}; @@ -135,8 +147,9 @@ impl RootLayout { } fn handle_omnibox_event(&mut self, recv: OmniboxEvent) { + // TODO: handle this error with dialog as well self.on_omnibox(recv.clone()) - .expect("Root for effects only"); + .expect("i am a shitty error"); let target = self .layout @@ -323,18 +336,81 @@ impl OmniboxSubscriber for RootLayout { } } OmniboxEvent::Command(OmniboxCommand::DumpJetsamPropertiesCategory) => { + let fifo_name = unsafe { + CStr::from_ptr(tmpnam(null_mut())) + }; + + let err = unsafe { + mkfifo(fifo_name.as_ptr(), 0o777) + }; + + if err != 0 { + return Err(OmniboxError::CommandError(rs_strerror(err))); + } + + // Spawn pipe reader + let fd_read_thread = std::thread::spawn(move || { + let fifo_fd_read = unsafe { + open(fifo_name.as_ptr(), O_RDONLY) + }; + + let mut file = unsafe { + File::from_raw_fd(fifo_fd_read) + }; + + let mut buf = String::new(); + file.read_to_string(&mut buf).expect("Must read string"); + + buf + }); + + // Spawn pipe writer (XPC endpoint) + self.runtime_handle.spawn(async move { + let fifo_fd_write = unsafe { + open(fifo_name.as_ptr(), O_WRONLY | O_NONBLOCK) + }; + + dumpjpcategory(fifo_fd_write).expect("Must OK"); + + unsafe { + libc::close(fifo_fd_write) + }; + }); + + // Join reader thread + let jetsam_data = fd_read_thread + .join() + .expect("Must get jetsam data"); + + let mut pager = Command::new(*PAGER) + .stdin(Stdio::piped()) + .spawn() + .map_err(|e| OmniboxError::CommandError(e.to_string()))?; - // let res = dumpjpcategory(); - // log::info!("XPCR: {:?}", res); - // Ok(None) - let res = dumpjpcategory() + pager + .stdin + .take() + .expect("Must get pager stdin") + .write_all(jetsam_data.as_bytes()); + // TODO: broken pipe + // .map_err(|e| OmniboxError::CommandError(e.to_string()))?; + + let res = pager + .wait() .map_err(|e| OmniboxError::CommandError(e.to_string()))?; self.cbsink_channel - .send(scrollable_dialog("dumpjpstate", &res)) - .expect("Must show dialog"); + .send(Box::new(Cursive::clear)) + .expect("Must clear"); - Ok(None) + if !res.success() { + Err(OmniboxError::CommandError(format!( + "{} exited {:?}", + *PAGER, res + ))) + } else { + Ok(None) + } } OmniboxEvent::Command(OmniboxCommand::Help) => { self.cbsink_channel From 05e06fd8027ce8362cf78e72653ef033fe94386e Mon Sep 17 00:00:00 2001 From: David Stancu Date: Wed, 26 May 2021 22:20:41 -0400 Subject: [PATCH 13/28] add better mach_port_t support, differentiate between send and recv add xpc_value for f64 write some more tests --- README.md | 252 ++++++++++++++++++++++++--- launchk/src/launchd/query_builder.rs | 3 +- launchk/src/tui/dialog.rs | 1 - launchk/src/tui/root.rs | 1 - xpc-sys/src/lib.rs | 7 + xpc-sys/src/objects/xpc_object.rs | 23 ++- xpc-sys/src/objects/xpc_type.rs | 4 +- xpc-sys/src/traits/xpc_value.rs | 73 +++++++- 8 files changed, 324 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index f8115ea..6e6b656 100644 --- a/README.md +++ b/README.md @@ -55,21 +55,224 @@ To write the query for `launchctl list`: In addition to checking `errno` is 0, `pipe_routine_with_error_handling` also looks for possible `error` and `errors` keys in the response dictionary and provides an `Err()` with `xpc_strerror` contents. -#### FFI Type Conversions +#### Rust to XPC Conversions to/from Rust/XPC objects uses the [xpc.h functions documented on Apple Developer](https://developer.apple.com/documentation/xpc/xpc_services_xpc_h?language=objc) using the `From` trait. -| Rust | XPC | -|----------------------------------------|-----------------------| -| i64 | _xpc_type_int64 | -| u64 | _xpc_type_uint64 | -| f64 | _xpc_type_double | -| bool | _xpc_bool_true/false | -| Into | _xpc_type_string | -| HashMap, Into> | _xpc_type_dictionary | -| Vec> | _xpc_type_array | +| Rust | XPC | +|----------------------------------------|----------------------------| +| i64 | _xpc_type_int64 | +| u64 | _xpc_type_uint64 | +| f64 | _xpc_type_double | +| bool | _xpc_bool_true/false | +| Into | _xpc_type_string | +| HashMap, Into> | _xpc_type_dictionary | +| Vec> | _xpc_type_array | +| std::os::unix::prelude::RawFd | _xpc_type_fd | +| (MachPortType::Send, mach_port_t) | _xpc_type_mach_send | +| (MachPortType::Recv, mach_port_t) | _xpc_type_mach_recv | + +Make XPC objects for anything with `From`. Make sure to use the correct type for file descriptors and Mach ports: +```rust +let mut message: HashMap<&str, XPCObject> = HashMap::new(); + +message.insert( + "domain-port", + XPCObject::from((MachPortType::Send, get_bootstrap_port() as mach_port_t)), +); +``` + +Go from an XPC object to value via the `TryXPCValue` trait. It checks your object's type via `xpc_get_type()` and yields a clear error if you're using the wrong type: +```rust +#[test] +fn deserialize_as_wrong_type() { + let an_i64: XPCObject = XPCObject::from(42 as i64); + let as_u64: Result = an_i64.xpc_value(); + assert_eq!( + as_u64.err().unwrap(), + XPCValueError("Cannot get int64 as uint64".to_string()) + ); +} +``` + +##### XPC Dictionaries + +Go from a `HashMap` to `xpc_object_t` with the `XPCObject` type: + +```rust +let mut message: HashMap<&str, XPCObject> = HashMap::new(); +message.insert("type", XPCObject::from(1 as u64)); +message.insert("handle", XPCObject::from(0 as u64)); +message.insert("subsystem", XPCObject::from(3 as u64)); +message.insert("routine", XPCObject::from(815 as u64)); +message.insert("legacy", XPCObject::from(true)); + +let xpc_object: XPCObject = message.into(); +``` + +Call `xpc_pipe_routine` and receive `Result`: + +```rust +let xpc_object: XPCObject = message.into(); + +match xpc_object.pipe_routine() { + Ok(xpc_object) => { /* do stuff and things */ }, + Err(XPCError::PipeError(err)) => { /* err is a string w/strerror(errno) */ } +} +``` + +The response is likely an XPC dictionary -- go back to a HashMap: + +```rust +let xpc_object: XPCObject = message.into(); +let response: Result = xpc_object + .pipe_routine() + .and_then(|r| r.try_into()); + +let XPCDictionary(hm) = response.unwrap(); +let whatever = hm.get("..."); +``` + +Response dictionaries can be nested, so `XPCDictionary` has a helper included for this scenario: + +```rust +let xpc_object: XPCObject = message.into(); + +// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System" +let response: Result = xpc_object + .pipe_routine() + .and_then(|r: XPCObject| r.try_into()); + .and_then(|d: XPCDictionary| d.get(&["service", "LimitLoadToSessionType"]) + .and_then(|lltst: XPCObject| lltst.xpc_value()); +``` + +Or, retrieve the `service` key (a child XPC Dictionary) from this response: + +```rust +let xpc_object: XPCObject = message.into(); + +// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System" +let response: Result = xpc_object + .pipe_routine() + .and_then(|r: XPCObject| r.try_into()); + .and_then(|d: XPCDictionary| d.get_as_dictionary(&["service"]); + +let XPCDictionary(hm) = response.unwrap(); +let whatever = hm.get("..."); +``` + +##### XPC Arrays + +An XPC array can be made from either `Vec` or `Vec>`: + +```rust +let xpc_array = XPCObject::from(vec![XPCObject::from("eins"), XPCObject::from("zwei"), XPCObject::from("polizei")]); + +let xpc_array = XPCObject::from(vec!["eins", "zwei", "polizei"]); +``` + +Go back to `Vec` using `xpc_value`: + +```rust +let rs_vec: Vec = xpc_array.xpc_value().unwrap(); +``` + +### Credits + +A big thanks to these open source projects and general resources: + + +- [block](https://crates.io/crates/block) Obj-C block support, necessary for any XPC function taking `xpc_*_applier_t` +- [Cursive](https://github.com/gyscos/cursive) TUI +- [tokio](https://github.com/tokio-rs/tokio) ASIO +- [plist](https://crates.io/crates/plist) Parsing & validation for XML and binary plists +- [notify](https://docs.rs/notify/4.0.16/notify/) fsnotify +- [bitflags](https://docs.rs/bitflags/1.2.1/bitflags/) + +- [Apple Developer XPC services](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html) +- [Apple Developer XPC API reference](https://developer.apple.com/documentation/xpc?language=objc) +- [MOXIL / launjctl](http://newosxbook.com/articles/jlaunchctl.html) +- [geosnow - A Long Evening With macOS' sandbox](https://geosn0w.github.io/A-Long-Evening-With-macOS%27s-Sandbox/) +- [Bits of launchd - @5aelo](https://saelo.github.io/presentations/bits_of_launchd.pdf) +- [Audit tokens explained (e.g. ASID)](https://knight.sc/reverse%20engineering/2020/03/20/audit-tokens-explained.html) +- [objc.io XPC guide](https://www.objc.io/issues/14-mac/xpc/) +- The various source links found in comments, from Chrome's sandbox and other headers with definitions for private API functions. +- Last but not least, this is Apple's launchd after all, right :>)? I did not know systemd was inspired by launchd until I read [this HN comment](https://news.ycombinator.com/item?id=2565780), which sent me down this eventual rabbit hole :) + +Everything else (C) David Stancu & Contributors 2021# launchk + +[![Rust](https://github.com/mach-kernel/launchk/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/mach-kernel/launchk/actions/workflows/rust.yml) + +A WIP [Cursive](https://github.com/gyscos/cursive) TUI that makes XPC queries & helps manage launchd jobs. + +Should work on macOS 10.10+ according to the availability sec. [in the docs](https://developer.apple.com/documentation/xpc?language=objc). + + + +#### Features + +- Poll XPC for jobs and display changes as they happen +- Filter by `LaunchAgents` and `LaunchDaemons` in scopes: + - System (/System/Library/) + - Global (/Library) + - User (~/) +- fsnotify detection for new plists added to above directories +- `:load/:unload` -> `launchctl load/unload` +- `:edit` -> Open plist in `$EDITOR`, defaulting to `vim`. Supports binary plists -> shown as XML for edit, then marshalled back into binary format on save. + +### xpc-sys crate + +There is some "convenience glue" for dealing with XPC objects. Eventually, this will be broken out into its own crate. Some tests exist for not breaking data to/from FFI. + +##### Object lifecycle + +XPCObject wraps `xpc_object_t` in an `Arc`. `Drop` will invoke `xpc_release()` on objects being dropped with no other [strong refs](https://doc.rust-lang.org/std/sync/struct.Arc.html#method.strong_count). + +**NOTE**: When using Objective-C blocks with the [block crate](https://crates.io/crates/block) (e.g. looping over an array), make sure to invoke `xpc_retain()` on any object you wish to keep after the closure is dropped, or else the XPC objects in the closure will be dropped as well! See the `XPCDictionary` implementation for more details. xpc-sys handles this for you for its conversions. + +#### XPCDictionary and QueryBuilder + +While we can go from `HashMap<&str, XPCObject>` to `XPCObject`, it can be a little verbose. A `QueryBuilder` trait exposes some builder methods to make building an XPC dictionary a little easier (without all of the `into()`s, and some additional error checking). + +To write the query for `launchctl list`: + +```rust + let LIST_SERVICES: XPCDictionary = XPCDictionary::new() + // "list com.apple.Spotlight" (if specified) + // .entry("name", "com.apple.Spotlight"); + .entry("subsystem", 3 as u64) + .entry("handle", 0 as u64) + .entry("routine", 815 as u64) + .entry("legacy", true); + + let reply: Result = XPCDictionary::new() + // LIST_SERVICES is a proto + .extend(&LIST_SERVICES) + // Specify the domain type, or fall back on requester domain + .with_domain_type_or_default(Some(domain_type)) + .entry_if_present("name", name) + .pipe_routine_with_error_handling(); +``` + +In addition to checking `errno` is 0, `pipe_routine_with_error_handling` also looks for possible `error` and `errors` keys in the response dictionary and provides an `Err()` with `xpc_strerror` contents. + +#### Rust to XPC + +Conversions to/from Rust/XPC objects uses the [xpc.h functions documented on Apple Developer](https://developer.apple.com/documentation/xpc/xpc_services_xpc_h?language=objc) using the `From` trait. -Make XPC objects for anything with `From`. From earlier example, even Mach ports: +| Rust | XPC | +|----------------------------------------|----------------------------| +| i64 | _xpc_type_int64 | +| u64 | _xpc_type_uint64 | +| f64 | _xpc_type_double | +| bool | _xpc_bool_true/false | +| Into | _xpc_type_string | +| HashMap, Into> | _xpc_type_dictionary | +| Vec> | _xpc_type_array | +| std::os::unix::prelude::RawFd | _xpc_type_fd | +| mach_port_t | ??? (xpc_mach_send_create) | + +Make XPC objects for anything with `From`. Make sure to use the correct type for file descriptors and Mach ports: ```rust let mut message: HashMap<&str, XPCObject> = HashMap::new(); @@ -178,21 +381,20 @@ let rs_vec: Vec = xpc_array.xpc_value().unwrap(); A big thanks to these open source projects and general resources: -- [block](https://crates.io/crates/block) Obj-C block support, necessary for any XPC function taking `xpc_*_applier_t` -- [Cursive](https://github.com/gyscos/cursive) TUI -- [tokio](https://github.com/tokio-rs/tokio) ASIO -- [plist](https://crates.io/crates/plist) Parsing & validation for XML and binary plists -- [notify](https://docs.rs/notify/4.0.16/notify/) fsnotify -- [bitflags](https://docs.rs/bitflags/1.2.1/bitflags/) - -- [Apple Developer XPC services](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html) -- [Apple Developer XPC API reference](https://developer.apple.com/documentation/xpc?language=objc) -- [MOXIL / launjctl](http://newosxbook.com/articles/jlaunchctl.html) -- [geosnow - A Long Evening With macOS' sandbox](https://geosn0w.github.io/A-Long-Evening-With-macOS%27s-Sandbox/) +- [block](https://crates.io/crates/block) Obj-C block support, necessary for any XPC function taking `xpc_*_applier_t` +- [Cursive](https://github.com/gyscos/cursive) TUI +- [tokio](https://github.com/tokio-rs/tokio) ASIO +- [plist](https://crates.io/crates/plist) Parsing & validation for XML and binary plists +- [notify](https://docs.rs/notify/4.0.16/notify/) fsnotify +- [bitflags](https://docs.rs/bitflags/1.2.1/bitflags/) +- [Apple Developer XPC services](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html) +- [Apple Developer XPC API reference](https://developer.apple.com/documentation/xpc?language=objc) +- [MOXIL / launjctl](http://newosxbook.com/articles/jlaunchctl.html) +- [geosnow - A Long Evening With macOS' sandbox](https://geosn0w.github.io/A-Long-Evening-With-macOS%27s-Sandbox/) - [Bits of launchd - @5aelo](https://saelo.github.io/presentations/bits_of_launchd.pdf) -- [Audit tokens explained (e.g. ASID)](https://knight.sc/reverse%20engineering/2020/03/20/audit-tokens-explained.html) -- [objc.io XPC guide](https://www.objc.io/issues/14-mac/xpc/) +- [Audit tokens explained (e.g. ASID)](https://knight.sc/reverse%20engineering/2020/03/20/audit-tokens-explained.html) +- [objc.io XPC guide](https://www.objc.io/issues/14-mac/xpc/) - The various source links found in comments, from Chrome's sandbox and other headers with definitions for private API functions. -- Last but not least, this is Apple's launchd after all, right :>)? I did not know systemd was inspired by launchd until I read [this HN comment](https://news.ycombinator.com/item?id=2565780), which sent me down this eventual rabbit hole :) +- Last but not least, this is Apple's launchd after all, right :>)? I did not know systemd was inspired by launchd until I read [this HN comment](https://news.ycombinator.com/item?id=2565780), which sent me down this eventual rabbit hole :) Everything else (C) David Stancu & Contributors 2021 \ No newline at end of file diff --git a/launchk/src/launchd/query_builder.rs b/launchk/src/launchd/query_builder.rs index 7a70bc8..48b929d 100644 --- a/launchk/src/launchd/query_builder.rs +++ b/launchk/src/launchd/query_builder.rs @@ -1,5 +1,6 @@ use crate::launchd::enums::{DomainType, SessionType}; use xpc_sys::objects::xpc_dictionary::XPCDictionary; +use xpc_sys::objects::xpc_object::MachPortType; use xpc_sys::objects::xpc_object::XPCObject; use xpc_sys::{get_bootstrap_port, mach_port_t}; @@ -17,7 +18,7 @@ pub trait QueryBuilder { where Self: Sized, { - self.entry("domain-port", get_bootstrap_port() as mach_port_t) + self.entry("domain-port", (MachPortType::Send, get_bootstrap_port() as mach_port_t)) } fn with_session_type_or_default(self, session: Option) -> XPCDictionary diff --git a/launchk/src/tui/dialog.rs b/launchk/src/tui/dialog.rs index 5378e69..88ab4f0 100644 --- a/launchk/src/tui/dialog.rs +++ b/launchk/src/tui/dialog.rs @@ -1,7 +1,6 @@ use std::sync::mpsc::Sender; use cursive::Cursive; -use cursive::traits::Scrollable; use cursive::{ theme::Effect, view::Margins, diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 1d3ea92..207687f 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -30,7 +30,6 @@ use tokio::runtime::Handle; use tokio::task::block_in_place; use xpc_sys::rs_strerror; -use crate::tui::dialog::scrollable_dialog; use crate::{launchd::query::dumpjpcategory, tui::dialog::{show_csr_info, show_help}}; use crate::tui::omnibox::command::OmniboxCommand; use crate::tui::omnibox::subscribed_view::{ diff --git a/xpc-sys/src/lib.rs b/xpc-sys/src/lib.rs index 128a4d0..101d8de 100644 --- a/xpc-sys/src/lib.rs +++ b/xpc-sys/src/lib.rs @@ -41,7 +41,14 @@ extern "C" { pub fn xpc_pipe_routine(pipe: xpc_pipe_t, msg: xpc_object_t, reply: *mut xpc_object_t) -> c_int; + // https://grep.app/search?q=_xpc_type_mach_.%2A®exp=true pub fn xpc_mach_send_create(port: mach_port_t) -> xpc_object_t; + pub fn xpc_mach_recv_create(port: mach_port_t) -> xpc_object_t; + pub fn xpc_mach_send_get_right(object: xpc_object_t) -> mach_port_t; + + pub static _xpc_type_mach_send: _xpc_type_s; + pub static _xpc_type_mach_recv: _xpc_type_s; + pub fn xpc_dictionary_set_mach_send( object: xpc_object_t, name: *const c_char, diff --git a/xpc-sys/src/objects/xpc_object.rs b/xpc-sys/src/objects/xpc_object.rs index e04ce65..95b99a4 100644 --- a/xpc-sys/src/objects/xpc_object.rs +++ b/xpc-sys/src/objects/xpc_object.rs @@ -2,7 +2,7 @@ use crate::objects::xpc_type::XPCType; use crate::{ mach_port_t, xpc_array_append_value, xpc_array_create, xpc_bool_create, xpc_copy_description, xpc_double_create, xpc_int64_create, xpc_mach_send_create, xpc_object_t, xpc_release, - xpc_string_create, xpc_uint64_create, xpc_fd_create + xpc_string_create, xpc_uint64_create, xpc_fd_create, xpc_mach_recv_create }; use std::ffi::{CStr, CString}; use std::os::unix::prelude::RawFd; @@ -82,10 +82,23 @@ impl From for XPCObject { } } -impl From for XPCObject { - /// Create XPCObject via xpc_uint64_create - fn from(value: mach_port_t) -> Self { - unsafe { XPCObject::new(xpc_mach_send_create(value)) } +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum MachPortType { + Send, + Recv, +} + +impl From<(MachPortType, mach_port_t)> for XPCObject { + /// Create XPCObject via xpc_mach_send_create or xpc_mach_recv_create + fn from((mpt, value): (MachPortType, mach_port_t)) -> Self { + let xpc_object = unsafe { + match mpt { + MachPortType::Send => xpc_mach_send_create(value), + MachPortType::Recv => xpc_mach_recv_create(value) + } + }; + + XPCObject::new(xpc_object) } } diff --git a/xpc-sys/src/objects/xpc_type.rs b/xpc-sys/src/objects/xpc_type.rs index 18ecbd1..96589ab 100644 --- a/xpc-sys/src/objects/xpc_type.rs +++ b/xpc-sys/src/objects/xpc_type.rs @@ -1,7 +1,7 @@ use crate::{ _xpc_type_array, _xpc_type_bool, _xpc_type_dictionary, _xpc_type_double, _xpc_type_int64, _xpc_type_s, _xpc_type_string, _xpc_type_uint64, xpc_get_type, xpc_object_t, xpc_type_get_name, - xpc_type_t, + xpc_type_t, _xpc_type_mach_send, _xpc_type_mach_recv, }; use crate::objects::xpc_error::XPCError; @@ -63,6 +63,8 @@ lazy_static! { pub static ref String: XPCType = unsafe { (&_xpc_type_string as *const _xpc_type_s).into() }; pub static ref Bool: XPCType = unsafe { (&_xpc_type_bool as *const _xpc_type_s).into() }; pub static ref Array: XPCType = unsafe { (&_xpc_type_array as *const _xpc_type_s).into() }; + pub static ref MachSend: XPCType = unsafe { (&_xpc_type_mach_send as *const _xpc_type_s).into() }; + pub static ref MachRecv: XPCType = unsafe { (&_xpc_type_mach_recv as *const _xpc_type_s).into() }; } /// Runtime type check for XPC object. I do not know if possible/advantageous to represent diff --git a/xpc-sys/src/traits/xpc_value.rs b/xpc-sys/src/traits/xpc_value.rs index 5d60d99..b040856 100644 --- a/xpc-sys/src/traits/xpc_value.rs +++ b/xpc-sys/src/traits/xpc_value.rs @@ -3,16 +3,13 @@ use std::cell::RefCell; use std::ffi::CStr; use std::rc::Rc; -use crate::objects::xpc_object::XPCObject; +use crate::objects::xpc_object::{XPCObject, MachPortType}; use crate::objects::xpc_type; -use crate::{ - size_t, xpc_array_apply, xpc_bool_get_value, xpc_int64_get_value, xpc_object_t, xpc_retain, - xpc_string_get_string_ptr, xpc_uint64_get_value, -}; +use crate::{size_t, xpc_array_apply, xpc_bool_get_value, xpc_int64_get_value, xpc_object_t, xpc_retain, xpc_string_get_string_ptr, xpc_uint64_get_value, xpc_double_get_value, xpc_mach_send_get_right, mach_port_t, xpc_type_get_name}; use crate::objects::xpc_error::XPCError; use crate::objects::xpc_error::XPCError::ValueError; -use crate::objects::xpc_type::check_xpc_type; +use crate::objects::xpc_type::{check_xpc_type, XPCType}; /// Implement to get data out of xpc_type_t and into /// a Rust native data type @@ -36,6 +33,14 @@ impl TryXPCValue for XPCObject { } } +impl TryXPCValue for XPCObject { + fn xpc_value(&self) -> Result { + check_xpc_type(&self, &xpc_type::Double)?; + let XPCObject(obj_pointer, _) = self; + Ok(unsafe { xpc_double_get_value(**obj_pointer) }) + } +} + impl TryXPCValue for XPCObject { fn xpc_value(&self) -> Result { check_xpc_type(&self, &xpc_type::String)?; @@ -54,6 +59,31 @@ impl TryXPCValue for XPCObject { } } +impl TryXPCValue<(MachPortType, mach_port_t)> for XPCObject { + fn xpc_value(&self) -> Result<(MachPortType, mach_port_t), XPCError> { + let XPCObject(obj_pointer, xpc_type) = self; + + let types = [ + check_xpc_type(&self, &xpc_type::MachSend).map(|()| MachPortType::Send), + check_xpc_type(&self, &xpc_type::MachRecv).map(|()| MachPortType::Recv), + ]; + + for check in types.iter() { + if check.is_ok() { + return Ok((*check.as_ref().unwrap(), unsafe { + xpc_mach_send_get_right(**obj_pointer) + })); + } + } + + Err(XPCError::ValueError( + format!("Object is {} and neither _xpc_type_mach_send nor _xpc_type_mach_recv", unsafe { + CStr::from_ptr(xpc_type_get_name(xpc_type.0)).to_string_lossy() + }) + )) + } +} + impl TryXPCValue> for XPCObject { fn xpc_value(&self) -> Result, XPCError> { check_xpc_type(&self, &xpc_type::Array)?; @@ -86,10 +116,13 @@ impl TryXPCValue> for XPCObject { #[cfg(test)] mod tests { + use crate::get_bootstrap_port; use crate::objects::xpc_error::XPCError; use crate::objects::xpc_error::XPCError::ValueError; + use crate::objects::xpc_object::MachPortType; use crate::objects::xpc_object::XPCObject; use crate::traits::xpc_value::TryXPCValue; + use crate::mach_port_t; #[test] fn xpc_to_rs_with_wrong_type() { @@ -131,6 +164,34 @@ mod tests { assert_eq!(std::u64::MAX, rs_u64); } + #[test] + fn xpc_value_f64() { + let xpc_f64 = XPCObject::from(std::f64::MAX); + let rs_f64: f64 = xpc_f64.xpc_value().unwrap(); + assert_eq!(std::f64::MAX, rs_f64); + } + + #[test] + fn xpc_value_mach_send() { + let xpc_bootstrap_port = XPCObject::from((MachPortType::Send, get_bootstrap_port() as mach_port_t)); + let (mpt, port): (MachPortType, mach_port_t) = xpc_bootstrap_port.xpc_value().unwrap(); + + assert_eq!(MachPortType::Send, mpt); + assert_eq!(get_bootstrap_port(), port); + } + + // Can't find any example in the wild, the value is 0 vs the provided 42, it likely + // does some kind of validation. + // + // #[test] + // fn xpc_value_mach_recv() { + // let xpc_mach_recv = XPCObject::from((MachPortType::Recv, 42 as mach_port_t)); + // let (mpt, port): (MachPortType, mach_port_t) = xpc_mach_recv.xpc_value().unwrap(); + // + // assert_eq!(MachPortType::Recv, mpt); + // assert_eq!(42, port); + // } + #[test] fn xpc_value_array() { let xpc_array = XPCObject::from(vec!["eins", "zwei", "polizei"]); From ce8682cd10dbebda47d45b15a3f1609196cc87fa Mon Sep 17 00:00:00 2001 From: David Stancu Date: Wed, 26 May 2021 22:22:20 -0400 Subject: [PATCH 14/28] fix/fmt --- launchk/src/launchd/query.rs | 26 +++++++++----- launchk/src/launchd/query_builder.rs | 5 ++- launchk/src/tui/root.rs | 54 +++++++++++----------------- xpc-sys/src/objects/xpc_error.rs | 2 +- xpc-sys/src/objects/xpc_object.rs | 12 +++---- xpc-sys/src/objects/xpc_type.rs | 10 +++--- xpc-sys/src/traits/xpc_value.rs | 24 +++++++------ 7 files changed, 68 insertions(+), 65 deletions(-) diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index 2cc554c..2bd6740 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -1,10 +1,24 @@ use crate::launchd::message::{ - DISABLE_NAMES, DUMPSTATE, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, DUMPJPCATEGORY + DISABLE_NAMES, DUMPJPCATEGORY, DUMPSTATE, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, }; -use std::{collections::HashSet, ffi::{CStr, CString, OsStr}, fs::File, io::Read, os::unix::prelude::{FromRawFd, RawFd}, path::Path, ptr::{null, null_mut}}; use std::convert::TryFrom; +use std::{ + collections::HashSet, + ffi::{CStr, CString, OsStr}, + fs::File, + io::Read, + os::unix::prelude::{FromRawFd, RawFd}, + path::Path, + ptr::{null, null_mut}, +}; -use xpc_sys::{MAP_SHARED, errno, objects::{xpc_object::{XPCObject}, xpc_shmem::XPCShmem}, rs_strerror, traits::{xpc_pipeable::XPCPipeable, xpc_value::TryXPCValue}}; +use xpc_sys::{ + errno, + objects::{xpc_object::XPCObject, xpc_shmem::XPCShmem}, + rs_strerror, + traits::{xpc_pipeable::XPCPipeable, xpc_value::TryXPCValue}, + MAP_SHARED, +}; use crate::launchd::entry_status::ENTRY_STATUS_CACHE; use std::iter::FromIterator; @@ -14,10 +28,6 @@ use xpc_sys::objects::xpc_error::XPCError; use crate::launchd::enums::{DomainType, SessionType}; use crate::launchd::query_builder::QueryBuilder; -use libc::{self, O_NONBLOCK, O_RDONLY, O_WRONLY, close, fdopen, fileno, fopen, mkfifo, open, pipe, tmpnam}; - -use std::os::raw::c_int; - pub fn find_in_all>(label: S) -> Result<(DomainType, XPCDictionary), XPCError> { let label_string = label.into(); @@ -158,4 +168,4 @@ pub fn dumpjpcategory(fd: RawFd) -> Result { .extend(&DUMPJPCATEGORY) .entry("fd", fd) .pipe_routine_with_error_handling() -} \ No newline at end of file +} diff --git a/launchk/src/launchd/query_builder.rs b/launchk/src/launchd/query_builder.rs index 48b929d..1f5f530 100644 --- a/launchk/src/launchd/query_builder.rs +++ b/launchk/src/launchd/query_builder.rs @@ -18,7 +18,10 @@ pub trait QueryBuilder { where Self: Sized, { - self.entry("domain-port", (MachPortType::Send, get_bootstrap_port() as mach_port_t)) + self.entry( + "domain-port", + (MachPortType::Send, get_bootstrap_port() as mach_port_t), + ) } fn with_session_type_or_default(self, session: Option) -> XPCDictionary diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 207687f..63d5a73 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -20,17 +20,16 @@ use cursive::view::{AnyView, ViewWrapper}; use cursive::views::{LinearLayout, NamedView, Panel}; use cursive::{Cursive, Vec2, View}; -use libc::O_NONBLOCK; -use libc::O_RDONLY; -use libc::O_WRONLY; use libc::mkfifo; use libc::open; use libc::tmpnam; +use libc::O_NONBLOCK; +use libc::O_RDONLY; +use libc::O_WRONLY; use tokio::runtime::Handle; -use tokio::task::block_in_place; + use xpc_sys::rs_strerror; -use crate::{launchd::query::dumpjpcategory, tui::dialog::{show_csr_info, show_help}}; use crate::tui::omnibox::command::OmniboxCommand; use crate::tui::omnibox::subscribed_view::{ OmniboxResult, OmniboxSubscribedView, OmniboxSubscriber, Subscribable, @@ -38,6 +37,10 @@ use crate::tui::omnibox::subscribed_view::{ use crate::tui::omnibox::view::{OmniboxError, OmniboxEvent, OmniboxView}; use crate::tui::service_list::view::ServiceListView; use crate::tui::sysinfo::SysInfo; +use crate::{ + launchd::query::dumpjpcategory, + tui::dialog::{show_csr_info, show_help}, +}; use crate::{launchd::query::dumpstate, tui::dialog}; lazy_static! { @@ -147,8 +150,7 @@ impl RootLayout { fn handle_omnibox_event(&mut self, recv: OmniboxEvent) { // TODO: handle this error with dialog as well - self.on_omnibox(recv.clone()) - .expect("i am a shitty error"); + self.on_omnibox(recv.clone()).expect("i am a shitty error"); let target = self .layout @@ -335,27 +337,19 @@ impl OmniboxSubscriber for RootLayout { } } OmniboxEvent::Command(OmniboxCommand::DumpJetsamPropertiesCategory) => { - let fifo_name = unsafe { - CStr::from_ptr(tmpnam(null_mut())) - }; - - let err = unsafe { - mkfifo(fifo_name.as_ptr(), 0o777) - }; - + let fifo_name = unsafe { CStr::from_ptr(tmpnam(null_mut())) }; + + let err = unsafe { mkfifo(fifo_name.as_ptr(), 0o777) }; + if err != 0 { return Err(OmniboxError::CommandError(rs_strerror(err))); } // Spawn pipe reader let fd_read_thread = std::thread::spawn(move || { - let fifo_fd_read = unsafe { - open(fifo_name.as_ptr(), O_RDONLY) - }; + let fifo_fd_read = unsafe { open(fifo_name.as_ptr(), O_RDONLY) }; - let mut file = unsafe { - File::from_raw_fd(fifo_fd_read) - }; + let mut file = unsafe { File::from_raw_fd(fifo_fd_read) }; let mut buf = String::new(); file.read_to_string(&mut buf).expect("Must read string"); @@ -365,21 +359,15 @@ impl OmniboxSubscriber for RootLayout { // Spawn pipe writer (XPC endpoint) self.runtime_handle.spawn(async move { - let fifo_fd_write = unsafe { - open(fifo_name.as_ptr(), O_WRONLY | O_NONBLOCK) - }; - + let fifo_fd_write = unsafe { open(fifo_name.as_ptr(), O_WRONLY | O_NONBLOCK) }; + dumpjpcategory(fifo_fd_write).expect("Must OK"); - unsafe { - libc::close(fifo_fd_write) - }; + unsafe { libc::close(fifo_fd_write) }; }); // Join reader thread - let jetsam_data = fd_read_thread - .join() - .expect("Must get jetsam data"); + let jetsam_data = fd_read_thread.join().expect("Must get jetsam data"); let mut pager = Command::new(*PAGER) .stdin(Stdio::piped()) @@ -391,8 +379,8 @@ impl OmniboxSubscriber for RootLayout { .take() .expect("Must get pager stdin") .write_all(jetsam_data.as_bytes()); - // TODO: broken pipe - // .map_err(|e| OmniboxError::CommandError(e.to_string()))?; + // TODO: broken pipe + // .map_err(|e| OmniboxError::CommandError(e.to_string()))?; let res = pager .wait() diff --git a/xpc-sys/src/objects/xpc_error.rs b/xpc-sys/src/objects/xpc_error.rs index 4446cc9..b15b7de 100644 --- a/xpc-sys/src/objects/xpc_error.rs +++ b/xpc-sys/src/objects/xpc_error.rs @@ -1,5 +1,5 @@ use crate::objects::xpc_error::XPCError::{ - DictionaryError, PipeError, QueryError, IOError, ValueError, + DictionaryError, IOError, PipeError, QueryError, ValueError, }; use std::error::Error; use std::fmt::{Display, Formatter}; diff --git a/xpc-sys/src/objects/xpc_object.rs b/xpc-sys/src/objects/xpc_object.rs index 95b99a4..cc9d9fa 100644 --- a/xpc-sys/src/objects/xpc_object.rs +++ b/xpc-sys/src/objects/xpc_object.rs @@ -1,8 +1,8 @@ use crate::objects::xpc_type::XPCType; use crate::{ mach_port_t, xpc_array_append_value, xpc_array_create, xpc_bool_create, xpc_copy_description, - xpc_double_create, xpc_int64_create, xpc_mach_send_create, xpc_object_t, xpc_release, - xpc_string_create, xpc_uint64_create, xpc_fd_create, xpc_mach_recv_create + xpc_double_create, xpc_fd_create, xpc_int64_create, xpc_mach_recv_create, xpc_mach_send_create, + xpc_object_t, xpc_release, xpc_string_create, xpc_uint64_create, }; use std::ffi::{CStr, CString}; use std::os::unix::prelude::RawFd; @@ -94,7 +94,7 @@ impl From<(MachPortType, mach_port_t)> for XPCObject { let xpc_object = unsafe { match mpt { MachPortType::Send => xpc_mach_send_create(value), - MachPortType::Recv => xpc_mach_recv_create(value) + MachPortType::Recv => xpc_mach_recv_create(value), } }; @@ -155,11 +155,7 @@ impl From for XPCObject { /// Use std::os::unix::prelude type for xpc_fd_create fn from(value: RawFd) -> Self { log::info!("Making FD from {}", value); - unsafe { - XPCObject::new( - xpc_fd_create(value) - ) - } + unsafe { XPCObject::new(xpc_fd_create(value)) } } } diff --git a/xpc-sys/src/objects/xpc_type.rs b/xpc-sys/src/objects/xpc_type.rs index 96589ab..f20e527 100644 --- a/xpc-sys/src/objects/xpc_type.rs +++ b/xpc-sys/src/objects/xpc_type.rs @@ -1,7 +1,7 @@ use crate::{ _xpc_type_array, _xpc_type_bool, _xpc_type_dictionary, _xpc_type_double, _xpc_type_int64, - _xpc_type_s, _xpc_type_string, _xpc_type_uint64, xpc_get_type, xpc_object_t, xpc_type_get_name, - xpc_type_t, _xpc_type_mach_send, _xpc_type_mach_recv, + _xpc_type_mach_recv, _xpc_type_mach_send, _xpc_type_s, _xpc_type_string, _xpc_type_uint64, + xpc_get_type, xpc_object_t, xpc_type_get_name, xpc_type_t, }; use crate::objects::xpc_error::XPCError; @@ -63,8 +63,10 @@ lazy_static! { pub static ref String: XPCType = unsafe { (&_xpc_type_string as *const _xpc_type_s).into() }; pub static ref Bool: XPCType = unsafe { (&_xpc_type_bool as *const _xpc_type_s).into() }; pub static ref Array: XPCType = unsafe { (&_xpc_type_array as *const _xpc_type_s).into() }; - pub static ref MachSend: XPCType = unsafe { (&_xpc_type_mach_send as *const _xpc_type_s).into() }; - pub static ref MachRecv: XPCType = unsafe { (&_xpc_type_mach_recv as *const _xpc_type_s).into() }; + pub static ref MachSend: XPCType = + unsafe { (&_xpc_type_mach_send as *const _xpc_type_s).into() }; + pub static ref MachRecv: XPCType = + unsafe { (&_xpc_type_mach_recv as *const _xpc_type_s).into() }; } /// Runtime type check for XPC object. I do not know if possible/advantageous to represent diff --git a/xpc-sys/src/traits/xpc_value.rs b/xpc-sys/src/traits/xpc_value.rs index b040856..0fa4c69 100644 --- a/xpc-sys/src/traits/xpc_value.rs +++ b/xpc-sys/src/traits/xpc_value.rs @@ -3,13 +3,17 @@ use std::cell::RefCell; use std::ffi::CStr; use std::rc::Rc; -use crate::objects::xpc_object::{XPCObject, MachPortType}; +use crate::objects::xpc_object::{MachPortType, XPCObject}; use crate::objects::xpc_type; -use crate::{size_t, xpc_array_apply, xpc_bool_get_value, xpc_int64_get_value, xpc_object_t, xpc_retain, xpc_string_get_string_ptr, xpc_uint64_get_value, xpc_double_get_value, xpc_mach_send_get_right, mach_port_t, xpc_type_get_name}; +use crate::{ + mach_port_t, size_t, xpc_array_apply, xpc_bool_get_value, xpc_double_get_value, + xpc_int64_get_value, xpc_mach_send_get_right, xpc_object_t, xpc_retain, + xpc_string_get_string_ptr, xpc_type_get_name, xpc_uint64_get_value, +}; use crate::objects::xpc_error::XPCError; use crate::objects::xpc_error::XPCError::ValueError; -use crate::objects::xpc_type::{check_xpc_type, XPCType}; +use crate::objects::xpc_type::check_xpc_type; /// Implement to get data out of xpc_type_t and into /// a Rust native data type @@ -76,11 +80,10 @@ impl TryXPCValue<(MachPortType, mach_port_t)> for XPCObject { } } - Err(XPCError::ValueError( - format!("Object is {} and neither _xpc_type_mach_send nor _xpc_type_mach_recv", unsafe { - CStr::from_ptr(xpc_type_get_name(xpc_type.0)).to_string_lossy() - }) - )) + Err(XPCError::ValueError(format!( + "Object is {} and neither _xpc_type_mach_send nor _xpc_type_mach_recv", + unsafe { CStr::from_ptr(xpc_type_get_name(xpc_type.0)).to_string_lossy() } + ))) } } @@ -117,12 +120,12 @@ impl TryXPCValue> for XPCObject { #[cfg(test)] mod tests { use crate::get_bootstrap_port; + use crate::mach_port_t; use crate::objects::xpc_error::XPCError; use crate::objects::xpc_error::XPCError::ValueError; use crate::objects::xpc_object::MachPortType; use crate::objects::xpc_object::XPCObject; use crate::traits::xpc_value::TryXPCValue; - use crate::mach_port_t; #[test] fn xpc_to_rs_with_wrong_type() { @@ -173,7 +176,8 @@ mod tests { #[test] fn xpc_value_mach_send() { - let xpc_bootstrap_port = XPCObject::from((MachPortType::Send, get_bootstrap_port() as mach_port_t)); + let xpc_bootstrap_port = + XPCObject::from((MachPortType::Send, get_bootstrap_port() as mach_port_t)); let (mpt, port): (MachPortType, mach_port_t) = xpc_bootstrap_port.xpc_value().unwrap(); assert_eq!(MachPortType::Send, mpt); From 468df0b0be3360f686a1096201d29160e3ea0cda Mon Sep 17 00:00:00 2001 From: David Stancu Date: Wed, 26 May 2021 22:54:10 -0400 Subject: [PATCH 15/28] clean up after fifo, handle root and child view error handling in the same way --- README.md | 203 +------------------------------- launchk/src/tui/root.rs | 58 +++++---- xpc-sys/src/traits/xpc_value.rs | 19 ++- 3 files changed, 46 insertions(+), 234 deletions(-) diff --git a/README.md b/README.md index 6e6b656..2fc7344 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,10 @@ Should work on macOS 10.10+ according to the availability sec. [in the docs](htt - Global (/Library) - User (~/) - fsnotify detection for new plists added to above directories -- `:load/:unload` -> `launchctl load/unload` +- load +- unload +- dumpstate (opens in `$PAGER`) +- dumpjpcategory (opens in `$PAGER`) - `:edit` -> Open plist in `$EDITOR`, defaulting to `vim`. Supports binary plists -> shown as XML for edit, then marshalled back into binary format on save. ### xpc-sys crate @@ -199,202 +202,4 @@ A big thanks to these open source projects and general resources: - The various source links found in comments, from Chrome's sandbox and other headers with definitions for private API functions. - Last but not least, this is Apple's launchd after all, right :>)? I did not know systemd was inspired by launchd until I read [this HN comment](https://news.ycombinator.com/item?id=2565780), which sent me down this eventual rabbit hole :) -Everything else (C) David Stancu & Contributors 2021# launchk - -[![Rust](https://github.com/mach-kernel/launchk/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/mach-kernel/launchk/actions/workflows/rust.yml) - -A WIP [Cursive](https://github.com/gyscos/cursive) TUI that makes XPC queries & helps manage launchd jobs. - -Should work on macOS 10.10+ according to the availability sec. [in the docs](https://developer.apple.com/documentation/xpc?language=objc). - - - -#### Features - -- Poll XPC for jobs and display changes as they happen -- Filter by `LaunchAgents` and `LaunchDaemons` in scopes: - - System (/System/Library/) - - Global (/Library) - - User (~/) -- fsnotify detection for new plists added to above directories -- `:load/:unload` -> `launchctl load/unload` -- `:edit` -> Open plist in `$EDITOR`, defaulting to `vim`. Supports binary plists -> shown as XML for edit, then marshalled back into binary format on save. - -### xpc-sys crate - -There is some "convenience glue" for dealing with XPC objects. Eventually, this will be broken out into its own crate. Some tests exist for not breaking data to/from FFI. - -##### Object lifecycle - -XPCObject wraps `xpc_object_t` in an `Arc`. `Drop` will invoke `xpc_release()` on objects being dropped with no other [strong refs](https://doc.rust-lang.org/std/sync/struct.Arc.html#method.strong_count). - -**NOTE**: When using Objective-C blocks with the [block crate](https://crates.io/crates/block) (e.g. looping over an array), make sure to invoke `xpc_retain()` on any object you wish to keep after the closure is dropped, or else the XPC objects in the closure will be dropped as well! See the `XPCDictionary` implementation for more details. xpc-sys handles this for you for its conversions. - -#### XPCDictionary and QueryBuilder - -While we can go from `HashMap<&str, XPCObject>` to `XPCObject`, it can be a little verbose. A `QueryBuilder` trait exposes some builder methods to make building an XPC dictionary a little easier (without all of the `into()`s, and some additional error checking). - -To write the query for `launchctl list`: - -```rust - let LIST_SERVICES: XPCDictionary = XPCDictionary::new() - // "list com.apple.Spotlight" (if specified) - // .entry("name", "com.apple.Spotlight"); - .entry("subsystem", 3 as u64) - .entry("handle", 0 as u64) - .entry("routine", 815 as u64) - .entry("legacy", true); - - let reply: Result = XPCDictionary::new() - // LIST_SERVICES is a proto - .extend(&LIST_SERVICES) - // Specify the domain type, or fall back on requester domain - .with_domain_type_or_default(Some(domain_type)) - .entry_if_present("name", name) - .pipe_routine_with_error_handling(); -``` - -In addition to checking `errno` is 0, `pipe_routine_with_error_handling` also looks for possible `error` and `errors` keys in the response dictionary and provides an `Err()` with `xpc_strerror` contents. - -#### Rust to XPC - -Conversions to/from Rust/XPC objects uses the [xpc.h functions documented on Apple Developer](https://developer.apple.com/documentation/xpc/xpc_services_xpc_h?language=objc) using the `From` trait. - -| Rust | XPC | -|----------------------------------------|----------------------------| -| i64 | _xpc_type_int64 | -| u64 | _xpc_type_uint64 | -| f64 | _xpc_type_double | -| bool | _xpc_bool_true/false | -| Into | _xpc_type_string | -| HashMap, Into> | _xpc_type_dictionary | -| Vec> | _xpc_type_array | -| std::os::unix::prelude::RawFd | _xpc_type_fd | -| mach_port_t | ??? (xpc_mach_send_create) | - -Make XPC objects for anything with `From`. Make sure to use the correct type for file descriptors and Mach ports: -```rust -let mut message: HashMap<&str, XPCObject> = HashMap::new(); - -message.insert( - "domain-port", - XPCObject::from(get_bootstrap_port() as mach_port_t), -); -``` - -Go from an XPC object to value via the `TryXPCValue` trait. It checks your object's type via `xpc_get_type()` and yields a clear error if you're using the wrong type: -```rust -#[test] -fn deserialize_as_wrong_type() { - let an_i64: XPCObject = XPCObject::from(42 as i64); - let as_u64: Result = an_i64.xpc_value(); - assert_eq!( - as_u64.err().unwrap(), - XPCValueError("Cannot get int64 as uint64".to_string()) - ); -} -``` - -##### XPC Dictionaries - -Go from a `HashMap` to `xpc_object_t` with the `XPCObject` type: - -```rust -let mut message: HashMap<&str, XPCObject> = HashMap::new(); -message.insert("type", XPCObject::from(1 as u64)); -message.insert("handle", XPCObject::from(0 as u64)); -message.insert("subsystem", XPCObject::from(3 as u64)); -message.insert("routine", XPCObject::from(815 as u64)); -message.insert("legacy", XPCObject::from(true)); - -let xpc_object: XPCObject = message.into(); -``` - -Call `xpc_pipe_routine` and receive `Result`: - -```rust -let xpc_object: XPCObject = message.into(); - -match xpc_object.pipe_routine() { - Ok(xpc_object) => { /* do stuff and things */ }, - Err(XPCError::PipeError(err)) => { /* err is a string w/strerror(errno) */ } -} -``` - -The response is likely an XPC dictionary -- go back to a HashMap: - -```rust -let xpc_object: XPCObject = message.into(); -let response: Result = xpc_object - .pipe_routine() - .and_then(|r| r.try_into()); - -let XPCDictionary(hm) = response.unwrap(); -let whatever = hm.get("..."); -``` - -Response dictionaries can be nested, so `XPCDictionary` has a helper included for this scenario: - -```rust -let xpc_object: XPCObject = message.into(); - -// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System" -let response: Result = xpc_object - .pipe_routine() - .and_then(|r: XPCObject| r.try_into()); - .and_then(|d: XPCDictionary| d.get(&["service", "LimitLoadToSessionType"]) - .and_then(|lltst: XPCObject| lltst.xpc_value()); -``` - -Or, retrieve the `service` key (a child XPC Dictionary) from this response: - -```rust -let xpc_object: XPCObject = message.into(); - -// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System" -let response: Result = xpc_object - .pipe_routine() - .and_then(|r: XPCObject| r.try_into()); - .and_then(|d: XPCDictionary| d.get_as_dictionary(&["service"]); - -let XPCDictionary(hm) = response.unwrap(); -let whatever = hm.get("..."); -``` - -##### XPC Arrays - -An XPC array can be made from either `Vec` or `Vec>`: - -```rust -let xpc_array = XPCObject::from(vec![XPCObject::from("eins"), XPCObject::from("zwei"), XPCObject::from("polizei")]); - -let xpc_array = XPCObject::from(vec!["eins", "zwei", "polizei"]); -``` - -Go back to `Vec` using `xpc_value`: - -```rust -let rs_vec: Vec = xpc_array.xpc_value().unwrap(); -``` - -### Credits - -A big thanks to these open source projects and general resources: - -- [block](https://crates.io/crates/block) Obj-C block support, necessary for any XPC function taking `xpc_*_applier_t` -- [Cursive](https://github.com/gyscos/cursive) TUI -- [tokio](https://github.com/tokio-rs/tokio) ASIO -- [plist](https://crates.io/crates/plist) Parsing & validation for XML and binary plists -- [notify](https://docs.rs/notify/4.0.16/notify/) fsnotify -- [bitflags](https://docs.rs/bitflags/1.2.1/bitflags/) -- [Apple Developer XPC services](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html) -- [Apple Developer XPC API reference](https://developer.apple.com/documentation/xpc?language=objc) -- [MOXIL / launjctl](http://newosxbook.com/articles/jlaunchctl.html) -- [geosnow - A Long Evening With macOS' sandbox](https://geosn0w.github.io/A-Long-Evening-With-macOS%27s-Sandbox/) -- [Bits of launchd - @5aelo](https://saelo.github.io/presentations/bits_of_launchd.pdf) -- [Audit tokens explained (e.g. ASID)](https://knight.sc/reverse%20engineering/2020/03/20/audit-tokens-explained.html) -- [objc.io XPC guide](https://www.objc.io/issues/14-mac/xpc/) -- The various source links found in comments, from Chrome's sandbox and other headers with definitions for private API functions. -- Last but not least, this is Apple's launchd after all, right :>)? I did not know systemd was inspired by launchd until I read [this HN comment](https://news.ycombinator.com/item?id=2565780), which sent me down this eventual rabbit hole :) - Everything else (C) David Stancu & Contributors 2021 \ No newline at end of file diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 63d5a73..8fcca64 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -1,5 +1,5 @@ use std::collections::VecDeque; -use std::fs::File; +use std::fs::{File, remove_file}; use std::io::Read; use std::os::unix::prelude::FromRawFd; use std::ptr::null_mut; @@ -149,8 +149,7 @@ impl RootLayout { } fn handle_omnibox_event(&mut self, recv: OmniboxEvent) { - // TODO: handle this error with dialog as well - self.on_omnibox(recv.clone()).expect("i am a shitty error"); + let self_event = self.on_omnibox(recv.clone()); let target = self .layout @@ -158,18 +157,25 @@ impl RootLayout { .and_then(|v| v.as_any_mut().downcast_mut::()) .expect("Must forward to ServiceList"); - match target.on_omnibox(recv) { - // Forward Omnibox command responses from view - Ok(Some(c)) => self - .omnibox_tx - .send(OmniboxEvent::Command(c)) - .expect("Must send response commands"), - Err(OmniboxError::CommandError(s)) => self - .cbsink_channel - .send(dialog::show_error(s)) - .expect("Must show error"), - _ => {} - }; + let omnibox_events = [ + self_event, + target.on_omnibox(recv) + ]; + + for omnibox_event in &omnibox_events { + match omnibox_event { + // Forward Omnibox command responses from view + Ok(Some(c)) => self + .omnibox_tx + .send(OmniboxEvent::Command(c.clone())) + .expect("Must send response commands"), + Err(OmniboxError::CommandError(s)) => self + .cbsink_channel + .send(dialog::show_error(s.clone())) + .expect("Must show error"), + _ => {} + } + } } fn ring_to_arrows(&mut self) -> Option { @@ -338,7 +344,6 @@ impl OmniboxSubscriber for RootLayout { } OmniboxEvent::Command(OmniboxCommand::DumpJetsamPropertiesCategory) => { let fifo_name = unsafe { CStr::from_ptr(tmpnam(null_mut())) }; - let err = unsafe { mkfifo(fifo_name.as_ptr(), 0o777) }; if err != 0 { @@ -348,39 +353,40 @@ impl OmniboxSubscriber for RootLayout { // Spawn pipe reader let fd_read_thread = std::thread::spawn(move || { let fifo_fd_read = unsafe { open(fifo_name.as_ptr(), O_RDONLY) }; - let mut file = unsafe { File::from_raw_fd(fifo_fd_read) }; let mut buf = String::new(); file.read_to_string(&mut buf).expect("Must read string"); + unsafe { libc::close(fifo_fd_read) }; + buf }); // Spawn pipe writer (XPC endpoint) - self.runtime_handle.spawn(async move { + let fd_write_thread = std::thread::spawn(move || { let fifo_fd_write = unsafe { open(fifo_name.as_ptr(), O_WRONLY | O_NONBLOCK) }; - dumpjpcategory(fifo_fd_write).expect("Must OK"); - unsafe { libc::close(fifo_fd_write) }; }); - // Join reader thread - let jetsam_data = fd_read_thread.join().expect("Must get jetsam data"); + // Join reader thread (and close fd) + let jetsam_data = fd_read_thread.join().expect("Must read jetsam data"); + // Join writer thread (and close fd) + fd_write_thread.join().expect("Must finish dump query"); let mut pager = Command::new(*PAGER) .stdin(Stdio::piped()) .spawn() .map_err(|e| OmniboxError::CommandError(e.to_string()))?; + // Broken pipe unless scroll to end, do not throw an error pager .stdin .take() .expect("Must get pager stdin") - .write_all(jetsam_data.as_bytes()); - // TODO: broken pipe - // .map_err(|e| OmniboxError::CommandError(e.to_string()))?; + .write_all(jetsam_data.as_bytes()) + .unwrap_or(()); let res = pager .wait() @@ -390,6 +396,8 @@ impl OmniboxSubscriber for RootLayout { .send(Box::new(Cursive::clear)) .expect("Must clear"); + remove_file(fifo_name.to_string_lossy().to_string()).map_err(|e| OmniboxError::CommandError(e.to_string()))?; + if !res.success() { Err(OmniboxError::CommandError(format!( "{} exited {:?}", diff --git a/xpc-sys/src/traits/xpc_value.rs b/xpc-sys/src/traits/xpc_value.rs index 0fa4c69..5b1257a 100644 --- a/xpc-sys/src/traits/xpc_value.rs +++ b/xpc-sys/src/traits/xpc_value.rs @@ -72,7 +72,7 @@ impl TryXPCValue<(MachPortType, mach_port_t)> for XPCObject { check_xpc_type(&self, &xpc_type::MachRecv).map(|()| MachPortType::Recv), ]; - for check in types.iter() { + for check in &types { if check.is_ok() { return Ok((*check.as_ref().unwrap(), unsafe { xpc_mach_send_get_right(**obj_pointer) @@ -186,15 +186,14 @@ mod tests { // Can't find any example in the wild, the value is 0 vs the provided 42, it likely // does some kind of validation. - // - // #[test] - // fn xpc_value_mach_recv() { - // let xpc_mach_recv = XPCObject::from((MachPortType::Recv, 42 as mach_port_t)); - // let (mpt, port): (MachPortType, mach_port_t) = xpc_mach_recv.xpc_value().unwrap(); - // - // assert_eq!(MachPortType::Recv, mpt); - // assert_eq!(42, port); - // } + #[test] + fn xpc_value_mach_recv() { + let xpc_mach_recv = XPCObject::from((MachPortType::Recv, 42 as mach_port_t)); + let (mpt, _port): (MachPortType, mach_port_t) = xpc_mach_recv.xpc_value().unwrap(); + + assert_eq!(MachPortType::Recv, mpt); + // assert_eq!(42, port); + } #[test] fn xpc_value_array() { From 142a66536726c8be121e5b88a3ecdbc41041192f Mon Sep 17 00:00:00 2001 From: David Stancu Date: Wed, 26 May 2021 23:11:23 -0400 Subject: [PATCH 16/28] add xpc-sys metadata --- Cargo.lock | 2 +- xpc-sys/Cargo.toml | 6 +- xpc-sys/README.md | 155 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 xpc-sys/README.md diff --git a/Cargo.lock b/Cargo.lock index a825312..8ad1827 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1385,7 +1385,7 @@ checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" [[package]] name = "xpc-sys" -version = "0.1.0" +version = "0.1.0-a1" dependencies = [ "bindgen", "bitflags", diff --git a/xpc-sys/Cargo.toml b/xpc-sys/Cargo.toml index fe94dd8..dce88eb 100644 --- a/xpc-sys/Cargo.toml +++ b/xpc-sys/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "xpc-sys" -version = "0.1.0" +description = "Conveniently call routines with wrappers for xpc_pipe_routine() and go from Rust types to XPC objects and back!" +version = "0.1.0-a1" authors = ["David Stancu "] +license = "MIT" edition = "2018" +keywords = ["apple", "xpc", "xpc-dictionary"] +categories = ["external-ffi-bindings", "os::macos-apis"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/xpc-sys/README.md b/xpc-sys/README.md new file mode 100644 index 0000000..19d8770 --- /dev/null +++ b/xpc-sys/README.md @@ -0,0 +1,155 @@ +# xpc-sys + +##### Object lifecycle + +XPCObject wraps `xpc_object_t` in an `Arc`. `Drop` will invoke `xpc_release()` on objects being dropped with no other [strong refs](https://doc.rust-lang.org/std/sync/struct.Arc.html#method.strong_count). + +**NOTE**: When using Objective-C blocks with the [block crate](https://crates.io/crates/block) (e.g. looping over an array), make sure to invoke `xpc_retain()` on any object you wish to keep after the closure is dropped, or else the XPC objects in the closure will be dropped as well! See the `XPCDictionary` implementation for more details. xpc-sys handles this for you for its conversions. + +#### XPCDictionary and QueryBuilder + +While we can go from `HashMap<&str, XPCObject>` to `XPCObject`, it can be a little verbose. A `QueryBuilder` trait exposes some builder methods to make building an XPC dictionary a little easier (without all of the `into()`s, and some additional error checking). + +To write the query for `launchctl list`: + +```rust + let LIST_SERVICES: XPCDictionary = XPCDictionary::new() + // "list com.apple.Spotlight" (if specified) + // .entry("name", "com.apple.Spotlight"); + .entry("subsystem", 3 as u64) + .entry("handle", 0 as u64) + .entry("routine", 815 as u64) + .entry("legacy", true); + + let reply: Result = XPCDictionary::new() + // LIST_SERVICES is a proto + .extend(&LIST_SERVICES) + // Specify the domain type, or fall back on requester domain + .with_domain_type_or_default(Some(domain_type)) + .entry_if_present("name", name) + .pipe_routine_with_error_handling(); +``` + +In addition to checking `errno` is 0, `pipe_routine_with_error_handling` also looks for possible `error` and `errors` keys in the response dictionary and provides an `Err()` with `xpc_strerror` contents. + +#### Rust to XPC + +Conversions to/from Rust/XPC objects uses the [xpc.h functions documented on Apple Developer](https://developer.apple.com/documentation/xpc/xpc_services_xpc_h?language=objc) using the `From` trait. + +| Rust | XPC | +|----------------------------------------|----------------------------| +| i64 | _xpc_type_int64 | +| u64 | _xpc_type_uint64 | +| f64 | _xpc_type_double | +| bool | _xpc_bool_true/false | +| Into | _xpc_type_string | +| HashMap, Into> | _xpc_type_dictionary | +| Vec> | _xpc_type_array | +| std::os::unix::prelude::RawFd | _xpc_type_fd | +| (MachPortType::Send, mach_port_t) | _xpc_type_mach_send | +| (MachPortType::Recv, mach_port_t) | _xpc_type_mach_recv | + +Make XPC objects for anything with `From`. Make sure to use the correct type for file descriptors and Mach ports: +```rust +let mut message: HashMap<&str, XPCObject> = HashMap::new(); + +message.insert( + "domain-port", + XPCObject::from((MachPortType::Send, get_bootstrap_port() as mach_port_t)), +); +``` + +Go from an XPC object to value via the `TryXPCValue` trait. It checks your object's type via `xpc_get_type()` and yields a clear error if you're using the wrong type: +```rust +#[test] +fn deserialize_as_wrong_type() { + let an_i64: XPCObject = XPCObject::from(42 as i64); + let as_u64: Result = an_i64.xpc_value(); + assert_eq!( + as_u64.err().unwrap(), + XPCValueError("Cannot get int64 as uint64".to_string()) + ); +} +``` + +##### XPC Dictionaries + +Go from a `HashMap` to `xpc_object_t` with the `XPCObject` type: + +```rust +let mut message: HashMap<&str, XPCObject> = HashMap::new(); +message.insert("type", XPCObject::from(1 as u64)); +message.insert("handle", XPCObject::from(0 as u64)); +message.insert("subsystem", XPCObject::from(3 as u64)); +message.insert("routine", XPCObject::from(815 as u64)); +message.insert("legacy", XPCObject::from(true)); + +let xpc_object: XPCObject = message.into(); +``` + +Call `xpc_pipe_routine` and receive `Result`: + +```rust +let xpc_object: XPCObject = message.into(); + +match xpc_object.pipe_routine() { + Ok(xpc_object) => { /* do stuff and things */ }, + Err(XPCError::PipeError(err)) => { /* err is a string w/strerror(errno) */ } +} +``` + +The response is likely an XPC dictionary -- go back to a HashMap: + +```rust +let xpc_object: XPCObject = message.into(); +let response: Result = xpc_object + .pipe_routine() + .and_then(|r| r.try_into()); + +let XPCDictionary(hm) = response.unwrap(); +let whatever = hm.get("..."); +``` + +Response dictionaries can be nested, so `XPCDictionary` has a helper included for this scenario: + +```rust +let xpc_object: XPCObject = message.into(); + +// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System" +let response: Result = xpc_object + .pipe_routine() + .and_then(|r: XPCObject| r.try_into()); + .and_then(|d: XPCDictionary| d.get(&["service", "LimitLoadToSessionType"]) + .and_then(|lltst: XPCObject| lltst.xpc_value()); +``` + +Or, retrieve the `service` key (a child XPC Dictionary) from this response: + +```rust +let xpc_object: XPCObject = message.into(); + +// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System" +let response: Result = xpc_object + .pipe_routine() + .and_then(|r: XPCObject| r.try_into()); + .and_then(|d: XPCDictionary| d.get_as_dictionary(&["service"]); + +let XPCDictionary(hm) = response.unwrap(); +let whatever = hm.get("..."); +``` + +##### XPC Arrays + +An XPC array can be made from either `Vec` or `Vec>`: + +```rust +let xpc_array = XPCObject::from(vec![XPCObject::from("eins"), XPCObject::from("zwei"), XPCObject::from("polizei")]); + +let xpc_array = XPCObject::from(vec!["eins", "zwei", "polizei"]); +``` + +Go back to `Vec` using `xpc_value`: + +```rust +let rs_vec: Vec = xpc_array.xpc_value().unwrap(); +``` From 9d5fc0daf19522f93751108a2942cd4613d30d2d Mon Sep 17 00:00:00 2001 From: David Stancu Date: Thu, 27 May 2021 08:34:12 -0400 Subject: [PATCH 17/28] fix docs.rs build --- Cargo.lock | 2 +- xpc-sys/Cargo.toml | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ad1827..1c55a0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1385,7 +1385,7 @@ checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" [[package]] name = "xpc-sys" -version = "0.1.0-a1" +version = "0.1.0-a2" dependencies = [ "bindgen", "bitflags", diff --git a/xpc-sys/Cargo.toml b/xpc-sys/Cargo.toml index dce88eb..3c9254c 100644 --- a/xpc-sys/Cargo.toml +++ b/xpc-sys/Cargo.toml @@ -1,14 +1,17 @@ [package] name = "xpc-sys" description = "Conveniently call routines with wrappers for xpc_pipe_routine() and go from Rust types to XPC objects and back!" -version = "0.1.0-a1" +version = "0.1.0-a2" authors = ["David Stancu "] license = "MIT" edition = "2018" keywords = ["apple", "xpc", "xpc-dictionary"] categories = ["external-ffi-bindings", "os::macos-apis"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[package.metadata.docs.rs] +# This sets the default target to `x86_64-unknown-linux-gnu` +# and only builds that target +targets = ["x86_64-apple-darwin"] [dependencies] block = "0.1.6" From 78b63975dabe4625a77d3505387839a026148ed3 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Thu, 27 May 2021 23:10:37 -0400 Subject: [PATCH 18/28] move pager + clear acrobatics to own function move unix fifo acrobatics to some methods around a struct prep for procinfo --- Cargo.lock | 1 + launchk/src/launchd/message.rs | 12 +++ launchk/src/launchd/query.rs | 10 ++- launchk/src/tui/mod.rs | 1 + launchk/src/tui/omnibox/command.rs | 8 +- launchk/src/tui/pager.rs | 40 ++++++++++ launchk/src/tui/root.rs | 106 +++++---------------------- launchk/src/tui/service_list/view.rs | 30 ++++---- xpc-sys/Cargo.toml | 1 + xpc-sys/src/objects/mod.rs | 1 + xpc-sys/src/objects/xpc_type.rs | 4 +- xpc-sys/src/traits/mod.rs | 2 +- 12 files changed, 111 insertions(+), 105 deletions(-) create mode 100644 launchk/src/tui/pager.rs diff --git a/Cargo.lock b/Cargo.lock index 1c55a0a..56c4ac8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1391,6 +1391,7 @@ dependencies = [ "bitflags", "block", "lazy_static", + "libc", "log", "xcrun", ] diff --git a/launchk/src/launchd/message.rs b/launchk/src/launchd/message.rs index 4f7f84a..3773aac 100644 --- a/launchk/src/launchd/message.rs +++ b/launchk/src/launchd/message.rs @@ -34,27 +34,39 @@ lazy_static! { .entry("no-einprogress", true); + /// launchctl enable pub static ref ENABLE_NAMES: XPCDictionary = XPCDictionary::new() .with_domain_port() // .entry("handle", UID or ASID) .entry("routine", 808 as u64) .entry("subsystem", 3 as u64); + /// launchctl disable pub static ref DISABLE_NAMES: XPCDictionary = XPCDictionary::new() .with_domain_port() // .entry("handle", UID or ASID) .entry("routine", 809 as u64) .entry("subsystem", 3 as u64); + /// launchctl dumpstate + /// Requires a shmem xpc_object_t member, see XPCShmem for more details pub static ref DUMPSTATE: XPCDictionary = XPCDictionary::new() .entry("subsystem", 3 as u64) .entry("routine", 834 as u64) .entry("type", 1 as u64) .with_handle_or_default(None); + /// launchctl dumpjpcategory + /// Requires a FD".entry("fd", 1 as RawFd)" pub static ref DUMPJPCATEGORY: XPCDictionary = XPCDictionary::new() .entry("subsystem", 3 as u64) .entry("routine", 837 as u64) .entry("type", 1 as u64) .with_handle_or_default(None); + + /// launchctl procinfo + /// Requires a FD".entry("fd", 1 as RawFd)" + pub static ref PROCINFO: XPCDictionary = XPCDictionary::new() + .entry("subsystem", 2 as u64) + .entry("routine", 708); } diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index 2bd6740..3cec604 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -1,5 +1,5 @@ use crate::launchd::message::{ - DISABLE_NAMES, DUMPJPCATEGORY, DUMPSTATE, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, + DISABLE_NAMES, DUMPJPCATEGORY, DUMPSTATE, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, PROCINFO, }; use std::convert::TryFrom; use std::{ @@ -169,3 +169,11 @@ pub fn dumpjpcategory(fd: RawFd) -> Result { .entry("fd", fd) .pipe_routine_with_error_handling() } + +pub fn procinfo(pid: i64, fd: RawFd) -> Result { + XPCDictionary::new() + .extend(&PROCINFO) + .entry("fd", fd) + .entry("pid", pid) + .pipe_routine_with_error_handling() +} diff --git a/launchk/src/tui/mod.rs b/launchk/src/tui/mod.rs index 6872ea2..90e7245 100644 --- a/launchk/src/tui/mod.rs +++ b/launchk/src/tui/mod.rs @@ -4,3 +4,4 @@ pub mod root; mod service_list; mod sysinfo; mod table; +mod pager; diff --git a/launchk/src/tui/omnibox/command.rs b/launchk/src/tui/omnibox/command.rs index 0490831..01273db 100644 --- a/launchk/src/tui/omnibox/command.rs +++ b/launchk/src/tui/omnibox/command.rs @@ -29,6 +29,7 @@ pub enum OmniboxCommand { CSRInfo, DumpState, DumpJetsamPropertiesCategory, + ProcInfo, Help, Quit, } @@ -39,7 +40,7 @@ impl fmt::Display for OmniboxCommand { } } -pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 11] = [ +pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 12] = [ ( "load", "▶️ Load highlighted job", @@ -81,6 +82,11 @@ pub static OMNIBOX_COMMANDS: [(&str, &str, OmniboxCommand); 11] = [ "ℹ️ launchctl dumpjpcategory", OmniboxCommand::DumpJetsamPropertiesCategory, ), + ( + "procinfo", + "ℹ️ launchctl procinfo for highlighted process", + OmniboxCommand::ProcInfo, + ), ("help", "🤔 Show all commands", OmniboxCommand::Help), ("exit", "🚪 see ya!", OmniboxCommand::Quit), ]; diff --git a/launchk/src/tui/pager.rs b/launchk/src/tui/pager.rs new file mode 100644 index 0000000..c887a59 --- /dev/null +++ b/launchk/src/tui/pager.rs @@ -0,0 +1,40 @@ +use std::error::Error; +use std::process::{Command, Stdio}; +use std::io::Write; +use std::sync::mpsc::Sender; + +use cursive::Cursive; + +use super::root::CbSinkMessage; +lazy_static! { + static ref PAGER: &'static str = option_env!("PAGER").unwrap_or("less"); +} + +pub fn show_pager(cbsink: &Sender, buf: &[u8]) -> Result<(), String> { + let mut pager = Command::new(*PAGER) + .stdin(Stdio::piped()) + .spawn() + .map_err(|e| e.to_string())?; + + // Broken pipe unless scroll to end, do not throw an error + pager + .stdin + .take() + .expect("Must get pager stdin") + .write_all(buf) + .unwrap_or(()); + + let res = pager + .wait() + .map_err(|e| e.to_string())?; + + cbsink + .send(Box::new(Cursive::clear)) + .expect("Must clear"); + + if res.success() { + Ok(()) + } else { + Err(format!("{} exited {:?}", *PAGER, res)) + } +} \ No newline at end of file diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 8fcca64..43855e9 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -1,7 +1,7 @@ use std::collections::VecDeque; use std::fs::{File, remove_file}; use std::io::Read; -use std::os::unix::prelude::FromRawFd; +use std::os::unix::prelude::{FromRawFd, RawFd}; use std::ptr::null_mut; use std::sync::mpsc::{channel, Receiver, Sender}; @@ -29,6 +29,7 @@ use libc::O_WRONLY; use tokio::runtime::Handle; use xpc_sys::rs_strerror; +use xpc_sys::objects::unix_fifo::UnixFifo; use crate::tui::omnibox::command::OmniboxCommand; use crate::tui::omnibox::subscribed_view::{ @@ -42,6 +43,7 @@ use crate::{ tui::dialog::{show_csr_info, show_help}, }; use crate::{launchd::query::dumpstate, tui::dialog}; +use crate::tui::pager::show_pager; lazy_static! { static ref PAGER: &'static str = option_env!("PAGER").unwrap_or("less"); @@ -307,105 +309,35 @@ impl OmniboxSubscriber for RootLayout { log::info!("shmem response sz {}", size); - let mut pager = Command::new(*PAGER) - .stdin(Stdio::piped()) - .spawn() - .map_err(|e| OmniboxError::CommandError(e.to_string()))?; + show_pager(&self.cbsink_channel, unsafe { + &*slice_from_raw_parts(shmem.region as *mut u8, size) + }).map_err(|e| OmniboxError::CommandError(e))?; - let pager_stdin = pager.stdin.take(); - - self.runtime_handle.spawn(async move { - let raw_slice = slice_from_raw_parts(shmem.region as *mut u8, size); - - unsafe { - pager_stdin - .expect("Must have pager stdin") - .write_all(&*raw_slice) - .expect("Must write dumpstate") - }; - }); - - let res = pager - .wait() - .map_err(|e| OmniboxError::CommandError(e.to_string()))?; - - self.cbsink_channel - .send(Box::new(Cursive::clear)) - .expect("Must clear"); - - if !res.success() { - Err(OmniboxError::CommandError(format!( - "{} exited {:?}", - *PAGER, res - ))) - } else { - Ok(None) - } + Ok(None) } OmniboxEvent::Command(OmniboxCommand::DumpJetsamPropertiesCategory) => { - let fifo_name = unsafe { CStr::from_ptr(tmpnam(null_mut())) }; - let err = unsafe { mkfifo(fifo_name.as_ptr(), 0o777) }; + let fifo = UnixFifo::new(0o777) + .map_err(|e| OmniboxError::CommandError(e))?; - if err != 0 { - return Err(OmniboxError::CommandError(rs_strerror(err))); - } + let fifo_clone = fifo.clone(); // Spawn pipe reader let fd_read_thread = std::thread::spawn(move || { - let fifo_fd_read = unsafe { open(fifo_name.as_ptr(), O_RDONLY) }; - let mut file = unsafe { File::from_raw_fd(fifo_fd_read) }; - - let mut buf = String::new(); - file.read_to_string(&mut buf).expect("Must read string"); - - unsafe { libc::close(fifo_fd_read) }; - - buf + fifo_clone.block_and_read_bytes() }); - // Spawn pipe writer (XPC endpoint) - let fd_write_thread = std::thread::spawn(move || { - let fifo_fd_write = unsafe { open(fifo_name.as_ptr(), O_WRONLY | O_NONBLOCK) }; - dumpjpcategory(fifo_fd_write).expect("Must OK"); - unsafe { libc::close(fifo_fd_write) }; - }); + fifo.with_writer(|fd_write| { + dumpjpcategory(fd_write as RawFd) + }).map_err(|e| OmniboxError::CommandError(e.to_string()))?; // Join reader thread (and close fd) let jetsam_data = fd_read_thread.join().expect("Must read jetsam data"); - // Join writer thread (and close fd) - fd_write_thread.join().expect("Must finish dump query"); - - let mut pager = Command::new(*PAGER) - .stdin(Stdio::piped()) - .spawn() - .map_err(|e| OmniboxError::CommandError(e.to_string()))?; - - // Broken pipe unless scroll to end, do not throw an error - pager - .stdin - .take() - .expect("Must get pager stdin") - .write_all(jetsam_data.as_bytes()) - .unwrap_or(()); - - let res = pager - .wait() - .map_err(|e| OmniboxError::CommandError(e.to_string()))?; + fifo.delete(); - self.cbsink_channel - .send(Box::new(Cursive::clear)) - .expect("Must clear"); - - remove_file(fifo_name.to_string_lossy().to_string()).map_err(|e| OmniboxError::CommandError(e.to_string()))?; - - if !res.success() { - Err(OmniboxError::CommandError(format!( - "{} exited {:?}", - *PAGER, res - ))) - } else { - Ok(None) - } + show_pager(&self.cbsink_channel, &jetsam_data) + .map_err(|e| OmniboxError::CommandError(e))?; + + Ok(None) } OmniboxEvent::Command(OmniboxCommand::Help) => { self.cbsink_channel diff --git a/launchk/src/tui/service_list/view.rs b/launchk/src/tui/service_list/view.rs index 8796bee..1a68495 100644 --- a/launchk/src/tui/service_list/view.rs +++ b/launchk/src/tui/service_list/view.rs @@ -98,10 +98,10 @@ impl ServiceListView { return None; } - let entry_info = get_entry_status(label); + let status = get_entry_status(label); let is_loaded = running.contains(label); - let entry_job_type_filter = entry_info + let entry_job_type_filter = status .plist .as_ref() .map(|ec| ec.job_type_filter(is_loaded)) @@ -117,8 +117,8 @@ impl ServiceListView { } Some(ServiceListItem { + status, name: label.clone(), - status: entry_info, job_type_filter: entry_job_type_filter, }) }) @@ -186,12 +186,12 @@ impl ServiceListView { fn handle_command(&self, cmd: OmniboxCommand) -> OmniboxResult { match cmd { OmniboxCommand::Reload => { - let (ServiceListItem { name, .. }, ..) = self.with_active_item_plist()?; + let (ServiceListItem { name, status, .. }, ..) = self.with_active_item_plist()?; let LaunchdEntryStatus { limit_load_to_session_type, domain, .. - } = get_entry_status(&name); + } = status; match (limit_load_to_session_type, domain) { (_, DomainType::Unknown) | (SessionType::Unknown, _) => Ok(Some( @@ -223,8 +223,8 @@ impl ServiceListView { ))) } OmniboxCommand::UnloadRequest => { - let (ServiceListItem { name, .. }, ..) = self.with_active_item_plist()?; - let LaunchdEntryStatus { domain, .. } = get_entry_status(&name); + let (ServiceListItem { name, status, .. }, ..) = self.with_active_item_plist()?; + let LaunchdEntryStatus { domain, .. } = status; match domain { DomainType::Unknown => Ok(Some(OmniboxCommand::DomainSessionPrompt( @@ -244,8 +244,8 @@ impl ServiceListView { ))) } OmniboxCommand::DisableRequest => { - let (ServiceListItem { name, .. }, ..) = self.with_active_item_plist()?; - let LaunchdEntryStatus { domain, .. } = get_entry_status(&name); + let (ServiceListItem { name, status, .. }, ..) = self.with_active_item_plist()?; + let LaunchdEntryStatus { domain, .. } = status; match domain { DomainType::Unknown => Ok(Some(OmniboxCommand::DomainSessionPrompt( @@ -279,11 +279,8 @@ impl ServiceListView { .map_err(|e| OmniboxError::CommandError(e.to_string())) } OmniboxCommand::Unload(dt, _handle) => { - let (ServiceListItem { name, .. }, plist) = self.with_active_item_plist()?; - let LaunchdEntryStatus { - limit_load_to_session_type, - .. - } = get_entry_status(&name); + let (ServiceListItem { name, status, .. }, plist) = self.with_active_item_plist()?; + let LaunchdEntryStatus { limit_load_to_session_type, .. } = status; unload( name, @@ -307,6 +304,11 @@ impl ServiceListView { .map(|_| None) .map_err(|e| OmniboxError::CommandError(e.to_string())) } + // OmniboxCommand::ProcInfo => { + // let (ServiceListItem { name, status, .. }, _) = self.with_active_item_plist()?; + + + // } _ => Ok(None), } } diff --git a/xpc-sys/Cargo.toml b/xpc-sys/Cargo.toml index 3c9254c..350b13d 100644 --- a/xpc-sys/Cargo.toml +++ b/xpc-sys/Cargo.toml @@ -18,6 +18,7 @@ block = "0.1.6" lazy_static = "1.4.0" log = "0.4.14" bitflags = "1.2.1" +libc = "0.2.94" [build-dependencies] bindgen = "0.53.1" diff --git a/xpc-sys/src/objects/mod.rs b/xpc-sys/src/objects/mod.rs index 3507b04..68ad75d 100644 --- a/xpc-sys/src/objects/mod.rs +++ b/xpc-sys/src/objects/mod.rs @@ -12,3 +12,4 @@ pub mod xpc_pipe; pub mod xpc_error; pub mod xpc_shmem; +pub mod unix_fifo; diff --git a/xpc-sys/src/objects/xpc_type.rs b/xpc-sys/src/objects/xpc_type.rs index f20e527..9218f42 100644 --- a/xpc-sys/src/objects/xpc_type.rs +++ b/xpc-sys/src/objects/xpc_type.rs @@ -1,7 +1,7 @@ use crate::{ _xpc_type_array, _xpc_type_bool, _xpc_type_dictionary, _xpc_type_double, _xpc_type_int64, _xpc_type_mach_recv, _xpc_type_mach_send, _xpc_type_s, _xpc_type_string, _xpc_type_uint64, - xpc_get_type, xpc_object_t, xpc_type_get_name, xpc_type_t, + xpc_get_type, xpc_object_t, xpc_type_get_name, xpc_type_t, _xpc_type_fd, }; use crate::objects::xpc_error::XPCError; @@ -67,6 +67,8 @@ lazy_static! { unsafe { (&_xpc_type_mach_send as *const _xpc_type_s).into() }; pub static ref MachRecv: XPCType = unsafe { (&_xpc_type_mach_recv as *const _xpc_type_s).into() }; + pub static ref Fd: XPCType = + unsafe { (&_xpc_type_fd as *const _xpc_type_s).into() }; } /// Runtime type check for XPC object. I do not know if possible/advantageous to represent diff --git a/xpc-sys/src/traits/mod.rs b/xpc-sys/src/traits/mod.rs index b8c2333..b0c2347 100644 --- a/xpc-sys/src/traits/mod.rs +++ b/xpc-sys/src/traits/mod.rs @@ -1,2 +1,2 @@ pub mod xpc_pipeable; -pub mod xpc_value; +pub mod xpc_value; \ No newline at end of file From 771402cd9950f5c6efd657dd8560580c9e6fb0e7 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Fri, 28 May 2021 09:30:01 -0400 Subject: [PATCH 19/28] impl procinfo, but segfault --- launchk/src/tui/omnibox/state.rs | 2 +- launchk/src/tui/root.rs | 8 +++--- launchk/src/tui/service_list/view.rs | 37 +++++++++++++++++++++++++--- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/launchk/src/tui/omnibox/state.rs b/launchk/src/tui/omnibox/state.rs index ad9f3c9..3c982dd 100644 --- a/launchk/src/tui/omnibox/state.rs +++ b/launchk/src/tui/omnibox/state.rs @@ -45,7 +45,7 @@ impl OmniboxState { OMNIBOX_COMMANDS .iter() - .filter(|(c, _, _)| c.to_string().contains(command_filter)) + .filter(|(c, _, _)| c.to_string().starts_with(command_filter)) .next() .map(|s| s.clone()) } diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 43855e9..8659038 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -3,6 +3,7 @@ use std::fs::{File, remove_file}; use std::io::Read; use std::os::unix::prelude::{FromRawFd, RawFd}; use std::ptr::null_mut; +use std::sync::Arc; use std::sync::mpsc::{channel, Receiver, Sender}; use std::{ @@ -316,8 +317,10 @@ impl OmniboxSubscriber for RootLayout { Ok(None) } OmniboxEvent::Command(OmniboxCommand::DumpJetsamPropertiesCategory) => { - let fifo = UnixFifo::new(0o777) - .map_err(|e| OmniboxError::CommandError(e))?; + let fifo = Arc::new( + UnixFifo::new(0o777) + .map_err(|e| OmniboxError::CommandError(e))? + ); let fifo_clone = fifo.clone(); @@ -332,7 +335,6 @@ impl OmniboxSubscriber for RootLayout { // Join reader thread (and close fd) let jetsam_data = fd_read_thread.join().expect("Must read jetsam data"); - fifo.delete(); show_pager(&self.cbsink_channel, &jetsam_data) .map_err(|e| OmniboxError::CommandError(e))?; diff --git a/launchk/src/tui/service_list/view.rs b/launchk/src/tui/service_list/view.rs index 1a68495..d9cb874 100644 --- a/launchk/src/tui/service_list/view.rs +++ b/launchk/src/tui/service_list/view.rs @@ -12,10 +12,12 @@ use cursive::{Cursive, View, XY}; use tokio::runtime::Handle; use tokio::time::interval; +use xpc_sys::objects::unix_fifo::UnixFifo; use crate::launchd::enums::{DomainType, SessionType}; use crate::launchd::job_type_filter::JobTypeFilter; use crate::launchd::plist::{edit_and_replace, LABEL_TO_ENTRY_CONFIG}; +use crate::launchd::query::procinfo; use crate::launchd::query::{disable, enable, list_all, load, unload}; use crate::launchd::{ entry_status::get_entry_status, entry_status::LaunchdEntryStatus, plist::LaunchdPlist, @@ -25,6 +27,7 @@ use crate::tui::omnibox::command::OmniboxCommand; use crate::tui::omnibox::state::OmniboxState; use crate::tui::omnibox::subscribed_view::{OmniboxResult, OmniboxSubscriber}; use crate::tui::omnibox::view::{OmniboxError, OmniboxEvent, OmniboxMode}; +use crate::tui::pager::show_pager; use crate::tui::root::CbSinkMessage; use crate::tui::service_list::list_item::ServiceListItem; use crate::tui::table::table_list_view::TableListView; @@ -304,11 +307,39 @@ impl ServiceListView { .map(|_| None) .map_err(|e| OmniboxError::CommandError(e.to_string())) } - // OmniboxCommand::ProcInfo => { - // let (ServiceListItem { name, status, .. }, _) = self.with_active_item_plist()?; + OmniboxCommand::ProcInfo => { + let (ServiceListItem { name, status, .. }, _) = self.with_active_item_plist()?; + + if status.pid == 0 { + return Err(OmniboxError::CommandError( + format!("No PID available for {}", name) + )); + } + + let fifo = Arc::new( + UnixFifo::new(0o777) + .map_err(|e| OmniboxError::CommandError(e))? + ); + + let fifo_clone = fifo.clone(); + + // Spawn pipe reader + let fd_read_thread = std::thread::spawn(move || { + fifo_clone.block_and_read_bytes() + }); + fifo.with_writer(|fd_write| { + procinfo(status.pid, fd_write) + }).map_err(|e| OmniboxError::CommandError(e.to_string()))?; - // } + // Join reader thread (and close fd) + let procinfo_data = fd_read_thread.join().expect("Must read jetsam data"); + + show_pager(&self.cb_sink, &procinfo_data) + .map_err(|e| OmniboxError::CommandError(e))?; + + Ok(None) + } _ => Ok(None), } } From 1a1e9dd9c6265231369045e7d1e6528ae690d496 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Fri, 28 May 2021 10:18:26 -0400 Subject: [PATCH 20/28] fix procinfo w/u64 cast! also does not require root! --- README.md | 1 + launchk/src/launchd/message.rs | 2 +- launchk/src/launchd/query.rs | 3 +- launchk/src/tui/mod.rs | 2 +- launchk/src/tui/pager.rs | 13 ++---- launchk/src/tui/root.rs | 49 +++++++---------------- launchk/src/tui/service_list/view.rs | 34 ++++++++-------- xpc-sys/src/objects/mod.rs | 2 +- xpc-sys/src/objects/unix_fifo.rs | 60 ++++++++++++++++++++++++++++ xpc-sys/src/objects/xpc_type.rs | 9 ++--- xpc-sys/src/traits/mod.rs | 2 +- 11 files changed, 106 insertions(+), 71 deletions(-) create mode 100644 xpc-sys/src/objects/unix_fifo.rs diff --git a/README.md b/README.md index 2fc7344..b09cf7c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Should work on macOS 10.10+ according to the availability sec. [in the docs](htt - unload - dumpstate (opens in `$PAGER`) - dumpjpcategory (opens in `$PAGER`) +- procinfo (opens in `$PAGER`, does not require root!) - `:edit` -> Open plist in `$EDITOR`, defaulting to `vim`. Supports binary plists -> shown as XML for edit, then marshalled back into binary format on save. ### xpc-sys crate diff --git a/launchk/src/launchd/message.rs b/launchk/src/launchd/message.rs index 3773aac..85e84d5 100644 --- a/launchk/src/launchd/message.rs +++ b/launchk/src/launchd/message.rs @@ -68,5 +68,5 @@ lazy_static! { /// Requires a FD".entry("fd", 1 as RawFd)" pub static ref PROCINFO: XPCDictionary = XPCDictionary::new() .entry("subsystem", 2 as u64) - .entry("routine", 708); + .entry("routine", 708 as u64); } diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index 3cec604..caab8b4 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -1,5 +1,6 @@ use crate::launchd::message::{ - DISABLE_NAMES, DUMPJPCATEGORY, DUMPSTATE, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, UNLOAD_PATHS, PROCINFO, + DISABLE_NAMES, DUMPJPCATEGORY, DUMPSTATE, ENABLE_NAMES, LIST_SERVICES, LOAD_PATHS, PROCINFO, + UNLOAD_PATHS, }; use std::convert::TryFrom; use std::{ diff --git a/launchk/src/tui/mod.rs b/launchk/src/tui/mod.rs index 90e7245..9d78d19 100644 --- a/launchk/src/tui/mod.rs +++ b/launchk/src/tui/mod.rs @@ -1,7 +1,7 @@ mod dialog; mod omnibox; +mod pager; pub mod root; mod service_list; mod sysinfo; mod table; -mod pager; diff --git a/launchk/src/tui/pager.rs b/launchk/src/tui/pager.rs index c887a59..c5102bd 100644 --- a/launchk/src/tui/pager.rs +++ b/launchk/src/tui/pager.rs @@ -1,6 +1,5 @@ -use std::error::Error; -use std::process::{Command, Stdio}; use std::io::Write; +use std::process::{Command, Stdio}; use std::sync::mpsc::Sender; use cursive::Cursive; @@ -24,17 +23,13 @@ pub fn show_pager(cbsink: &Sender, buf: &[u8]) -> Result<(), Stri .write_all(buf) .unwrap_or(()); - let res = pager - .wait() - .map_err(|e| e.to_string())?; + let res = pager.wait().map_err(|e| e.to_string())?; - cbsink - .send(Box::new(Cursive::clear)) - .expect("Must clear"); + cbsink.send(Box::new(Cursive::clear)).expect("Must clear"); if res.success() { Ok(()) } else { Err(format!("{} exited {:?}", *PAGER, res)) } -} \ No newline at end of file +} diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 8659038..0000e9e 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -1,19 +1,12 @@ use std::collections::VecDeque; -use std::fs::{File, remove_file}; + use std::io::Read; -use std::os::unix::prelude::{FromRawFd, RawFd}; -use std::ptr::null_mut; -use std::sync::Arc; +use std::os::unix::prelude::RawFd; + use std::sync::mpsc::{channel, Receiver, Sender}; +use std::sync::Arc; -use std::{ - cell::RefCell, - ffi::{CStr, CString}, - io::Write, - os::raw::c_char, - process::{Command, Stdio}, - ptr::slice_from_raw_parts, -}; +use std::ptr::slice_from_raw_parts; use cursive::event::{Event, EventResult, Key}; use cursive::traits::{Resizable, Scrollable}; @@ -21,15 +14,8 @@ use cursive::view::{AnyView, ViewWrapper}; use cursive::views::{LinearLayout, NamedView, Panel}; use cursive::{Cursive, Vec2, View}; -use libc::mkfifo; -use libc::open; -use libc::tmpnam; -use libc::O_NONBLOCK; -use libc::O_RDONLY; -use libc::O_WRONLY; use tokio::runtime::Handle; -use xpc_sys::rs_strerror; use xpc_sys::objects::unix_fifo::UnixFifo; use crate::tui::omnibox::command::OmniboxCommand; @@ -37,6 +23,7 @@ use crate::tui::omnibox::subscribed_view::{ OmniboxResult, OmniboxSubscribedView, OmniboxSubscriber, Subscribable, }; use crate::tui::omnibox::view::{OmniboxError, OmniboxEvent, OmniboxView}; +use crate::tui::pager::show_pager; use crate::tui::service_list::view::ServiceListView; use crate::tui::sysinfo::SysInfo; use crate::{ @@ -44,7 +31,6 @@ use crate::{ tui::dialog::{show_csr_info, show_help}, }; use crate::{launchd::query::dumpstate, tui::dialog}; -use crate::tui::pager::show_pager; lazy_static! { static ref PAGER: &'static str = option_env!("PAGER").unwrap_or("less"); @@ -160,10 +146,7 @@ impl RootLayout { .and_then(|v| v.as_any_mut().downcast_mut::()) .expect("Must forward to ServiceList"); - let omnibox_events = [ - self_event, - target.on_omnibox(recv) - ]; + let omnibox_events = [self_event, target.on_omnibox(recv)]; for omnibox_event in &omnibox_events { match omnibox_event { @@ -312,26 +295,22 @@ impl OmniboxSubscriber for RootLayout { show_pager(&self.cbsink_channel, unsafe { &*slice_from_raw_parts(shmem.region as *mut u8, size) - }).map_err(|e| OmniboxError::CommandError(e))?; + }) + .map_err(|e| OmniboxError::CommandError(e))?; Ok(None) } OmniboxEvent::Command(OmniboxCommand::DumpJetsamPropertiesCategory) => { - let fifo = Arc::new( - UnixFifo::new(0o777) - .map_err(|e| OmniboxError::CommandError(e))? - ); + let fifo = + Arc::new(UnixFifo::new(0o777).map_err(|e| OmniboxError::CommandError(e))?); let fifo_clone = fifo.clone(); // Spawn pipe reader - let fd_read_thread = std::thread::spawn(move || { - fifo_clone.block_and_read_bytes() - }); + let fd_read_thread = std::thread::spawn(move || fifo_clone.block_and_read_bytes()); - fifo.with_writer(|fd_write| { - dumpjpcategory(fd_write as RawFd) - }).map_err(|e| OmniboxError::CommandError(e.to_string()))?; + fifo.with_writer(|fd_write| dumpjpcategory(fd_write as RawFd)) + .map_err(|e| OmniboxError::CommandError(e.to_string()))?; // Join reader thread (and close fd) let jetsam_data = fd_read_thread.join().expect("Must read jetsam data"); diff --git a/launchk/src/tui/service_list/view.rs b/launchk/src/tui/service_list/view.rs index d9cb874..de118d0 100644 --- a/launchk/src/tui/service_list/view.rs +++ b/launchk/src/tui/service_list/view.rs @@ -282,8 +282,12 @@ impl ServiceListView { .map_err(|e| OmniboxError::CommandError(e.to_string())) } OmniboxCommand::Unload(dt, _handle) => { - let (ServiceListItem { name, status, .. }, plist) = self.with_active_item_plist()?; - let LaunchdEntryStatus { limit_load_to_session_type, .. } = status; + let (ServiceListItem { name, status, .. }, plist) = + self.with_active_item_plist()?; + let LaunchdEntryStatus { + limit_load_to_session_type, + .. + } = status; unload( name, @@ -309,31 +313,27 @@ impl ServiceListView { } OmniboxCommand::ProcInfo => { let (ServiceListItem { name, status, .. }, _) = self.with_active_item_plist()?; - + if status.pid == 0 { - return Err(OmniboxError::CommandError( - format!("No PID available for {}", name) - )); + return Err(OmniboxError::CommandError(format!( + "No PID available for {}", + name + ))); } - let fifo = Arc::new( - UnixFifo::new(0o777) - .map_err(|e| OmniboxError::CommandError(e))? - ); + let fifo = + Arc::new(UnixFifo::new(0o777).map_err(|e| OmniboxError::CommandError(e))?); let fifo_clone = fifo.clone(); // Spawn pipe reader - let fd_read_thread = std::thread::spawn(move || { - fifo_clone.block_and_read_bytes() - }); + let fd_read_thread = std::thread::spawn(move || fifo_clone.block_and_read_bytes()); - fifo.with_writer(|fd_write| { - procinfo(status.pid, fd_write) - }).map_err(|e| OmniboxError::CommandError(e.to_string()))?; + fifo.with_writer(|fd_write| procinfo(status.pid, fd_write)) + .map_err(|e| OmniboxError::CommandError(e.to_string()))?; // Join reader thread (and close fd) - let procinfo_data = fd_read_thread.join().expect("Must read jetsam data"); + let procinfo_data = fd_read_thread.join().expect("Must read procinfo data"); show_pager(&self.cb_sink, &procinfo_data) .map_err(|e| OmniboxError::CommandError(e))?; diff --git a/xpc-sys/src/objects/mod.rs b/xpc-sys/src/objects/mod.rs index 68ad75d..6dbdc17 100644 --- a/xpc-sys/src/objects/mod.rs +++ b/xpc-sys/src/objects/mod.rs @@ -10,6 +10,6 @@ pub mod xpc_type; /// xpc_pipe_t pub mod xpc_pipe; +pub mod unix_fifo; pub mod xpc_error; pub mod xpc_shmem; -pub mod unix_fifo; diff --git a/xpc-sys/src/objects/unix_fifo.rs b/xpc-sys/src/objects/unix_fifo.rs new file mode 100644 index 0000000..02c7f57 --- /dev/null +++ b/xpc-sys/src/objects/unix_fifo.rs @@ -0,0 +1,60 @@ +use libc::{mkfifo, mode_t, open, tmpnam, O_RDONLY, O_WRONLY}; +use std::os::unix::prelude::RawFd; +use std::{ + ffi::{CStr, CString}, + fs::{remove_file, File}, + io::Read, + os::unix::prelude::FromRawFd, + ptr::null_mut, +}; + +use crate::rs_strerror; + +/// A simple wrapper around a UNIX FIFO +pub struct UnixFifo(pub CString); + +impl UnixFifo { + /// Create a new FIFO, make sure mode_t is 0oXXX! + pub fn new(mode: mode_t) -> Result { + let fifo_name = unsafe { CStr::from_ptr(tmpnam(null_mut())) }; + let err = unsafe { mkfifo(fifo_name.as_ptr(), mode) }; + + if err == 0 { + Ok(UnixFifo(fifo_name.to_owned())) + } else { + Err(rs_strerror(err)) + } + } + + /// Open the FIFO as O_RDONLY, read until EOF, clean up fd before returning the buffer. + pub fn block_and_read_bytes(&self) -> Vec { + let Self(fifo_name) = self; + + let fifo_fd_read = unsafe { open(fifo_name.as_ptr(), O_RDONLY) }; + let mut file = unsafe { File::from_raw_fd(fifo_fd_read) }; + + let mut buf: Vec = Vec::new(); + file.read_to_end(&mut buf).expect("Must read bytes"); + + unsafe { libc::close(fifo_fd_read) }; + + buf + } + + /// Open O_WRONLY, pass to fn and clean up before returning. + pub fn with_writer(&self, f: impl Fn(RawFd) -> T) -> T { + let Self(fifo_name) = self; + let fifo_fd_write = unsafe { open(fifo_name.as_ptr(), O_WRONLY) }; + let response = f(fifo_fd_write); + unsafe { libc::close(fifo_fd_write) }; + response + } +} + +impl Drop for UnixFifo { + fn drop(&mut self) { + let Self(fifo_name) = self; + + remove_file(&fifo_name.to_string_lossy().to_string()).expect("Must tear down FIFO"); + } +} diff --git a/xpc-sys/src/objects/xpc_type.rs b/xpc-sys/src/objects/xpc_type.rs index 9218f42..2ac6bbc 100644 --- a/xpc-sys/src/objects/xpc_type.rs +++ b/xpc-sys/src/objects/xpc_type.rs @@ -1,7 +1,7 @@ use crate::{ - _xpc_type_array, _xpc_type_bool, _xpc_type_dictionary, _xpc_type_double, _xpc_type_int64, - _xpc_type_mach_recv, _xpc_type_mach_send, _xpc_type_s, _xpc_type_string, _xpc_type_uint64, - xpc_get_type, xpc_object_t, xpc_type_get_name, xpc_type_t, _xpc_type_fd, + _xpc_type_array, _xpc_type_bool, _xpc_type_dictionary, _xpc_type_double, _xpc_type_fd, + _xpc_type_int64, _xpc_type_mach_recv, _xpc_type_mach_send, _xpc_type_s, _xpc_type_string, + _xpc_type_uint64, xpc_get_type, xpc_object_t, xpc_type_get_name, xpc_type_t, }; use crate::objects::xpc_error::XPCError; @@ -67,8 +67,7 @@ lazy_static! { unsafe { (&_xpc_type_mach_send as *const _xpc_type_s).into() }; pub static ref MachRecv: XPCType = unsafe { (&_xpc_type_mach_recv as *const _xpc_type_s).into() }; - pub static ref Fd: XPCType = - unsafe { (&_xpc_type_fd as *const _xpc_type_s).into() }; + pub static ref Fd: XPCType = unsafe { (&_xpc_type_fd as *const _xpc_type_s).into() }; } /// Runtime type check for XPC object. I do not know if possible/advantageous to represent diff --git a/xpc-sys/src/traits/mod.rs b/xpc-sys/src/traits/mod.rs index b0c2347..b8c2333 100644 --- a/xpc-sys/src/traits/mod.rs +++ b/xpc-sys/src/traits/mod.rs @@ -1,2 +1,2 @@ pub mod xpc_pipeable; -pub mod xpc_value; \ No newline at end of file +pub mod xpc_value; From 21cf9f81ffc4f31c2227d0203219156be10c5dc0 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sat, 29 May 2021 13:49:05 -0400 Subject: [PATCH 21/28] [1/2] add columnsizer make column sizing less crappy --- launchk/src/tui/dialog.rs | 5 +- launchk/src/tui/omnibox/view.rs | 9 ++- launchk/src/tui/pager.rs | 1 + launchk/src/tui/service_list/view.rs | 4 +- launchk/src/tui/table/column_sizer.rs | 72 ++++++++++++++++++++++++ launchk/src/tui/table/mod.rs | 1 + launchk/src/tui/table/table_list_view.rs | 27 ++++----- 7 files changed, 97 insertions(+), 22 deletions(-) create mode 100644 launchk/src/tui/table/column_sizer.rs diff --git a/launchk/src/tui/dialog.rs b/launchk/src/tui/dialog.rs index 88ab4f0..e3ea913 100644 --- a/launchk/src/tui/dialog.rs +++ b/launchk/src/tui/dialog.rs @@ -17,8 +17,7 @@ use crate::{ }; use xpc_sys::csr::{csr_check, CsrConfig}; -/// The XPC error key sometimes contains information that is not necessarily a failure, -/// so let's just call it "Notice" until we figure out what to do next? +/// XPC "error" key can be present with no failure..."notice"? pub fn show_error(err: String) -> CbSinkMessage { let cl = |siv: &mut Cursive| { let dialog = Dialog::around(TextView::new(err)) @@ -58,6 +57,8 @@ pub fn show_prompt( Box::new(cl) } +/// Don't know how to get this info when job is not running, +/// so we can ask user and suggest a default (domain 7, aqua) pub fn domain_session_prompt>( label: S, domain_only: bool, diff --git a/launchk/src/tui/omnibox/view.rs b/launchk/src/tui/omnibox/view.rs index 60545ee..332675f 100644 --- a/launchk/src/tui/omnibox/view.rs +++ b/launchk/src/tui/omnibox/view.rs @@ -113,6 +113,7 @@ impl OmniboxView { ) } + /// Commands fn handle_active(event: &Event, state: &OmniboxState) -> Option { let OmniboxState { mode, @@ -128,7 +129,8 @@ impl OmniboxView { .filter(|(cmd, _, _)| *cmd == *command_filter) .map(|(_, _, oc)| oc.clone()); - let (lf_char, cf_char) = match (event, mode) { + // Avoid extra clauses below, use same options for string filters + let (lf_char_update, cf_char_update) = match (event, mode) { (Event::Char(c), OmniboxMode::LabelFilter) => { (Some(format!("{}{}", label_filter, c)), None) } @@ -139,6 +141,7 @@ impl OmniboxView { }; match (event, mode) { + // Toggle back to query from bitmask filters (Event::Char(':'), OmniboxMode::JobTypeFilter) => { Some(state.with_new(Some(OmniboxMode::CommandFilter), None, None, None)) } @@ -149,7 +152,7 @@ impl OmniboxView { // User -> string filters (Event::Char(_), OmniboxMode::LabelFilter) | (Event::Char(_), OmniboxMode::CommandFilter) => { - Some(state.with_new(None, lf_char, cf_char, None)) + Some(state.with_new(None, lf_char_update, cf_char_update, None)) } (Event::Key(Key::Backspace), OmniboxMode::LabelFilter) if !label_filter.is_empty() => { let mut lf = label_filter.clone(); @@ -184,6 +187,7 @@ impl OmniboxView { } } + /// Toggle bitmask on key fn handle_job_type_filter(event: &Event, state: &OmniboxState) -> Option { let mut jtf = state.job_type_filter.clone(); @@ -200,6 +204,7 @@ impl OmniboxView { Some(state.with_new(Some(OmniboxMode::JobTypeFilter), None, None, Some(jtf))) } + /// Leave idle state fn handle_idle(event: &Event, state: &OmniboxState) -> Option { match event { Event::Char('/') => Some(state.with_new( diff --git a/launchk/src/tui/pager.rs b/launchk/src/tui/pager.rs index c5102bd..e82182c 100644 --- a/launchk/src/tui/pager.rs +++ b/launchk/src/tui/pager.rs @@ -9,6 +9,7 @@ lazy_static! { static ref PAGER: &'static str = option_env!("PAGER").unwrap_or("less"); } +/// Show $PAGER (or less), write buf, and clear Cursive after exiting pub fn show_pager(cbsink: &Sender, buf: &[u8]) -> Result<(), String> { let mut pager = Command::new(*PAGER) .stdin(Stdio::piped()) diff --git a/launchk/src/tui/service_list/view.rs b/launchk/src/tui/service_list/view.rs index de118d0..2942e03 100644 --- a/launchk/src/tui/service_list/view.rs +++ b/launchk/src/tui/service_list/view.rs @@ -47,7 +47,7 @@ async fn poll_running_jobs(svcs: Arc>>, cb_sink: Sender, + /// TODO; wtf do I mean by padding + pub padding: Cell, + /// Column index -> width + pub user_sizes: HashMap, + + num_dynamic_columns: usize, + /// Sum of user size widths + user_sizes_total: usize, +} + +impl ColumnSizer { + /// Create a new ColumnSizer + pub fn new( + columns: I + ) -> Arc where + I: IntoIterator)> + Clone, + K: AsRef + { + let num_columns = columns.clone().into_iter().count(); + let column_iter = columns.into_iter(); + + let user_sizes: HashMap = column_iter + .zip(0..num_columns) + .filter_map(|((_, user_len), i)| user_len.map(|ul| (i, ul))) + .collect(); + + let user_sizes_total = user_sizes.values().sum(); + let num_dynamic_columns = num_columns - user_sizes.len(); + + let cs = Self { + num_dynamic_columns, + user_sizes, + user_sizes_total, + dynamic_column_size: Default::default(), + padding: Default::default(), + }; + + Arc::new(cs) + } + + /// Get the width for a column by index + pub fn width_for_index(&self, i: usize) -> usize { + self + .user_sizes + .get(&i) + .map(Clone::clone) + .unwrap_or(self.dynamic_column_size.get()) + } + + + /// Call when x changes to recompute dynamic_column_size and padding + pub fn update_x(&self, x: usize) { + let mut remaining = x - self.user_sizes_total; + + let mut dcs = remaining / self.num_dynamic_columns; + if dcs > 35 { + dcs = 35; + } + + remaining = remaining - (self.num_dynamic_columns * dcs); + + self.dynamic_column_size.set(dcs); + self.padding.set(remaining / (self.num_dynamic_columns + self.user_sizes.len())); + } +} \ No newline at end of file diff --git a/launchk/src/tui/table/mod.rs b/launchk/src/tui/table/mod.rs index a9a8371..a409925 100644 --- a/launchk/src/tui/table/mod.rs +++ b/launchk/src/tui/table/mod.rs @@ -1,2 +1,3 @@ mod table_headers; +mod column_sizer; pub mod table_list_view; diff --git a/launchk/src/tui/table/table_list_view.rs b/launchk/src/tui/table/table_list_view.rs index 5c262d1..baa3a6a 100644 --- a/launchk/src/tui/table/table_list_view.rs +++ b/launchk/src/tui/table/table_list_view.rs @@ -12,7 +12,6 @@ use cursive::views::{LinearLayout, ResizedView, ScrollView, SelectView}; use cursive::{Vec2, View, XY}; use crate::tui::table::table_headers::TableHeaders; - pub trait TableListItem { fn as_row(&self) -> Vec; } @@ -36,19 +35,15 @@ impl TableListView { fn build_user_col_sizes( columns: &Vec<(&str, Option)>, ) -> Arc<(HashMap, usize)> { - let mut user_col_sizes: HashMap = HashMap::new(); - let mut user_col_size_total: usize = 0; - - for (i, (_, sz)) in columns.iter().enumerate() { - if sz.is_none() { - continue; - } - let sz = sz.unwrap(); - user_col_size_total += sz; - user_col_sizes.insert(i, sz); - } + let user_col_sizes: HashMap = columns + .iter() + .zip(0..columns.len()) + .filter_map(|((_, user_len), i)| user_len.map(|ul| (i, ul))) + .collect(); - Arc::new((user_col_sizes, user_col_size_total)) + let total = user_col_sizes.values().sum(); + + Arc::new((user_col_sizes, total)) } pub fn new(columns: Vec<(&str, Option)>) -> Self { @@ -88,8 +83,6 @@ impl TableListView { where I: IntoIterator, { - // self.compute_sizes(); - let (dyn_max, padding) = *self.dynamic_cols_sz.borrow(); let (user_col_sizes, _) = &*self.user_col_sizes; @@ -116,6 +109,7 @@ impl TableListView { }; truncated.truncate(field_width - 1); + // truncated.truncate(pad - 1); format!("{:pad$}", truncated, pad = pad) }) .collect(); @@ -138,8 +132,8 @@ impl TableListView { /// "Responsive" fn compute_sizes(&mut self) { + // Total is sum of all defined sizes let (user_col_sizes, user_col_sizes_total) = &*self.user_col_sizes; - let num_dynamic = self.num_columns - user_col_sizes.len(); // All sizes are static @@ -147,6 +141,7 @@ impl TableListView { return; } + // Amount remaining for dynamically sized columns to split let remaining = self.last_layout_size.borrow().x - user_col_sizes_total; let mut per_dynamic_col = remaining / num_dynamic; From eda5b8485792d24d576181a731224264996118e6 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sat, 29 May 2021 14:15:39 -0400 Subject: [PATCH 22/28] [2/2] hook up columnsizer, delete a ton of cruft! --- launchk/src/tui/omnibox/view.rs | 2 +- launchk/src/tui/service_list/view.rs | 5 +- launchk/src/tui/table/column_sizer.rs | 119 ++++++++++++---------- launchk/src/tui/table/mod.rs | 2 +- launchk/src/tui/table/table_headers.rs | 42 ++------ launchk/src/tui/table/table_list_view.rs | 124 +++++------------------ 6 files changed, 106 insertions(+), 188 deletions(-) diff --git a/launchk/src/tui/omnibox/view.rs b/launchk/src/tui/omnibox/view.rs index 332675f..62112ee 100644 --- a/launchk/src/tui/omnibox/view.rs +++ b/launchk/src/tui/omnibox/view.rs @@ -113,7 +113,7 @@ impl OmniboxView { ) } - /// Commands + /// Commands fn handle_active(event: &Event, state: &OmniboxState) -> Option { let OmniboxState { mode, diff --git a/launchk/src/tui/service_list/view.rs b/launchk/src/tui/service_list/view.rs index 2942e03..e4c4624 100644 --- a/launchk/src/tui/service_list/view.rs +++ b/launchk/src/tui/service_list/view.rs @@ -315,10 +315,7 @@ impl ServiceListView { let (ServiceListItem { name, status, .. }, _) = self.with_active_item_plist()?; if status.pid == 0 { - return Err(OmniboxError::CommandError(format!( - "No PID for {}", - name - ))); + return Err(OmniboxError::CommandError(format!("No PID for {}", name))); } let fifo = diff --git a/launchk/src/tui/table/column_sizer.rs b/launchk/src/tui/table/column_sizer.rs index fde85e9..21a7c53 100644 --- a/launchk/src/tui/table/column_sizer.rs +++ b/launchk/src/tui/table/column_sizer.rs @@ -2,71 +2,82 @@ use std::{cell::Cell, collections::HashMap, sync::Arc}; /// Width oriented column sizing utility pub struct ColumnSizer { - /// Non user defined columns are an even split of space remaining from - /// x - user_sizes_total - pub dynamic_column_size: Cell, - /// TODO; wtf do I mean by padding - pub padding: Cell, - /// Column index -> width - pub user_sizes: HashMap, + /// Non user defined columns are an even split of space remaining from + /// x - user_sizes_total + pub dynamic_column_size: Cell, + /// TODO; wtf do I mean by padding + pub padding: Cell, + /// Column index -> width + pub user_sizes: HashMap, + pub num_columns: usize, - num_dynamic_columns: usize, - /// Sum of user size widths - user_sizes_total: usize, + num_dynamic_columns: usize, + /// Sum of user size widths + user_sizes_total: usize, } impl ColumnSizer { - /// Create a new ColumnSizer - pub fn new( - columns: I - ) -> Arc where - I: IntoIterator)> + Clone, - K: AsRef - { - let num_columns = columns.clone().into_iter().count(); - let column_iter = columns.into_iter(); + /// Create a new ColumnSizer + pub fn new(columns: I) -> Arc + where + I: IntoIterator)> + Clone, + K: AsRef, + { + let num_columns = columns.clone().into_iter().count(); + let column_iter = columns.into_iter(); - let user_sizes: HashMap = column_iter - .zip(0..num_columns) - .filter_map(|((_, user_len), i)| user_len.map(|ul| (i, ul))) - .collect(); + let user_sizes: HashMap = column_iter + .zip(0..num_columns) + .filter_map(|((_, user_len), i)| user_len.map(|ul| (i, ul))) + .collect(); - let user_sizes_total = user_sizes.values().sum(); - let num_dynamic_columns = num_columns - user_sizes.len(); + let user_sizes_total = user_sizes.values().sum(); + let num_dynamic_columns = num_columns - user_sizes.len(); - let cs = Self { - num_dynamic_columns, - user_sizes, - user_sizes_total, - dynamic_column_size: Default::default(), - padding: Default::default(), - }; + let cs = Self { + num_dynamic_columns, + num_columns, + user_sizes, + user_sizes_total, + dynamic_column_size: Default::default(), + padding: Default::default(), + }; - Arc::new(cs) - } - - /// Get the width for a column by index - pub fn width_for_index(&self, i: usize) -> usize { - self - .user_sizes - .get(&i) - .map(Clone::clone) - .unwrap_or(self.dynamic_column_size.get()) - } + Arc::new(cs) + } + /// Get the width for a column by index + pub fn width_for_index(&self, i: usize) -> usize { + let size = self + .user_sizes + .get(&i) + .map(Clone::clone) + .unwrap_or(self.dynamic_column_size.get()); - /// Call when x changes to recompute dynamic_column_size and padding - pub fn update_x(&self, x: usize) { - let mut remaining = x - self.user_sizes_total; + // I have 'sized' my user defined columns around how much + // space I need to just display the font, and the rest by + // blindly dividing space, only apply padding to UDCs - let mut dcs = remaining / self.num_dynamic_columns; - if dcs > 35 { - dcs = 35; + if self.user_sizes.contains_key(&i) { + size + self.padding.get() + } else { + size + } } - remaining = remaining - (self.num_dynamic_columns * dcs); + /// Call when x changes to recompute dynamic_column_size and padding + pub fn update_x(&self, x: usize) { + let mut remaining = x - self.user_sizes_total; + + let mut dcs = remaining / self.num_dynamic_columns; + if dcs > 35 { + dcs = 35; + } - self.dynamic_column_size.set(dcs); - self.padding.set(remaining / (self.num_dynamic_columns + self.user_sizes.len())); - } -} \ No newline at end of file + remaining = remaining - (self.num_dynamic_columns * dcs); + + self.dynamic_column_size.set(dcs); + self.padding + .set(remaining / (self.num_dynamic_columns + self.user_sizes.len())); + } +} diff --git a/launchk/src/tui/table/mod.rs b/launchk/src/tui/table/mod.rs index a409925..4968171 100644 --- a/launchk/src/tui/table/mod.rs +++ b/launchk/src/tui/table/mod.rs @@ -1,3 +1,3 @@ -mod table_headers; mod column_sizer; +mod table_headers; pub mod table_list_view; diff --git a/launchk/src/tui/table/table_headers.rs b/launchk/src/tui/table/table_headers.rs index f298722..02d3b96 100644 --- a/launchk/src/tui/table/table_headers.rs +++ b/launchk/src/tui/table/table_headers.rs @@ -1,62 +1,42 @@ -use std::cell::RefCell; -use std::collections::HashMap; -use std::sync::mpsc::Receiver; use std::sync::Arc; use cursive::theme::{BaseColor, Color, Effect, Style}; use cursive::{Printer, View, XY}; +use super::column_sizer::ColumnSizer; + +/// Draw column headers from their names + a column sizer pub struct TableHeaders { columns: Vec, - user_col_sizes: Arc<(HashMap, usize)>, - dynamic_col_sz_rx: Receiver<(usize, usize)>, - dynamic_cols_sz: RefCell<(usize, usize)>, + column_sizer: Arc, } impl TableHeaders { pub fn new>( columns: impl Iterator, - user_col_sizes: Arc<(HashMap, usize)>, - dynamic_col_sz_rx: Receiver<(usize, usize)>, + column_sizer: Arc, ) -> Self { Self { columns: columns.map(|f| f.into()).collect(), - dynamic_cols_sz: RefCell::new((0, 0)), - user_col_sizes, - dynamic_col_sz_rx, + column_sizer, } } } impl View for TableHeaders { fn draw(&self, printer: &Printer<'_, '_>) { - if let Ok(dcs) = self.dynamic_col_sz_rx.try_recv() { - self.dynamic_cols_sz.replace(dcs); - } - let bold = Style::from(Color::Dark(BaseColor::Blue)).combine(Effect::Bold); - let (dyn_max, padding) = *self.dynamic_cols_sz.borrow(); - if dyn_max < 1 { - return; - } - - let (ucs, _) = &*self.user_col_sizes; - let headers: String = self .columns .iter() .enumerate() .map(|(i, column)| { - let width = ucs.get(&i).map(|s| s.clone()).unwrap_or(dyn_max); - - let pad = if ucs.contains_key(&i) { - width + padding - } else { - width - }; - - format!("{:pad$}", column, pad = pad) + format!( + "{:with_padding$}", + column, + with_padding = self.column_sizer.width_for_index(i) + ) }) .collect::>() .join(""); diff --git a/launchk/src/tui/table/table_list_view.rs b/launchk/src/tui/table/table_list_view.rs index baa3a6a..cf901c5 100644 --- a/launchk/src/tui/table/table_list_view.rs +++ b/launchk/src/tui/table/table_list_view.rs @@ -1,65 +1,47 @@ -use std::cell::RefCell; -use std::collections::HashMap; use std::marker::PhantomData; use std::rc::Rc; -use std::sync::mpsc::{channel, Receiver, Sender}; + use std::sync::Arc; use cursive::event::{Event, EventResult}; use cursive::traits::{Resizable, Scrollable}; use cursive::view::ViewWrapper; use cursive::views::{LinearLayout, ResizedView, ScrollView, SelectView}; -use cursive::{Vec2, View, XY}; +use cursive::{Vec2, View}; use crate::tui::table::table_headers::TableHeaders; + +use super::column_sizer::ColumnSizer; pub trait TableListItem { fn as_row(&self) -> Vec; } +/// A "table" implemented on top of SelectView where we +/// divvy up x into columns pub struct TableListView { + column_sizer: Arc, linear_layout: LinearLayout, - last_layout_size: RefCell>, - num_columns: usize, - // User override cols, total size - user_col_sizes: Arc<(HashMap, usize)>, - // Precompute dynamic sizes once on replace - // (Max dynamic col size, Padding between columns) - dynamic_cols_sz: RefCell<(usize, usize)>, - // Share it - dynamic_cols_sz_tx: Sender<(usize, usize)>, - // Don't swallow type, presumably needed later + // LinearLayout swallows T from , but we still need it inner: PhantomData, } impl TableListView { - fn build_user_col_sizes( - columns: &Vec<(&str, Option)>, - ) -> Arc<(HashMap, usize)> { - let user_col_sizes: HashMap = columns - .iter() - .zip(0..columns.len()) - .filter_map(|((_, user_len), i)| user_len.map(|ul| (i, ul))) - .collect(); - - let total = user_col_sizes.values().sum(); - - Arc::new((user_col_sizes, total)) - } - - pub fn new(columns: Vec<(&str, Option)>) -> Self { - let (dynamic_cols_sz_tx, rx): (Sender<(usize, usize)>, Receiver<(usize, usize)>) = - channel(); - let user_col_sizes = Self::build_user_col_sizes(&columns); + pub fn new(columns: I) -> TableListView + where + I: IntoIterator)> + Clone, + K: AsRef, + { + let column_names = columns + .clone() + .into_iter() + .map(|(n, _)| n.as_ref().to_string()); + let column_sizer = ColumnSizer::new(columns); let mut linear_layout = LinearLayout::vertical(); linear_layout.add_child( - TableHeaders::new( - columns.iter().map(|(n, _)| n.to_string()), - user_col_sizes.clone(), - rx, - ) - .full_width() - .max_height(1), + TableHeaders::new(column_names, column_sizer.clone()) + .full_width() + .max_height(1), ); linear_layout.add_child( SelectView::::new() @@ -67,14 +49,9 @@ impl TableListView { .full_height() .scrollable(), ); - Self { linear_layout, - user_col_sizes, - dynamic_cols_sz_tx, - dynamic_cols_sz: RefCell::new((0, 0)), - last_layout_size: RefCell::new(XY::new(0, 0)), - num_columns: *&columns.len(), + column_sizer, inner: PhantomData::default(), } } @@ -83,34 +60,19 @@ impl TableListView { where I: IntoIterator, { - let (dyn_max, padding) = *self.dynamic_cols_sz.borrow(); - let (user_col_sizes, _) = &*self.user_col_sizes; - let rows: Vec<(String, T)> = items .into_iter() .map(|item: T| { let presented: Vec = item .as_row() .iter() - .take(self.num_columns) + .take(self.column_sizer.num_columns) .enumerate() .map(|(i, field)| { + let wfi = self.column_sizer.width_for_index(i); let mut truncated = field.clone(); - let field_width = user_col_sizes - .get(&i) - .clone() - .map(|s| s.clone()) - .unwrap_or(dyn_max.clone()); - - let pad = if user_col_sizes.contains_key(&i) { - field_width + padding - } else { - field_width - }; - - truncated.truncate(field_width - 1); - // truncated.truncate(pad - 1); - format!("{:pad$}", truncated, pad = pad) + truncated.truncate(wfi - 1); + format!("{:with_padding$}", truncated, with_padding = wfi) }) .collect(); @@ -130,37 +92,6 @@ impl TableListView { self.get_selectview().selection() } - /// "Responsive" - fn compute_sizes(&mut self) { - // Total is sum of all defined sizes - let (user_col_sizes, user_col_sizes_total) = &*self.user_col_sizes; - let num_dynamic = self.num_columns - user_col_sizes.len(); - - // All sizes are static - if num_dynamic < 1 { - return; - } - - // Amount remaining for dynamically sized columns to split - let remaining = self.last_layout_size.borrow().x - user_col_sizes_total; - let mut per_dynamic_col = remaining / num_dynamic; - - // Max col sz = 35 - if per_dynamic_col > 35 { - per_dynamic_col = 35; - } - - // After user col reservations, remove dyn cols, and distribute that space btw - // the user provided column sizes. - let remain_padding = - (remaining - (per_dynamic_col * num_dynamic)) / (self.num_columns - num_dynamic); - self.dynamic_cols_sz - .replace((per_dynamic_col, remain_padding)); - self.dynamic_cols_sz_tx - .send((per_dynamic_col, remain_padding)) - .expect("Must update dynamic cols"); - } - /// Get the index of the SelectView and unwrap it out of /// ScrollView>>> fn get_mut_selectview(&mut self) -> &mut SelectView { @@ -203,8 +134,7 @@ impl ViewWrapper for TableListView { } fn wrap_layout(&mut self, size: Vec2) { - self.last_layout_size.replace(size); + self.column_sizer.update_x(size.x); self.linear_layout.layout(size); - self.compute_sizes(); } } From dcc8ddbbbc34b80530449bdbc95b8c203efcdbde Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sat, 29 May 2021 15:31:30 -0400 Subject: [PATCH 23/28] xpc shmem: remove useless arc xpc pipe: unused / dead code document shmem in readme --- README.md | 162 +------------------------- launchk/src/launchd/message.rs | 10 +- launchk/src/launchd/plist.rs | 6 +- launchk/src/launchd/query.rs | 16 +-- launchk/src/launchd/query_builder.rs | 11 +- launchk/src/tui/root.rs | 10 +- xpc-sys/README.md | 141 ++++++++++++++++------ xpc-sys/src/objects/mod.rs | 3 - xpc-sys/src/objects/xpc_dictionary.rs | 2 +- xpc-sys/src/objects/xpc_object.rs | 12 +- xpc-sys/src/objects/xpc_pipe.rs | 8 -- xpc-sys/src/objects/xpc_shmem.rs | 25 +--- xpc-sys/src/objects/xpc_type.rs | 5 +- 13 files changed, 153 insertions(+), 258 deletions(-) delete mode 100644 xpc-sys/src/objects/xpc_pipe.rs diff --git a/README.md b/README.md index b09cf7c..a8bca5e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Rust](https://github.com/mach-kernel/launchk/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/mach-kernel/launchk/actions/workflows/rust.yml) -A WIP [Cursive](https://github.com/gyscos/cursive) TUI that makes XPC queries & helps manage launchd jobs. +A [Cursive](https://github.com/gyscos/cursive) TUI that makes XPC queries & helps manage launchd jobs. Should work on macOS 10.10+ according to the availability sec. [in the docs](https://developer.apple.com/documentation/xpc?language=objc). @@ -23,176 +23,20 @@ Should work on macOS 10.10+ according to the availability sec. [in the docs](htt - procinfo (opens in `$PAGER`, does not require root!) - `:edit` -> Open plist in `$EDITOR`, defaulting to `vim`. Supports binary plists -> shown as XML for edit, then marshalled back into binary format on save. -### xpc-sys crate +#### xpc-sys -There is some "convenience glue" for dealing with XPC objects. Eventually, this will be broken out into its own crate. Some tests exist for not breaking data to/from FFI. - -##### Object lifecycle - -XPCObject wraps `xpc_object_t` in an `Arc`. `Drop` will invoke `xpc_release()` on objects being dropped with no other [strong refs](https://doc.rust-lang.org/std/sync/struct.Arc.html#method.strong_count). - -**NOTE**: When using Objective-C blocks with the [block crate](https://crates.io/crates/block) (e.g. looping over an array), make sure to invoke `xpc_retain()` on any object you wish to keep after the closure is dropped, or else the XPC objects in the closure will be dropped as well! See the `XPCDictionary` implementation for more details. xpc-sys handles this for you for its conversions. - -#### XPCDictionary and QueryBuilder - -While we can go from `HashMap<&str, XPCObject>` to `XPCObject`, it can be a little verbose. A `QueryBuilder` trait exposes some builder methods to make building an XPC dictionary a little easier (without all of the `into()`s, and some additional error checking). - -To write the query for `launchctl list`: - -```rust - let LIST_SERVICES: XPCDictionary = XPCDictionary::new() - // "list com.apple.Spotlight" (if specified) - // .entry("name", "com.apple.Spotlight"); - .entry("subsystem", 3 as u64) - .entry("handle", 0 as u64) - .entry("routine", 815 as u64) - .entry("legacy", true); - - let reply: Result = XPCDictionary::new() - // LIST_SERVICES is a proto - .extend(&LIST_SERVICES) - // Specify the domain type, or fall back on requester domain - .with_domain_type_or_default(Some(domain_type)) - .entry_if_present("name", name) - .pipe_routine_with_error_handling(); -``` - -In addition to checking `errno` is 0, `pipe_routine_with_error_handling` also looks for possible `error` and `errors` keys in the response dictionary and provides an `Err()` with `xpc_strerror` contents. - -#### Rust to XPC - -Conversions to/from Rust/XPC objects uses the [xpc.h functions documented on Apple Developer](https://developer.apple.com/documentation/xpc/xpc_services_xpc_h?language=objc) using the `From` trait. - -| Rust | XPC | -|----------------------------------------|----------------------------| -| i64 | _xpc_type_int64 | -| u64 | _xpc_type_uint64 | -| f64 | _xpc_type_double | -| bool | _xpc_bool_true/false | -| Into | _xpc_type_string | -| HashMap, Into> | _xpc_type_dictionary | -| Vec> | _xpc_type_array | -| std::os::unix::prelude::RawFd | _xpc_type_fd | -| (MachPortType::Send, mach_port_t) | _xpc_type_mach_send | -| (MachPortType::Recv, mach_port_t) | _xpc_type_mach_recv | - -Make XPC objects for anything with `From`. Make sure to use the correct type for file descriptors and Mach ports: -```rust -let mut message: HashMap<&str, XPCObject> = HashMap::new(); - -message.insert( - "domain-port", - XPCObject::from((MachPortType::Send, get_bootstrap_port() as mach_port_t)), -); -``` - -Go from an XPC object to value via the `TryXPCValue` trait. It checks your object's type via `xpc_get_type()` and yields a clear error if you're using the wrong type: -```rust -#[test] -fn deserialize_as_wrong_type() { - let an_i64: XPCObject = XPCObject::from(42 as i64); - let as_u64: Result = an_i64.xpc_value(); - assert_eq!( - as_u64.err().unwrap(), - XPCValueError("Cannot get int64 as uint64".to_string()) - ); -} -``` - -##### XPC Dictionaries - -Go from a `HashMap` to `xpc_object_t` with the `XPCObject` type: - -```rust -let mut message: HashMap<&str, XPCObject> = HashMap::new(); -message.insert("type", XPCObject::from(1 as u64)); -message.insert("handle", XPCObject::from(0 as u64)); -message.insert("subsystem", XPCObject::from(3 as u64)); -message.insert("routine", XPCObject::from(815 as u64)); -message.insert("legacy", XPCObject::from(true)); - -let xpc_object: XPCObject = message.into(); -``` - -Call `xpc_pipe_routine` and receive `Result`: - -```rust -let xpc_object: XPCObject = message.into(); - -match xpc_object.pipe_routine() { - Ok(xpc_object) => { /* do stuff and things */ }, - Err(XPCError::PipeError(err)) => { /* err is a string w/strerror(errno) */ } -} -``` - -The response is likely an XPC dictionary -- go back to a HashMap: - -```rust -let xpc_object: XPCObject = message.into(); -let response: Result = xpc_object - .pipe_routine() - .and_then(|r| r.try_into()); - -let XPCDictionary(hm) = response.unwrap(); -let whatever = hm.get("..."); -``` - -Response dictionaries can be nested, so `XPCDictionary` has a helper included for this scenario: - -```rust -let xpc_object: XPCObject = message.into(); - -// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System" -let response: Result = xpc_object - .pipe_routine() - .and_then(|r: XPCObject| r.try_into()); - .and_then(|d: XPCDictionary| d.get(&["service", "LimitLoadToSessionType"]) - .and_then(|lltst: XPCObject| lltst.xpc_value()); -``` - -Or, retrieve the `service` key (a child XPC Dictionary) from this response: - -```rust -let xpc_object: XPCObject = message.into(); - -// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System" -let response: Result = xpc_object - .pipe_routine() - .and_then(|r: XPCObject| r.try_into()); - .and_then(|d: XPCDictionary| d.get_as_dictionary(&["service"]); - -let XPCDictionary(hm) = response.unwrap(); -let whatever = hm.get("..."); -``` - -##### XPC Arrays - -An XPC array can be made from either `Vec` or `Vec>`: - -```rust -let xpc_array = XPCObject::from(vec![XPCObject::from("eins"), XPCObject::from("zwei"), XPCObject::from("polizei")]); - -let xpc_array = XPCObject::from(vec!["eins", "zwei", "polizei"]); -``` - -Go back to `Vec` using `xpc_value`: - -```rust -let rs_vec: Vec = xpc_array.xpc_value().unwrap(); -``` +While building launchk all of the XPC convenience glue was placed in `xpc-sys`. [See its docs here](xpc-sys/README.md). ### Credits A big thanks to these open source projects and general resources: - - [block](https://crates.io/crates/block) Obj-C block support, necessary for any XPC function taking `xpc_*_applier_t` - [Cursive](https://github.com/gyscos/cursive) TUI - [tokio](https://github.com/tokio-rs/tokio) ASIO - [plist](https://crates.io/crates/plist) Parsing & validation for XML and binary plists - [notify](https://docs.rs/notify/4.0.16/notify/) fsnotify - [bitflags](https://docs.rs/bitflags/1.2.1/bitflags/) - - [Apple Developer XPC services](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html) - [Apple Developer XPC API reference](https://developer.apple.com/documentation/xpc?language=objc) - [MOXIL / launjctl](http://newosxbook.com/articles/jlaunchctl.html) diff --git a/launchk/src/launchd/message.rs b/launchk/src/launchd/message.rs index 85e84d5..0ac32f8 100644 --- a/launchk/src/launchd/message.rs +++ b/launchk/src/launchd/message.rs @@ -1,6 +1,8 @@ use crate::launchd::query_builder::QueryBuilder; use xpc_sys::objects::xpc_dictionary::XPCDictionary; +// A bunch of XPCDictionary 'protos' that can be extended to make XPC queries + lazy_static! { /// launchctl list [name] pub static ref LIST_SERVICES: XPCDictionary = XPCDictionary::new() @@ -13,7 +15,7 @@ lazy_static! { /// launchctl load [path] pub static ref LOAD_PATHS: XPCDictionary = XPCDictionary::new() - .with_domain_port() + .with_domain_port_as_bootstrap_port() .entry("routine", 800 as u64) .entry("subsystem", 3 as u64) .entry("handle", 0 as u64) @@ -24,7 +26,7 @@ lazy_static! { /// launchctl unload [path] pub static ref UNLOAD_PATHS: XPCDictionary = XPCDictionary::new() - .with_domain_port() + .with_domain_port_as_bootstrap_port() .entry("routine", 801 as u64) .entry("subsystem", 3 as u64) .entry("handle", 0 as u64) @@ -36,14 +38,14 @@ lazy_static! { /// launchctl enable pub static ref ENABLE_NAMES: XPCDictionary = XPCDictionary::new() - .with_domain_port() + .with_domain_port_as_bootstrap_port() // .entry("handle", UID or ASID) .entry("routine", 808 as u64) .entry("subsystem", 3 as u64); /// launchctl disable pub static ref DISABLE_NAMES: XPCDictionary = XPCDictionary::new() - .with_domain_port() + .with_domain_port_as_bootstrap_port() // .entry("handle", UID or ASID) .entry("routine", 809 as u64) .entry("subsystem", 3 as u64); diff --git a/launchk/src/launchd/plist.rs b/launchk/src/launchd/plist.rs index e8fe1a3..d48b8de 100644 --- a/launchk/src/launchd/plist.rs +++ b/launchk/src/launchd/plist.rs @@ -21,11 +21,9 @@ lazy_static! { pub static ref LABEL_TO_ENTRY_CONFIG: RwLock> = RwLock::new(HashMap::new()); static ref EDITOR: &'static str = option_env!("EDITOR").unwrap_or("vim"); + static ref TMP_DIR: &'static str = option_env!("TMPDIR").unwrap_or("/tmp"); } -// TODO: fall back on /tmp -static TMP_DIR: &str = env!("TMPDIR"); - /* od -xc binary.plist 0000000 7062 696c 7473 3030 @@ -277,7 +275,7 @@ pub fn edit_and_replace(plist_meta: &LaunchdPlist) -> Result<(), String> { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("Must get ts"); - let temp_path = Path::new(TMP_DIR).join(format!("{}", now.as_secs())); + let temp_path = Path::new(*TMP_DIR).join(format!("{}", now.as_secs())); plist.to_file_xml(&temp_path).map_err(|e| e.to_string())?; // Start $EDITOR diff --git a/launchk/src/launchd/query.rs b/launchk/src/launchd/query.rs index caab8b4..fc3be94 100644 --- a/launchk/src/launchd/query.rs +++ b/launchk/src/launchd/query.rs @@ -3,20 +3,10 @@ use crate::launchd::message::{ UNLOAD_PATHS, }; use std::convert::TryFrom; -use std::{ - collections::HashSet, - ffi::{CStr, CString, OsStr}, - fs::File, - io::Read, - os::unix::prelude::{FromRawFd, RawFd}, - path::Path, - ptr::{null, null_mut}, -}; +use std::{collections::HashSet, os::unix::prelude::RawFd}; use xpc_sys::{ - errno, - objects::{xpc_object::XPCObject, xpc_shmem::XPCShmem}, - rs_strerror, + objects::xpc_shmem::XPCShmem, traits::{xpc_pipeable::XPCPipeable, xpc_value::TryXPCValue}, MAP_SHARED, }; @@ -156,7 +146,7 @@ pub fn dumpstate() -> Result<(usize, XPCShmem), XPCError> { let response = XPCDictionary::new() .extend(&DUMPSTATE) - .entry("shmem", XPCObject::from(&shmem.xpc_object)) + .entry("shmem", shmem.xpc_object.clone()) .pipe_routine_with_error_handling()?; let bytes_written: u64 = response.get(&["bytes-written"])?.xpc_value()?; diff --git a/launchk/src/launchd/query_builder.rs b/launchk/src/launchd/query_builder.rs index 1f5f530..0c80e52 100644 --- a/launchk/src/launchd/query_builder.rs +++ b/launchk/src/launchd/query_builder.rs @@ -4,17 +4,23 @@ use xpc_sys::objects::xpc_object::MachPortType; use xpc_sys::objects::xpc_object::XPCObject; use xpc_sys::{get_bootstrap_port, mach_port_t}; +/// Builder methods for XPCDictionary to make querying easier pub trait QueryBuilder { + /// Add entry to query fn entry, O: Into>(self, key: S, value: O) -> XPCDictionary; + + /// Add entry if option is Some() fn entry_if_present, O: Into>( self, key: S, value: Option, ) -> XPCDictionary; + /// Extend an existing XPCDictionary fn extend(self, other: &XPCDictionary) -> XPCDictionary; - fn with_domain_port(self) -> XPCDictionary + /// Adds "domain_port" with get_bootstrap_port() -> _xpc_type_mach_send + fn with_domain_port_as_bootstrap_port(self) -> XPCDictionary where Self: Sized, { @@ -24,6 +30,7 @@ pub trait QueryBuilder { ) } + /// Adds provided session type or falls back on Aqua fn with_session_type_or_default(self, session: Option) -> XPCDictionary where Self: Sized, @@ -31,6 +38,7 @@ pub trait QueryBuilder { self.entry("session", session.unwrap_or(SessionType::Aqua).to_string()) } + /// Adds provided handle or falls back on 0 fn with_handle_or_default(self, handle: Option) -> XPCDictionary where Self: Sized, @@ -38,6 +46,7 @@ pub trait QueryBuilder { self.entry("handle", handle.unwrap_or(0)) } + /// Adds provided DomainType, falls back on 7 (requestor's domain) fn with_domain_type_or_default(self, t: Option) -> XPCDictionary where Self: Sized, diff --git a/launchk/src/tui/root.rs b/launchk/src/tui/root.rs index 0000e9e..a902370 100644 --- a/launchk/src/tui/root.rs +++ b/launchk/src/tui/root.rs @@ -1,16 +1,12 @@ use std::collections::VecDeque; - -use std::io::Read; use std::os::unix::prelude::RawFd; - +use std::ptr::slice_from_raw_parts; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::Arc; -use std::ptr::slice_from_raw_parts; - use cursive::event::{Event, EventResult, Key}; use cursive::traits::{Resizable, Scrollable}; -use cursive::view::{AnyView, ViewWrapper}; +use cursive::view::ViewWrapper; use cursive::views::{LinearLayout, NamedView, Panel}; use cursive::{Cursive, Vec2, View}; @@ -113,7 +109,7 @@ impl RootLayout { .unwrap_or(()); } - /// Cursive uses a different crate for its channel, so this is some glue + /// Cursive uses a different crate for its channels (?), so this is some glue fn cbsink_channel(siv: &mut Cursive, handle: &Handle) -> Sender { let (tx, rx): (Sender, Receiver) = channel(); let sink = siv.cb_sink().clone(); diff --git a/xpc-sys/README.md b/xpc-sys/README.md index 19d8770..bfbc562 100644 --- a/xpc-sys/README.md +++ b/xpc-sys/README.md @@ -1,40 +1,18 @@ # xpc-sys -##### Object lifecycle +[![Rust](https://github.com/mach-kernel/launchk/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/mach-kernel/launchk/actions/workflows/rust.yml) ![crates.io](https://img.shields.io/crates/v/xpc-sys.svg) -XPCObject wraps `xpc_object_t` in an `Arc`. `Drop` will invoke `xpc_release()` on objects being dropped with no other [strong refs](https://doc.rust-lang.org/std/sync/struct.Arc.html#method.strong_count). - -**NOTE**: When using Objective-C blocks with the [block crate](https://crates.io/crates/block) (e.g. looping over an array), make sure to invoke `xpc_retain()` on any object you wish to keep after the closure is dropped, or else the XPC objects in the closure will be dropped as well! See the `XPCDictionary` implementation for more details. xpc-sys handles this for you for its conversions. +Various utilities for conveniently dealing with XPC in Rust. -#### XPCDictionary and QueryBuilder - -While we can go from `HashMap<&str, XPCObject>` to `XPCObject`, it can be a little verbose. A `QueryBuilder` trait exposes some builder methods to make building an XPC dictionary a little easier (without all of the `into()`s, and some additional error checking). +- [Object lifecycle](#object-lifecycle) +- [QueryBuilder](#query-builder) +- [XPC Dictionary](#xpc-dictionary) +- [XPC Array](#xpc-array) +- [XPC Shmem](#xpc-shmem) -To write the query for `launchctl list`: +#### Getting Started -```rust - let LIST_SERVICES: XPCDictionary = XPCDictionary::new() - // "list com.apple.Spotlight" (if specified) - // .entry("name", "com.apple.Spotlight"); - .entry("subsystem", 3 as u64) - .entry("handle", 0 as u64) - .entry("routine", 815 as u64) - .entry("legacy", true); - - let reply: Result = XPCDictionary::new() - // LIST_SERVICES is a proto - .extend(&LIST_SERVICES) - // Specify the domain type, or fall back on requester domain - .with_domain_type_or_default(Some(domain_type)) - .entry_if_present("name", name) - .pipe_routine_with_error_handling(); -``` - -In addition to checking `errno` is 0, `pipe_routine_with_error_handling` also looks for possible `error` and `errors` keys in the response dictionary and provides an `Err()` with `xpc_strerror` contents. - -#### Rust to XPC - -Conversions to/from Rust/XPC objects uses the [xpc.h functions documented on Apple Developer](https://developer.apple.com/documentation/xpc/xpc_services_xpc_h?language=objc) using the `From` trait. +Conversions to/from Rust/XPC objects uses the [xpc.h functions documented on Apple Developer](https://developer.apple.com/documentation/xpc/xpc_services_xpc_h?language=objc) using the `From` trait. Complex types such as arrays and shared memory objects described in greater detail below. | Rust | XPC | |----------------------------------------|----------------------------| @@ -48,6 +26,7 @@ Conversions to/from Rust/XPC objects uses the [xpc.h functions documented on App | std::os::unix::prelude::RawFd | _xpc_type_fd | | (MachPortType::Send, mach_port_t) | _xpc_type_mach_send | | (MachPortType::Recv, mach_port_t) | _xpc_type_mach_recv | +| XPCShmem | _xpc_type_shmem | Make XPC objects for anything with `From`. Make sure to use the correct type for file descriptors and Mach ports: ```rust @@ -72,7 +51,45 @@ fn deserialize_as_wrong_type() { } ``` -##### XPC Dictionaries +[Top](#xpc-sys) + +#### Object lifecycle + +XPCObject wraps `xpc_object_t` in an `Arc`. `Drop` will invoke `xpc_release()` on objects being dropped with no other [strong refs](https://doc.rust-lang.org/std/sync/struct.Arc.html#method.strong_count). + +**NOTE**: When using Objective-C blocks with the [block crate](https://crates.io/crates/block) (e.g. looping over an array), make sure to invoke `xpc_retain()` on any object you wish to keep after the closure is dropped, or else the XPC objects in the closure will be dropped as well! See the `XPCDictionary` implementation for more details. xpc-sys handles this for you for its conversions. + +[Top](#xpc-sys) + +#### QueryBuilder + +While we can go from `HashMap<&str, XPCObject>` to `XPCObject`, it can be a little verbose. A `QueryBuilder` trait exposes some builder methods to make building an XPC dictionary a little easier (without all of the `into()`s, and some additional error checking). + +To write the query for `launchctl list`: + +```rust + let LIST_SERVICES: XPCDictionary = XPCDictionary::new() + // "list com.apple.Spotlight" (if specified) + // .entry("name", "com.apple.Spotlight"); + .entry("subsystem", 3 as u64) + .entry("handle", 0 as u64) + .entry("routine", 815 as u64) + .entry("legacy", true); + + let reply: Result = XPCDictionary::new() + // LIST_SERVICES is a proto + .extend(&LIST_SERVICES) + // Specify the domain type, or fall back on requester domain + .with_domain_type_or_default(Some(domain_type)) + .entry_if_present("name", name) + .pipe_routine_with_error_handling(); +``` + +In addition to checking `errno` is 0, `pipe_routine_with_error_handling` also looks for possible `error` and `errors` keys in the response dictionary and provides an `Err()` with `xpc_strerror` contents. + +[Top](#xpc-sys) + +#### XPC Dictionary Go from a `HashMap` to `xpc_object_t` with the `XPCObject` type: @@ -138,7 +155,9 @@ let XPCDictionary(hm) = response.unwrap(); let whatever = hm.get("..."); ``` -##### XPC Arrays +[Top](#xpc-sys) + +#### XPC Array An XPC array can be made from either `Vec` or `Vec>`: @@ -153,3 +172,59 @@ Go back to `Vec` using `xpc_value`: ```rust let rs_vec: Vec = xpc_array.xpc_value().unwrap(); ``` + +[Top](#xpc-sys) + +#### XPC Shmem + +Make XPC shared memory objects by providing a size and vm_allocate/mmap flags. [`vm_allocate`](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MemoryAlloc.html) is used under the hood: + +```rust +let shmem = XPCShmem::new_task_self( + 0x1400000, + i32::try_from(MAP_SHARED).expect("Must conv flags"), +)?; + +// Use as _xpc_type_shmem argument in XPCDictionary +let response = XPCDictionary::new() + .extend(&DUMPSTATE) + .entry("shmem", shmem.xpc_object.clone()) + .pipe_routine_with_error_handling()?; +``` + +To work with the shmem region, use [`slice_from_raw_parts`](https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html): + +```rust +let bytes: &[u8] = unsafe { + &*slice_from_raw_parts(shmem.region as *mut u8, size) +}; + +// Make a string from bytes in the shmem +let mut hey_look_a_string = String::new(); +bytes.read_to_string(buf); +``` + +[Top](#xpc-sys) + +### Credits + +A big thanks to these open source projects and general resources: + + +- [block](https://crates.io/crates/block) Obj-C block support, necessary for any XPC function taking `xpc_*_applier_t` +- [Cursive](https://github.com/gyscos/cursive) TUI +- [tokio](https://github.com/tokio-rs/tokio) ASIO +- [plist](https://crates.io/crates/plist) Parsing & validation for XML and binary plists +- [notify](https://docs.rs/notify/4.0.16/notify/) fsnotify +- [bitflags](https://docs.rs/bitflags/1.2.1/bitflags/) +- [Apple Developer XPC services](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html) +- [Apple Developer XPC API reference](https://developer.apple.com/documentation/xpc?language=objc) +- [MOXIL / launjctl](http://newosxbook.com/articles/jlaunchctl.html) +- [geosnow - A Long Evening With macOS' sandbox](https://geosn0w.github.io/A-Long-Evening-With-macOS%27s-Sandbox/) +- [Bits of launchd - @5aelo](https://saelo.github.io/presentations/bits_of_launchd.pdf) +- [Audit tokens explained (e.g. ASID)](https://knight.sc/reverse%20engineering/2020/03/20/audit-tokens-explained.html) +- [objc.io XPC guide](https://www.objc.io/issues/14-mac/xpc/) +- The various source links found in comments, from Chrome's sandbox and other headers with definitions for private API functions. +- Last but not least, this is Apple's launchd after all, right :>)? I did not know systemd was inspired by launchd until I read [this HN comment](https://news.ycombinator.com/item?id=2565780), which sent me down this eventual rabbit hole :) + +Everything else (C) David Stancu & Contributors 2021 \ No newline at end of file diff --git a/xpc-sys/src/objects/mod.rs b/xpc-sys/src/objects/mod.rs index 6dbdc17..ca35acb 100644 --- a/xpc-sys/src/objects/mod.rs +++ b/xpc-sys/src/objects/mod.rs @@ -7,9 +7,6 @@ pub mod xpc_dictionary; /// xpc_object_t -> xpc_type_t pub mod xpc_type; -/// xpc_pipe_t -pub mod xpc_pipe; - pub mod unix_fifo; pub mod xpc_error; pub mod xpc_shmem; diff --git a/xpc-sys/src/objects/xpc_dictionary.rs b/xpc-sys/src/objects/xpc_dictionary.rs index da90531..39056db 100644 --- a/xpc-sys/src/objects/xpc_dictionary.rs +++ b/xpc-sys/src/objects/xpc_dictionary.rs @@ -136,7 +136,7 @@ where { /// Creates a XPC dictionary /// - /// Values must be XPCObject newtype but can encapsulate any + /// Values must be XPCObject but can encapsulate any /// valid xpc_object_t fn from(message: HashMap) -> Self { let dict = unsafe { xpc_dictionary_create(null(), null_mut(), 0) }; diff --git a/xpc-sys/src/objects/xpc_object.rs b/xpc-sys/src/objects/xpc_object.rs index cc9d9fa..0039e61 100644 --- a/xpc-sys/src/objects/xpc_object.rs +++ b/xpc-sys/src/objects/xpc_object.rs @@ -41,7 +41,8 @@ impl Default for XPCObject { } impl fmt::Display for XPCObject { - /// Use xpc_copy_description to get an easy snapshot of a dictionary + /// Use xpc_copy_description to show as a string, for + /// _xpc_type_dictionary contents are shown! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let XPCObject(arc, _) = self; @@ -118,6 +119,7 @@ impl From<&str> for XPCObject { } impl> From> for XPCObject { + /// Create XPCObject via xpc_array_create fn from(value: Vec) -> Self { let xpc_array = unsafe { xpc_array_create(null_mut(), 0) }; for object in value { @@ -144,9 +146,11 @@ impl From for XPCObject { } } -impl From<&Arc> for XPCObject { - fn from(value: &Arc) -> Self { - let XPCObject(ref arc, ref xpc_type) = **value; +impl> From for XPCObject { + /// Create XPCObject from another ref + fn from(other: R) -> Self { + let other_ref = other.as_ref(); + let XPCObject(ref arc, ref xpc_type) = other_ref; XPCObject(arc.clone(), xpc_type.clone()) } } diff --git a/xpc-sys/src/objects/xpc_pipe.rs b/xpc-sys/src/objects/xpc_pipe.rs deleted file mode 100644 index 66ff44f..0000000 --- a/xpc-sys/src/objects/xpc_pipe.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::xpc_pipe_t; - -#[repr(transparent)] -#[derive(Clone, PartialEq, Eq)] -pub struct XPCPipe(pub xpc_pipe_t); - -unsafe impl Send for XPCPipe {} -unsafe impl Sync for XPCPipe {} diff --git a/xpc-sys/src/objects/xpc_shmem.rs b/xpc-sys/src/objects/xpc_shmem.rs index c0fb05d..bc8b716 100644 --- a/xpc-sys/src/objects/xpc_shmem.rs +++ b/xpc-sys/src/objects/xpc_shmem.rs @@ -4,16 +4,18 @@ use crate::{ mach_port_t, mach_task_self_, rs_strerror, vm_address_t, vm_allocate, vm_deallocate, vm_size_t, xpc_shmem_create, }; +use std::ffi::c_void; use std::os::raw::c_int; use std::ptr::null_mut; -use std::{ffi::c_void, sync::Arc}; +/// Wrapper around vm_allocate() vm_deallocate() with an XPCObject +/// member of XPC type _xpc_type_shmem #[derive(Debug, Clone)] pub struct XPCShmem { pub task: mach_port_t, pub size: vm_size_t, pub region: *mut c_void, - pub xpc_object: Arc, + pub xpc_object: XPCObject, } unsafe impl Send for XPCShmem {} @@ -37,9 +39,7 @@ impl XPCShmem { task, size, region, - xpc_object: Arc::new(unsafe { - xpc_shmem_create(region as *mut c_void, size as u64).into() - }), + xpc_object: unsafe { xpc_shmem_create(region as *mut c_void, size as u64).into() }, }) } } @@ -52,25 +52,12 @@ impl XPCShmem { impl Drop for XPCShmem { fn drop(&mut self) { let XPCShmem { - size, - task, - region, - xpc_object, + size, task, region, .. } = self; if *region == null_mut() { return; } - let refs = Arc::strong_count(xpc_object); - if refs > 1 { - log::warn!( - "vm_allocated region {:p} still has {} refs, cannot vm_deallocate", - *region, - refs - ); - return; - } - unsafe { vm_deallocate(*task, *region as vm_address_t, *size) }; } } diff --git a/xpc-sys/src/objects/xpc_type.rs b/xpc-sys/src/objects/xpc_type.rs index 2ac6bbc..f8ce1b2 100644 --- a/xpc-sys/src/objects/xpc_type.rs +++ b/xpc-sys/src/objects/xpc_type.rs @@ -1,7 +1,7 @@ use crate::{ _xpc_type_array, _xpc_type_bool, _xpc_type_dictionary, _xpc_type_double, _xpc_type_fd, - _xpc_type_int64, _xpc_type_mach_recv, _xpc_type_mach_send, _xpc_type_s, _xpc_type_string, - _xpc_type_uint64, xpc_get_type, xpc_object_t, xpc_type_get_name, xpc_type_t, + _xpc_type_int64, _xpc_type_mach_recv, _xpc_type_mach_send, _xpc_type_s, _xpc_type_shmem, + _xpc_type_string, _xpc_type_uint64, xpc_get_type, xpc_object_t, xpc_type_get_name, xpc_type_t, }; use crate::objects::xpc_error::XPCError; @@ -68,6 +68,7 @@ lazy_static! { pub static ref MachRecv: XPCType = unsafe { (&_xpc_type_mach_recv as *const _xpc_type_s).into() }; pub static ref Fd: XPCType = unsafe { (&_xpc_type_fd as *const _xpc_type_s).into() }; + pub static ref Shmem: XPCType = unsafe { (&_xpc_type_shmem as *const _xpc_type_s).into() }; } /// Runtime type check for XPC object. I do not know if possible/advantageous to represent From 2389397be15b6b1b234607aa10d74f8dd201f915 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sat, 29 May 2021 15:39:00 -0400 Subject: [PATCH 24/28] drop libc from launchk --- Cargo.lock | 1 - launchk/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56c4ac8..ef072e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -630,7 +630,6 @@ dependencies = [ "env_logger 0.8.3", "futures", "lazy_static", - "libc", "log", "notify", "plist", diff --git a/launchk/Cargo.toml b/launchk/Cargo.toml index 696411d..94630ff 100644 --- a/launchk/Cargo.toml +++ b/launchk/Cargo.toml @@ -8,7 +8,6 @@ edition = "2018" [dependencies] xpc-sys = { path= "../xpc-sys" } -libc = "0.2.94" lazy_static = "1.4.0" cursive = { version = "0.15.0", features = ["toml"] } tokio = { version = "1", features = ["full"] } From 7ec4b5206fad8fefaa7c21997b15630f805ef25a Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sat, 29 May 2021 17:04:07 -0400 Subject: [PATCH 25/28] fix broken build with cargo run --release, better error messages --- xpc-sys/src/objects/xpc_dictionary.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/xpc-sys/src/objects/xpc_dictionary.rs b/xpc-sys/src/objects/xpc_dictionary.rs index 39056db..a00632a 100644 --- a/xpc-sys/src/objects/xpc_dictionary.rs +++ b/xpc-sys/src/objects/xpc_dictionary.rs @@ -4,13 +4,14 @@ use std::convert::{TryFrom, TryInto}; use std::ffi::{CStr, CString}; use std::os::raw::c_char; use std::ptr::{null, null_mut}; -use std::rc::Rc; +use std::sync::Arc; use crate::objects::xpc_error::XPCError; use crate::objects::xpc_error::XPCError::DictionaryError; use crate::objects::xpc_object::XPCObject; +use crate::rs_strerror; use crate::{objects, xpc_retain}; -use crate::{xpc_dictionary_apply, xpc_dictionary_create, xpc_dictionary_set_value, xpc_object_t}; +use crate::{xpc_dictionary_apply, xpc_dictionary_create, xpc_dictionary_set_value, xpc_object_t, errno}; use block::ConcreteBlock; @@ -82,31 +83,33 @@ impl TryFrom<&XPCObject> for XPCDictionary { )); } - let map: Rc>> = Rc::new(RefCell::new(HashMap::new())); - let map_rc_clone = map.clone(); + let map: Arc>> = Arc::new(RefCell::new(HashMap::new())); + let map_block_clone = map.clone(); + // https://developer.apple.com/documentation/xpc/1505404-xpc_dictionary_apply?language=objc let block = ConcreteBlock::new(move |key: *const c_char, value: xpc_object_t| { // Prevent xpc_release() collection on block exit unsafe { xpc_retain(value) }; - let str_key = unsafe { CStr::from_ptr(key).to_string_lossy().to_string() }; - map_rc_clone.borrow_mut().insert(str_key, value.into()); + map_block_clone.borrow_mut().insert(str_key, value.into()); + + // Must return true + true }); let block = block.copy(); - let ok = unsafe { xpc_dictionary_apply(object.as_ptr(), &*block as *const _ as *mut _) }; // Explicitly drop the block so map is the only live reference // so we can collect it below - std::mem::drop(block); + drop(block); if ok { - match Rc::try_unwrap(map) { + match Arc::try_unwrap(map) { Ok(cell) => Ok(XPCDictionary(cell.into_inner())), - Err(_) => Err(DictionaryError("Unable to unwrap Rc".to_string())), + Err(_) => Err(DictionaryError("Unable to unwrap Arc".to_string())), } } else { - Err(DictionaryError("xpc_dictionary_apply failed".to_string())) + Err(DictionaryError(format!("xpc_dictionary_apply failed: {}", rs_strerror(unsafe { errno })))) } } } From ed53d76be0fa4818ddc013e1e98a579bd07a84fd Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sat, 29 May 2021 17:36:50 -0400 Subject: [PATCH 26/28] fix panic on small resize --- launchk/src/tui/dialog.rs | 4 ++-- launchk/src/tui/omnibox/view.rs | 7 +++++-- launchk/src/tui/table/column_sizer.rs | 19 +++++++++++++++---- xpc-sys/src/objects/xpc_dictionary.rs | 9 +++++++-- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/launchk/src/tui/dialog.rs b/launchk/src/tui/dialog.rs index e3ea913..0587b0b 100644 --- a/launchk/src/tui/dialog.rs +++ b/launchk/src/tui/dialog.rs @@ -154,7 +154,7 @@ pub fn show_csr_info() -> CbSinkMessage { .title("CSR Info") .content(TextView::new(csr_flags.join("\n"))) .dismiss_button("OK") - .padding(Margins::trbl(2, 2, 2, 2)), + .padding(Margins::trbl(4, 4, 4, 4)), ) }) } @@ -171,7 +171,7 @@ pub fn show_help() -> CbSinkMessage { .title("Help") .content(TextView::new(commands.join("\n"))) .dismiss_button("OK") - .padding(Margins::trbl(2, 2, 2, 2)), + .padding(Margins::trbl(4, 4, 4, 4)), ) }) } diff --git a/launchk/src/tui/omnibox/view.rs b/launchk/src/tui/omnibox/view.rs index 62112ee..2283877 100644 --- a/launchk/src/tui/omnibox/view.rs +++ b/launchk/src/tui/omnibox/view.rs @@ -294,13 +294,16 @@ impl OmniboxView { .. } = &*read; - let jtf_ofs = if *mode != OmniboxMode::JobTypeFilter { + let mut jtf_ofs = if *mode != OmniboxMode::JobTypeFilter { "[sguadl]".len() } else { "[system global user agent daemon loaded]".len() }; - let mut jtf_ofs = self.last_size.borrow().x - jtf_ofs; + if jtf_ofs < self.last_size.borrow().x { + jtf_ofs = self.last_size.borrow().x - jtf_ofs; + } + printer.print(XY::new(jtf_ofs, 0), "["); jtf_ofs += 1; diff --git a/launchk/src/tui/table/column_sizer.rs b/launchk/src/tui/table/column_sizer.rs index 21a7c53..63b7018 100644 --- a/launchk/src/tui/table/column_sizer.rs +++ b/launchk/src/tui/table/column_sizer.rs @@ -57,24 +57,35 @@ impl ColumnSizer { // I have 'sized' my user defined columns around how much // space I need to just display the font, and the rest by // blindly dividing space, only apply padding to UDCs - - if self.user_sizes.contains_key(&i) { + let size = if self.user_sizes.contains_key(&i) { size + self.padding.get() } else { size + }; + + if size > 1 { + size + } else { + 1 } } /// Call when x changes to recompute dynamic_column_size and padding pub fn update_x(&self, x: usize) { - let mut remaining = x - self.user_sizes_total; + let mut remaining = if x > self.user_sizes_total { + x - self.user_sizes_total + } else { + 0 + }; let mut dcs = remaining / self.num_dynamic_columns; if dcs > 35 { dcs = 35; } - remaining = remaining - (self.num_dynamic_columns * dcs); + if remaining > (self.num_dynamic_columns * dcs) { + remaining = remaining - (self.num_dynamic_columns * dcs); + } self.dynamic_column_size.set(dcs); self.padding diff --git a/xpc-sys/src/objects/xpc_dictionary.rs b/xpc-sys/src/objects/xpc_dictionary.rs index a00632a..caa0733 100644 --- a/xpc-sys/src/objects/xpc_dictionary.rs +++ b/xpc-sys/src/objects/xpc_dictionary.rs @@ -10,8 +10,10 @@ use crate::objects::xpc_error::XPCError; use crate::objects::xpc_error::XPCError::DictionaryError; use crate::objects::xpc_object::XPCObject; use crate::rs_strerror; +use crate::{ + errno, xpc_dictionary_apply, xpc_dictionary_create, xpc_dictionary_set_value, xpc_object_t, +}; use crate::{objects, xpc_retain}; -use crate::{xpc_dictionary_apply, xpc_dictionary_create, xpc_dictionary_set_value, xpc_object_t, errno}; use block::ConcreteBlock; @@ -109,7 +111,10 @@ impl TryFrom<&XPCObject> for XPCDictionary { Err(_) => Err(DictionaryError("Unable to unwrap Arc".to_string())), } } else { - Err(DictionaryError(format!("xpc_dictionary_apply failed: {}", rs_strerror(unsafe { errno })))) + Err(DictionaryError(format!( + "xpc_dictionary_apply failed: {}", + rs_strerror(unsafe { errno }) + ))) } } } From d240c9f2eb73570c064533f7d7ac5b4c1ec93525 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sat, 29 May 2021 18:13:15 -0400 Subject: [PATCH 27/28] replace demo reel, fix command suggestion careless gsub error update demo reel again update readme again --- README.md | 24 +++++++++++++++--------- launchk/src/tui/omnibox/view.rs | 2 +- xpc-sys/README.md | 4 +++- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a8bca5e..638612a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A [Cursive](https://github.com/gyscos/cursive) TUI that makes XPC queries & help Should work on macOS 10.10+ according to the availability sec. [in the docs](https://developer.apple.com/documentation/xpc?language=objc). - + #### Features @@ -16,16 +16,19 @@ Should work on macOS 10.10+ according to the availability sec. [in the docs](htt - Global (/Library) - User (~/) - fsnotify detection for new plists added to above directories -- load -- unload -- dumpstate (opens in `$PAGER`) -- dumpjpcategory (opens in `$PAGER`) -- procinfo (opens in `$PAGER`, does not require root!) -- `:edit` -> Open plist in `$EDITOR`, defaulting to `vim`. Supports binary plists -> shown as XML for edit, then marshalled back into binary format on save. +- `load` +- `unload` +- `dumpstate` (opens in `$PAGER`) +- `dumpjpcategory` (opens in `$PAGER`) +- `procinfo` (opens in `$PAGER`, does not require root!) +- `edit` plist in `$EDITOR` with support for binary plists +- `csrinfo` show all CSR flags and their values #### xpc-sys -While building launchk all of the XPC convenience glue was placed in `xpc-sys`. [See its docs here](xpc-sys/README.md). +While building launchk, XPC convenience glue was placed in `xpc-sys`. + +[[See its README here]](xpc-sys/README.md) ### Credits @@ -37,6 +40,9 @@ A big thanks to these open source projects and general resources: - [plist](https://crates.io/crates/plist) Parsing & validation for XML and binary plists - [notify](https://docs.rs/notify/4.0.16/notify/) fsnotify - [bitflags](https://docs.rs/bitflags/1.2.1/bitflags/) +- [libc](https://crates.io/crates/libc) +- [lazy_static](https://crates.io/crates/lazy_static) +- [xcrun](https://crates.io/crates/xcrun) - [Apple Developer XPC services](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html) - [Apple Developer XPC API reference](https://developer.apple.com/documentation/xpc?language=objc) - [MOXIL / launjctl](http://newosxbook.com/articles/jlaunchctl.html) @@ -47,4 +53,4 @@ A big thanks to these open source projects and general resources: - The various source links found in comments, from Chrome's sandbox and other headers with definitions for private API functions. - Last but not least, this is Apple's launchd after all, right :>)? I did not know systemd was inspired by launchd until I read [this HN comment](https://news.ycombinator.com/item?id=2565780), which sent me down this eventual rabbit hole :) -Everything else (C) David Stancu & Contributors 2021 \ No newline at end of file +Everything else (C) David Stancu & Contributors 2021 diff --git a/launchk/src/tui/omnibox/view.rs b/launchk/src/tui/omnibox/view.rs index 2283877..dae2ab0 100644 --- a/launchk/src/tui/omnibox/view.rs +++ b/launchk/src/tui/omnibox/view.rs @@ -276,7 +276,7 @@ impl OmniboxView { return; } let (cmd, desc, ..) = suggestion.unwrap(); - let cmd_string = cmd.to_string().replace(&state.command_filter, ""); + let cmd_string = cmd.to_string().replacen(&state.command_filter, "", 1); printer.with_style(Style::from(Color::Light(BaseColor::Black)), |p| { p.print(XY::new(0, 0), cmd_string.as_str()) diff --git a/xpc-sys/README.md b/xpc-sys/README.md index bfbc562..c44e163 100644 --- a/xpc-sys/README.md +++ b/xpc-sys/README.md @@ -210,13 +210,15 @@ bytes.read_to_string(buf); A big thanks to these open source projects and general resources: - - [block](https://crates.io/crates/block) Obj-C block support, necessary for any XPC function taking `xpc_*_applier_t` - [Cursive](https://github.com/gyscos/cursive) TUI - [tokio](https://github.com/tokio-rs/tokio) ASIO - [plist](https://crates.io/crates/plist) Parsing & validation for XML and binary plists - [notify](https://docs.rs/notify/4.0.16/notify/) fsnotify - [bitflags](https://docs.rs/bitflags/1.2.1/bitflags/) +- [libc](https://crates.io/crates/libc) +- [lazy_static](https://crates.io/crates/lazy_static) +- [xcrun](https://crates.io/crates/xcrun) - [Apple Developer XPC services](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html) - [Apple Developer XPC API reference](https://developer.apple.com/documentation/xpc?language=objc) - [MOXIL / launjctl](http://newosxbook.com/articles/jlaunchctl.html) From d4e6ed7b6ac57e907ddf2a6a2230dd4a7283a3c0 Mon Sep 17 00:00:00 2001 From: David Stancu Date: Sat, 29 May 2021 18:55:19 -0400 Subject: [PATCH 28/28] bump version --- Cargo.lock | 2 +- xpc-sys/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef072e6..2981c7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1384,7 +1384,7 @@ checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" [[package]] name = "xpc-sys" -version = "0.1.0-a2" +version = "0.1.0" dependencies = [ "bindgen", "bitflags", diff --git a/xpc-sys/Cargo.toml b/xpc-sys/Cargo.toml index 350b13d..c524301 100644 --- a/xpc-sys/Cargo.toml +++ b/xpc-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "xpc-sys" description = "Conveniently call routines with wrappers for xpc_pipe_routine() and go from Rust types to XPC objects and back!" -version = "0.1.0-a2" +version = "0.1.0" authors = ["David Stancu "] license = "MIT" edition = "2018"