summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-04-17 15:06:33 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-04-23 13:03:54 +0200
commit1c4657b3cd565dbc14747c69dc416c020103ad88 (patch)
tree2bab61bd7243bdc038bbf470202b2af041a05723
parent02a2dad77573ae9fc2c538f92c6fd30959b3eff4 (diff)
downloadmullvadvpn-1c4657b3cd565dbc14747c69dc416c020103ad88.tar.xz
mullvadvpn-1c4657b3cd565dbc14747c69dc416c020103ad88.zip
Add more process helper functions to talpid-macos
-rw-r--r--Cargo.lock4
-rw-r--r--talpid-macos/Cargo.toml2
-rw-r--r--talpid-macos/src/apsl-header27
-rw-r--r--talpid-macos/src/bindings.rs192
-rwxr-xr-xtalpid-macos/src/generate-bindings.sh20
-rw-r--r--talpid-macos/src/lib.rs4
-rw-r--r--talpid-macos/src/process.rs95
7 files changed, 340 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ea1474a865..046d9e0b42 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2364,9 +2364,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.169"
+version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
+checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libdbus-sys"
diff --git a/talpid-macos/Cargo.toml b/talpid-macos/Cargo.toml
index 7868707b4c..7b910f5e6d 100644
--- a/talpid-macos/Cargo.toml
+++ b/talpid-macos/Cargo.toml
@@ -11,5 +11,5 @@ rust-version.workspace = true
workspace = true
[target.'cfg(target_os="macos")'.dependencies]
-libc = "0.2"
+libc = "0.2.172"
log = { workspace = true }
diff --git a/talpid-macos/src/apsl-header b/talpid-macos/src/apsl-header
new file mode 100644
index 0000000000..eebfa36fa6
--- /dev/null
+++ b/talpid-macos/src/apsl-header
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2025 Mullvad VPN AB. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ *
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
diff --git a/talpid-macos/src/bindings.rs b/talpid-macos/src/bindings.rs
new file mode 100644
index 0000000000..4076cd2c29
--- /dev/null
+++ b/talpid-macos/src/bindings.rs
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2025 Mullvad VPN AB. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ *
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
+/* automatically generated by rust-bindgen 0.70.1 */
+
+pub const PROX_FDTYPE_VNODE: u32 = 1;
+pub const PROC_PIDFDVNODEPATHINFO: u32 = 2;
+pub type __uint32_t = ::std::os::raw::c_uint;
+pub type __int64_t = ::std::os::raw::c_longlong;
+pub type __darwin_gid_t = __uint32_t;
+pub type __darwin_off_t = __int64_t;
+pub type __darwin_uid_t = __uint32_t;
+pub type gid_t = __darwin_gid_t;
+pub type off_t = __darwin_off_t;
+pub type uid_t = __darwin_uid_t;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct fsid {
+ pub val: [i32; 2usize],
+}
+#[allow(clippy::unnecessary_operation, clippy::identity_op)]
+const _: () = {
+ ["Size of fsid"][::std::mem::size_of::<fsid>() - 8usize];
+ ["Alignment of fsid"][::std::mem::align_of::<fsid>() - 4usize];
+ ["Offset of field: fsid::val"][::std::mem::offset_of!(fsid, val) - 0usize];
+};
+pub type fsid_t = fsid;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct proc_fileinfo {
+ pub fi_openflags: u32,
+ pub fi_status: u32,
+ pub fi_offset: off_t,
+ pub fi_type: i32,
+ pub fi_guardflags: u32,
+}
+#[allow(clippy::unnecessary_operation, clippy::identity_op)]
+const _: () = {
+ ["Size of proc_fileinfo"][::std::mem::size_of::<proc_fileinfo>() - 24usize];
+ ["Alignment of proc_fileinfo"][::std::mem::align_of::<proc_fileinfo>() - 8usize];
+ ["Offset of field: proc_fileinfo::fi_openflags"]
+ [::std::mem::offset_of!(proc_fileinfo, fi_openflags) - 0usize];
+ ["Offset of field: proc_fileinfo::fi_status"]
+ [::std::mem::offset_of!(proc_fileinfo, fi_status) - 4usize];
+ ["Offset of field: proc_fileinfo::fi_offset"]
+ [::std::mem::offset_of!(proc_fileinfo, fi_offset) - 8usize];
+ ["Offset of field: proc_fileinfo::fi_type"]
+ [::std::mem::offset_of!(proc_fileinfo, fi_type) - 16usize];
+ ["Offset of field: proc_fileinfo::fi_guardflags"]
+ [::std::mem::offset_of!(proc_fileinfo, fi_guardflags) - 20usize];
+};
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vinfo_stat {
+ pub vst_dev: u32,
+ pub vst_mode: u16,
+ pub vst_nlink: u16,
+ pub vst_ino: u64,
+ pub vst_uid: uid_t,
+ pub vst_gid: gid_t,
+ pub vst_atime: i64,
+ pub vst_atimensec: i64,
+ pub vst_mtime: i64,
+ pub vst_mtimensec: i64,
+ pub vst_ctime: i64,
+ pub vst_ctimensec: i64,
+ pub vst_birthtime: i64,
+ pub vst_birthtimensec: i64,
+ pub vst_size: off_t,
+ pub vst_blocks: i64,
+ pub vst_blksize: i32,
+ pub vst_flags: u32,
+ pub vst_gen: u32,
+ pub vst_rdev: u32,
+ pub vst_qspare: [i64; 2usize],
+}
+#[allow(clippy::unnecessary_operation, clippy::identity_op)]
+const _: () = {
+ ["Size of vinfo_stat"][::std::mem::size_of::<vinfo_stat>() - 136usize];
+ ["Alignment of vinfo_stat"][::std::mem::align_of::<vinfo_stat>() - 8usize];
+ ["Offset of field: vinfo_stat::vst_dev"][::std::mem::offset_of!(vinfo_stat, vst_dev) - 0usize];
+ ["Offset of field: vinfo_stat::vst_mode"]
+ [::std::mem::offset_of!(vinfo_stat, vst_mode) - 4usize];
+ ["Offset of field: vinfo_stat::vst_nlink"]
+ [::std::mem::offset_of!(vinfo_stat, vst_nlink) - 6usize];
+ ["Offset of field: vinfo_stat::vst_ino"][::std::mem::offset_of!(vinfo_stat, vst_ino) - 8usize];
+ ["Offset of field: vinfo_stat::vst_uid"][::std::mem::offset_of!(vinfo_stat, vst_uid) - 16usize];
+ ["Offset of field: vinfo_stat::vst_gid"][::std::mem::offset_of!(vinfo_stat, vst_gid) - 20usize];
+ ["Offset of field: vinfo_stat::vst_atime"]
+ [::std::mem::offset_of!(vinfo_stat, vst_atime) - 24usize];
+ ["Offset of field: vinfo_stat::vst_atimensec"]
+ [::std::mem::offset_of!(vinfo_stat, vst_atimensec) - 32usize];
+ ["Offset of field: vinfo_stat::vst_mtime"]
+ [::std::mem::offset_of!(vinfo_stat, vst_mtime) - 40usize];
+ ["Offset of field: vinfo_stat::vst_mtimensec"]
+ [::std::mem::offset_of!(vinfo_stat, vst_mtimensec) - 48usize];
+ ["Offset of field: vinfo_stat::vst_ctime"]
+ [::std::mem::offset_of!(vinfo_stat, vst_ctime) - 56usize];
+ ["Offset of field: vinfo_stat::vst_ctimensec"]
+ [::std::mem::offset_of!(vinfo_stat, vst_ctimensec) - 64usize];
+ ["Offset of field: vinfo_stat::vst_birthtime"]
+ [::std::mem::offset_of!(vinfo_stat, vst_birthtime) - 72usize];
+ ["Offset of field: vinfo_stat::vst_birthtimensec"]
+ [::std::mem::offset_of!(vinfo_stat, vst_birthtimensec) - 80usize];
+ ["Offset of field: vinfo_stat::vst_size"]
+ [::std::mem::offset_of!(vinfo_stat, vst_size) - 88usize];
+ ["Offset of field: vinfo_stat::vst_blocks"]
+ [::std::mem::offset_of!(vinfo_stat, vst_blocks) - 96usize];
+ ["Offset of field: vinfo_stat::vst_blksize"]
+ [::std::mem::offset_of!(vinfo_stat, vst_blksize) - 104usize];
+ ["Offset of field: vinfo_stat::vst_flags"]
+ [::std::mem::offset_of!(vinfo_stat, vst_flags) - 108usize];
+ ["Offset of field: vinfo_stat::vst_gen"]
+ [::std::mem::offset_of!(vinfo_stat, vst_gen) - 112usize];
+ ["Offset of field: vinfo_stat::vst_rdev"]
+ [::std::mem::offset_of!(vinfo_stat, vst_rdev) - 116usize];
+ ["Offset of field: vinfo_stat::vst_qspare"]
+ [::std::mem::offset_of!(vinfo_stat, vst_qspare) - 120usize];
+};
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vnode_info {
+ pub vi_stat: vinfo_stat,
+ pub vi_type: ::std::os::raw::c_int,
+ pub vi_pad: ::std::os::raw::c_int,
+ pub vi_fsid: fsid_t,
+}
+#[allow(clippy::unnecessary_operation, clippy::identity_op)]
+const _: () = {
+ ["Size of vnode_info"][::std::mem::size_of::<vnode_info>() - 152usize];
+ ["Alignment of vnode_info"][::std::mem::align_of::<vnode_info>() - 8usize];
+ ["Offset of field: vnode_info::vi_stat"][::std::mem::offset_of!(vnode_info, vi_stat) - 0usize];
+ ["Offset of field: vnode_info::vi_type"]
+ [::std::mem::offset_of!(vnode_info, vi_type) - 136usize];
+ ["Offset of field: vnode_info::vi_pad"][::std::mem::offset_of!(vnode_info, vi_pad) - 140usize];
+ ["Offset of field: vnode_info::vi_fsid"]
+ [::std::mem::offset_of!(vnode_info, vi_fsid) - 144usize];
+};
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vnode_info_path {
+ pub vip_vi: vnode_info,
+ pub vip_path: [::std::os::raw::c_char; 1024usize],
+}
+#[allow(clippy::unnecessary_operation, clippy::identity_op)]
+const _: () = {
+ ["Size of vnode_info_path"][::std::mem::size_of::<vnode_info_path>() - 1176usize];
+ ["Alignment of vnode_info_path"][::std::mem::align_of::<vnode_info_path>() - 8usize];
+ ["Offset of field: vnode_info_path::vip_vi"]
+ [::std::mem::offset_of!(vnode_info_path, vip_vi) - 0usize];
+ ["Offset of field: vnode_info_path::vip_path"]
+ [::std::mem::offset_of!(vnode_info_path, vip_path) - 152usize];
+};
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vnode_fdinfowithpath {
+ pub pfi: proc_fileinfo,
+ pub pvip: vnode_info_path,
+}
+#[allow(clippy::unnecessary_operation, clippy::identity_op)]
+const _: () = {
+ ["Size of vnode_fdinfowithpath"][::std::mem::size_of::<vnode_fdinfowithpath>() - 1200usize];
+ ["Alignment of vnode_fdinfowithpath"][::std::mem::align_of::<vnode_fdinfowithpath>() - 8usize];
+ ["Offset of field: vnode_fdinfowithpath::pfi"]
+ [::std::mem::offset_of!(vnode_fdinfowithpath, pfi) - 0usize];
+ ["Offset of field: vnode_fdinfowithpath::pvip"]
+ [::std::mem::offset_of!(vnode_fdinfowithpath, pvip) - 24usize];
+};
diff --git a/talpid-macos/src/generate-bindings.sh b/talpid-macos/src/generate-bindings.sh
new file mode 100755
index 0000000000..5aac30142c
--- /dev/null
+++ b/talpid-macos/src/generate-bindings.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+# This generates new bindings from 'proc_info.h'.
+# bindgen is required: cargo install bindgen-cli
+
+set -eu
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+cd "$SCRIPT_DIR"
+
+MACOS_SDK_PATH="$(xcrun --sdk macosx --show-sdk-path)"
+PROC_INFO_PATH="$MACOS_SDK_PATH/usr/include/sys/proc_info.h"
+
+cp ./apsl-header ./bindings.rs
+
+bindgen "$PROC_INFO_PATH" \
+ --allowlist-item "^PROC_PIDFDVNODEPATHINFO" \
+ --allowlist-item "^PROX_FDTYPE_VNODE" \
+ --allowlist-item "^vnode_fdinfowithpath" \
+ >> ./bindings.rs
diff --git a/talpid-macos/src/lib.rs b/talpid-macos/src/lib.rs
index 964dd689e0..5a282660d3 100644
--- a/talpid-macos/src/lib.rs
+++ b/talpid-macos/src/lib.rs
@@ -5,3 +5,7 @@
/// Processes
pub mod process;
+
+/// OS bindings generated by 'generate_bindings.rs'
+#[allow(non_camel_case_types)]
+mod bindings;
diff --git a/talpid-macos/src/process.rs b/talpid-macos/src/process.rs
index 69722e054c..0e72d2f7ca 100644
--- a/talpid-macos/src/process.rs
+++ b/talpid-macos/src/process.rs
@@ -1,9 +1,16 @@
-use libc::{c_void, pid_t, proc_listallpids, proc_pidpath};
+use libc::{
+ c_void, pid_t, proc_bsdinfo, proc_fdinfo, proc_listallpids, proc_pidfdinfo, proc_pidinfo,
+ proc_pidpath, PROC_PIDLISTFDS, PROC_PIDTBSDINFO,
+};
use std::{
+ ffi::{c_int, CStr, CString},
io,
path::{Path, PathBuf},
+ ptr,
};
+use crate::bindings::{vnode_fdinfowithpath, PROC_PIDFDVNODEPATHINFO, PROX_FDTYPE_VNODE};
+
/// Return the first process identifier matching a specified path, if one exists.
pub fn pid_of_path(find_path: impl AsRef<Path>) -> Option<pid_t> {
match list_pids() {
@@ -65,3 +72,89 @@ pub fn process_path(pid: pid_t) -> io::Result<PathBuf> {
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid process path"))?,
))
}
+
+/// Return file descriptors associated with `pid`
+// reference: lsof source code: https://github.com/apple-oss-distributions/lsof/blob/c48c28f51e82a5d682a4459bdbdc42face73468f/lsof/dialects/darwin/libproc/dproc.c#L623
+pub fn process_file_descriptors(pid: pid_t) -> io::Result<Vec<proc_fdinfo>> {
+ // SAFETY: Passing nil arguments is safe and returns the required buffer size for the given pid
+ let fds_buf_size = unsafe { proc_pidinfo(pid, PROC_PIDLISTFDS, 0, ptr::null_mut(), 0) };
+
+ if fds_buf_size < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ let fds_num = fds_buf_size as usize / std::mem::size_of::<proc_fdinfo>();
+
+ // SAFETY: This is a pure C struct which we're expected to zero-initialize
+ let empty_fdinfo = unsafe { std::mem::zeroed::<proc_fdinfo>() };
+
+ let mut file_desc_buf = vec![empty_fdinfo; fds_num as usize];
+
+ // SAFETY: fds_buf is large enough to contain `fds_num`
+ let fds_buf_size = unsafe {
+ proc_pidinfo(
+ pid,
+ PROC_PIDLISTFDS,
+ 0,
+ file_desc_buf.as_mut_ptr() as _,
+ fds_buf_size as c_int,
+ )
+ };
+ if fds_buf_size < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ // Truncate file descriptor vector based on new count
+ let new_fds_num = fds_buf_size as usize / std::mem::size_of::<proc_fdinfo>();
+ assert!(new_fds_num <= fds_num);
+ file_desc_buf.truncate(new_fds_num);
+
+ Ok(file_desc_buf)
+}
+
+/// Return the file path that belongs to a vnode file descriptor type for a given process.
+pub fn get_file_desc_vnode_path(pid: pid_t, info: &proc_fdinfo) -> io::Result<CString> {
+ assert!(info.proc_fdtype == PROX_FDTYPE_VNODE as _);
+
+ // SAFETY: This is a pure C struct which we're expected to zero-initialize
+ let mut vnode: vnode_fdinfowithpath = unsafe { std::mem::zeroed() };
+
+ // SAFETY: Our buffer is initialized, aligned, and large enough to contain the result.
+ let err = unsafe {
+ proc_pidfdinfo(
+ pid,
+ info.proc_fd,
+ PROC_PIDFDVNODEPATHINFO as _,
+ &mut vnode as *mut _ as _,
+ std::mem::size_of_val(&vnode) as _,
+ )
+ };
+ if err <= 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ // SAFETY: `proc_pidfdinfo` returned a null-terminated path here
+ let cstr_path = unsafe { CStr::from_ptr(vnode.pvip.vip_path.as_ptr()) };
+ Ok(cstr_path.to_owned())
+}
+
+/// Return the 'proc_bsdinfo' associated with a given process identifier
+pub fn process_bsdinfo(pid: pid_t) -> io::Result<proc_bsdinfo> {
+ // SAFETY: This is a pure C struct which we're expected to zero-initialize
+ let mut info: proc_bsdinfo = unsafe { std::mem::zeroed() };
+
+ // SAFETY: Our buffer (info) is initialized, aligned, and large enough to contain the result.
+ let err = unsafe {
+ proc_pidinfo(
+ pid,
+ PROC_PIDTBSDINFO as _,
+ 0,
+ &mut info as *mut proc_bsdinfo as *mut c_void,
+ std::mem::size_of_val(&info) as _,
+ )
+ };
+ if err <= 0 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(info)
+}