spawn_task_port/
lib.rs

1extern crate mach;
2extern crate uuid;
3
4// re-export this for convenience.
5pub use mach::port::mach_port_t;
6
7use std::ffi::CString;
8use std::io::{Error, ErrorKind, Result};
9use std::mem;
10use std::ops::Drop;
11use std::os::unix::process::CommandExt;
12use std::process::{Command, Child};
13
14use mach::bootstrap::bootstrap_look_up;
15use mach::kern_return::{kern_return_t, KERN_SUCCESS};
16use mach::port::{MACH_PORT_NULL, MACH_PORT_RIGHT_RECEIVE};
17use mach::mach_port::{mach_port_allocate, mach_port_deallocate, mach_port_insert_right};
18use mach::message::{MACH_MSG_TYPE_MAKE_SEND, MACH_MSGH_BITS, MACH_MSG_TYPE_COPY_SEND,
19                    MACH_MSGH_BITS_COMPLEX, MACH_RCV_MSG, MACH_MSG_TIMEOUT_NONE, mach_msg_send,
20                    mach_msg, mach_msg_header_t, mach_msg_body_t, mach_msg_port_descriptor_t,
21                    mach_msg_trailer_t};
22use mach::task::{TASK_BOOTSTRAP_PORT, task_get_special_port};
23use mach::traps::mach_task_self;
24use uuid::Uuid;
25
26/// A macro to wrap mach APIs that return `kern_return_t` to early-return
27/// a `std::io::Result` when they fail.
28macro_rules! ktry {
29    ($e:expr) => {{
30        let kr = $e;
31        if kr != KERN_SUCCESS {
32            return Err(Error::new(ErrorKind::Other,
33                                  format!("`{}` failed with return code {:x}",
34                                          stringify!($e), kr)));
35        }
36    }}
37}
38
39/// A wrapper for a `mach_port_t` to deallocate the port on drop.
40struct MachPort(mach_port_t);
41
42impl Drop for MachPort {
43    fn drop(&mut self) {
44        // Ignore failures, there's not much that can be done here.
45        unsafe {
46            mach_port_deallocate(mach_task_self(), self.0);
47        }
48    }
49}
50
51/// The message format that the child sends to the parent.
52#[allow(dead_code)]
53struct SendMessage {
54    header: mach_msg_header_t,
55    body: mach_msg_body_t,
56    task_port: mach_msg_port_descriptor_t,
57}
58
59/// The message format that the parent receives from the child.
60#[allow(dead_code)]
61struct RecvMessage {
62    header: mach_msg_header_t,
63    body: mach_msg_body_t,
64    task_port: mach_msg_port_descriptor_t,
65    //TODO: make this a mach_msg_audit_trailer_t so we can audit the child PID
66    trailer: mach_msg_trailer_t,
67}
68
69extern "C" {
70    /// This is not a public API, but it's what everything uses internally.
71    fn bootstrap_register2(bp: mach_port_t,
72                           service_name: *const i8,
73                           sp: mach_port_t,
74                           flags: u64)
75                           -> kern_return_t;
76//TODO: use this for auditing
77//fn audit_token_to_pid(audit_token_t atoken) -> pid_t;
78}
79
80/// As OS X-specific extension to `std::process::Command` to spawn a process and
81/// get back access to its Mach task port.
82pub trait CommandSpawnWithTask {
83    /// Executes the command as a child process, returning both the `Child`
84    /// as well as the process' Mach task port as a `mach_port_t`.
85    fn spawn_get_task_port(&mut self) -> Result<(Child, mach_port_t)>;
86}
87
88impl CommandSpawnWithTask for Command {
89    fn spawn_get_task_port(&mut self) -> Result<(Child, mach_port_t)> {
90        // First, create a port to which the child can send us a message.
91        let port = unsafe {
92            let mut port: mach_port_t = mem::uninitialized();
93            ktry!(mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mut port));
94            let port = MachPort(port);
95
96            // Allocate a send right for the server port.
97            ktry!(mach_port_insert_right(mach_task_self(),
98                                         port.0,
99                                         port.0,
100                                         MACH_MSG_TYPE_MAKE_SEND));
101            port
102        };
103
104        // Register the port with the bootstrap server.
105        let uuid = Uuid::new_v4().simple().to_string();
106        let name = CString::new(uuid).or(Err(Error::new(ErrorKind::Other, "CString")))?;
107        unsafe {
108            let mut bootstrap_port = mem::uninitialized();
109            ktry!(task_get_special_port(mach_task_self(),
110                                        TASK_BOOTSTRAP_PORT,
111                                        &mut bootstrap_port));
112            ktry!(bootstrap_register2(bootstrap_port, name.as_ptr(), port.0, 0));
113        }
114
115        let child = self.before_exec(move || {
116                unsafe {
117                    // Next, in the child process' `before_exec`, look up the
118                    // registered port.
119                    let mut bootstrap_port: mach_port_t = mem::uninitialized();
120                    ktry!(task_get_special_port(mach_task_self(),
121                                                TASK_BOOTSTRAP_PORT,
122                                                &mut bootstrap_port));
123
124                    let mut parent_port: mach_port_t = mem::uninitialized();
125                    ktry!(bootstrap_look_up(bootstrap_port, name.as_ptr(), &mut parent_port));
126                    let parent_port = MachPort(parent_port);
127                    // Now use the port to send our task port to the parent.
128                    let mut msg = SendMessage {
129                        header: mach_msg_header_t {
130                            msgh_bits: MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0) |
131                                       MACH_MSGH_BITS_COMPLEX,
132                            msgh_size: mem::size_of::<SendMessage>() as u32,
133                            msgh_remote_port: parent_port.0,
134                            msgh_local_port: MACH_PORT_NULL,
135                            msgh_voucher_port: MACH_PORT_NULL,
136                            msgh_id: 0,
137                        },
138                        body: mach_msg_body_t { msgh_descriptor_count: 1 },
139                        task_port: mach_msg_port_descriptor_t::new(mach_task_self(),
140                                                                   MACH_MSG_TYPE_COPY_SEND),
141                    };
142                    ktry!(mach_msg_send(&mut msg.header));
143                }
144                Ok(())
145            })
146            .spawn()?;
147        // In the parent, receive the child's task port.
148        let child_task_port = unsafe {
149            let mut msg: RecvMessage = mem::uninitialized();
150            //TODO: MACH_RCV_MSG |
151            // MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) |
152            // MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT)
153            ktry!(mach_msg(&mut msg.header,
154                           MACH_RCV_MSG,
155                           0,
156                           mem::size_of::<RecvMessage>() as u32,
157                           port.0,
158                           MACH_MSG_TIMEOUT_NONE,
159                           MACH_PORT_NULL));
160            //TODO: Check that this message came from the child process
161            // with `audit_token_to_pid`.
162            msg.task_port.name
163        };
164        Ok((child, child_task_port))
165    }
166}