Skip to main content

fdio/
lib.rs

1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Bindings for the Zircon fdio library
6
7mod fdio_sys;
8
9mod spawn_builder;
10
11pub use spawn_builder::{Error as SpawnBuilderError, SpawnBuilder};
12
13use bitflags::bitflags;
14use fidl_fuchsia_io as fio;
15use std::convert::TryInto as _;
16use std::ffi::{CStr, CString, NulError};
17use std::fs::File;
18use std::marker::PhantomData;
19use std::mem::{self, MaybeUninit};
20use std::num::TryFromIntError;
21use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
22use std::os::raw;
23use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
24use std::str::Utf8Error;
25
26/// Connects a channel to a named service.
27pub fn service_connect(service_path: &str, channel: zx::Channel) -> Result<(), zx::Status> {
28    let service_path =
29        CString::new(service_path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
30    let service_path = service_path.as_ptr();
31
32    // The channel is always consumed.
33    let channel = channel.into_raw();
34    let status = unsafe { fdio_sys::fdio_service_connect(service_path, channel) };
35    zx::Status::ok(status)
36}
37
38/// Connects a channel to a named service relative to a directory `dir`.
39/// `dir` must be a directory protocol channel.
40pub fn service_connect_at(
41    dir: &zx::Channel,
42    service_path: &str,
43    channel: zx::Channel,
44) -> Result<(), zx::Status> {
45    let dir = dir.raw_handle();
46    let service_path =
47        CString::new(service_path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
48    let service_path = service_path.as_ptr();
49
50    // The channel is always consumed.
51    let channel = channel.into_raw();
52    let status = unsafe { fdio_sys::fdio_service_connect_at(dir, service_path, channel) };
53    zx::Status::ok(status)
54}
55
56/// Opens the remote object at the given `path` with the given `flags` asynchronously.
57/// ('asynchronous' here is referring to fuchsia.io.Directory.Open not having a return value).
58///
59/// Wraps fdio_open3.
60pub fn open(path: &str, flags: fio::Flags, channel: zx::Channel) -> Result<(), zx::Status> {
61    let path = CString::new(path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
62    let path = path.as_ptr();
63    let flags = flags.bits();
64
65    // The channel is always consumed.
66    let channel = channel.into_raw();
67    let status = unsafe { fdio_sys::fdio_open3(path, flags, channel) };
68    zx::Status::ok(status)
69}
70
71/// Opens the remote object at the given `path` relative to the given `dir` with the given `flags`
72/// asynchronously. ('asynchronous' here is referring to fuchsia.io.Directory.Open not having a
73/// return value).
74///
75/// `dir` must be a directory protocol channel.
76///
77/// Wraps fdio_open3_at.
78pub fn open_at(
79    dir: &zx::Channel,
80    path: &str,
81    flags: fio::Flags,
82    channel: zx::Channel,
83) -> Result<(), zx::Status> {
84    let dir = dir.raw_handle();
85    let path = CString::new(path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
86    let path = path.as_ptr();
87    let flags = flags.bits();
88
89    // The channel is always consumed.
90    let channel = channel.into_raw();
91    let status = unsafe { fdio_sys::fdio_open3_at(dir, path, flags, channel) };
92    zx::Status::ok(status)
93}
94
95/// Opens the remote object at the given `path` with the given `flags` synchronously, and on
96/// success, binds that channel to a file descriptor and returns it.
97///
98/// Wraps fdio_open3_fd.
99pub fn open_fd(path: &str, flags: fio::Flags) -> Result<File, zx::Status> {
100    let path = CString::new(path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
101    let path = path.as_ptr();
102    let flags = flags.bits();
103
104    // file descriptors are always positive; we expect fdio to initialize this to a legal value.
105    let mut fd = MaybeUninit::new(-1);
106    let status = {
107        let fd = fd.as_mut_ptr();
108        unsafe { fdio_sys::fdio_open3_fd(path, flags, fd) }
109    };
110    let () = zx::Status::ok(status)?;
111    let fd = unsafe { fd.assume_init() };
112    debug_assert!(fd >= 0, "{} >= 0", fd);
113    let f = unsafe { File::from_raw_fd(fd) };
114    Ok(f)
115}
116
117/// Opens the remote object at the given `path` relative to the given `dir` with the given `flags`
118/// synchronously, and on success, binds that channel to a file descriptor and returns it.
119///
120/// `dir` must be backed by a directory protocol channel (even though it is
121/// wrapped in a std::fs::File).
122///
123/// Wraps fdio_open3_fd_at.
124pub fn open_fd_at(dir: &File, path: &str, flags: fio::Flags) -> Result<File, zx::Status> {
125    let dir = dir.as_raw_fd();
126    let path = CString::new(path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
127    let path = path.as_ptr();
128    let flags = flags.bits();
129
130    // file descriptors are always positive; we expect fdio to initialize this to a legal value.
131    let mut fd = MaybeUninit::new(-1);
132    let status = {
133        let fd = fd.as_mut_ptr();
134        unsafe { fdio_sys::fdio_open3_fd_at(dir, path, flags, fd) }
135    };
136    let () = zx::Status::ok(status)?;
137    let fd = unsafe { fd.assume_init() };
138    debug_assert!(fd >= 0, "{} >= 0", fd);
139    let f = unsafe { File::from_raw_fd(fd) };
140    Ok(f)
141}
142
143/// Clones an object's underlying handle.
144pub fn clone_fd(f: impl AsFd) -> Result<zx::NullableHandle, zx::Status> {
145    clone_fd_inner(f.as_fd())
146}
147
148fn clone_fd_inner<'a>(fd: BorrowedFd<'a>) -> Result<zx::NullableHandle, zx::Status> {
149    // we expect fdio to initialize this to a legal value.
150    let mut handle = MaybeUninit::new(zx::NullableHandle::invalid().raw_handle());
151    let status = {
152        let handle = handle.as_mut_ptr();
153        unsafe { fdio_sys::fdio_fd_clone(fd.as_raw_fd(), handle) }
154    };
155    let () = zx::Status::ok(status)?;
156    let handle = unsafe { handle.assume_init() };
157    let handle = unsafe { zx::NullableHandle::from_raw(handle) };
158    debug_assert!(!handle.is_invalid(), "({:?}).is_invalid()", handle);
159    Ok(handle)
160}
161
162/// Removes an object from the file descriptor table and returns its underlying handle.
163pub fn transfer_fd(f: impl Into<OwnedFd>) -> Result<zx::NullableHandle, zx::Status> {
164    transfer_fd_inner(f.into())
165}
166
167fn transfer_fd_inner(fd: OwnedFd) -> Result<zx::NullableHandle, zx::Status> {
168    let fd = fd.into_raw_fd();
169    // we expect fdio to initialize this to a legal value.
170    let mut handle = MaybeUninit::new(zx::NullableHandle::invalid().raw_handle());
171    let status = {
172        let handle = handle.as_mut_ptr();
173        unsafe { fdio_sys::fdio_fd_transfer(fd.as_raw_fd(), handle) }
174    };
175    let () = zx::Status::ok(status)?;
176    let handle = unsafe { handle.assume_init() };
177    let handle = unsafe { zx::NullableHandle::from_raw(handle) };
178    debug_assert!(!handle.is_invalid(), "({:?}).is_invalid()", handle);
179    Ok(handle)
180}
181
182/// Create an object from a handle.
183///
184/// Afterward, the handle is owned by fdio, and will close with `OwnedFd`.
185/// See `transfer_fd` for a way to get it back.
186pub fn create_fd(handle: zx::NullableHandle) -> Result<OwnedFd, zx::Status> {
187    let handle = handle.into_raw();
188    // file descriptors are always positive; we expect fdio to initialize this to a legal value.
189    let mut fd = MaybeUninit::new(-1);
190    let status = {
191        let fd = fd.as_mut_ptr();
192        unsafe { fdio_sys::fdio_fd_create(handle, fd) }
193    };
194    let () = zx::Status::ok(status)?;
195    let fd = unsafe { fd.assume_init() };
196    debug_assert!(fd >= 0, "{} >= 0", fd);
197    // Safety: The handle is now owned by fdio, so it does not require any other cleanup.
198    let f = unsafe { OwnedFd::from_raw_fd(fd) };
199    Ok(f)
200}
201
202/// Create a null file descriptor.
203///
204/// The file descriptor behaves like `/dev/null`.
205pub fn create_fd_null() -> Option<OwnedFd> {
206    let fd = unsafe { fdio_sys::fdio_fd_create_null() };
207    if fd < 0 {
208        return None;
209    }
210    // Safety: The fd is new and owned by fdio.
211    let f = unsafe { OwnedFd::from_raw_fd(fd) };
212    Some(f)
213}
214
215/// Bind a handle to a specific file descriptor.
216///
217/// Afterward, the handle is owned by fdio, and will close when the file descriptor is closed.
218/// See `transfer_fd` for a way to get it back.
219pub fn bind_to_fd(handle: zx::NullableHandle, fd: RawFd) -> Result<(), zx::Status> {
220    if fd < 0 {
221        // fdio_bind_to_fd supports finding the next available fd when provided with a negative
222        // number, but due to lack of use-cases for this in Rust this is currently unsupported by
223        // this function.
224        return Err(zx::Status::INVALID_ARGS);
225    }
226
227    // The handle is always consumed.
228    let handle = handle.into_raw();
229    // we expect fdio to initialize this to a legal value.
230    let mut fdio = MaybeUninit::new(std::ptr::null_mut());
231
232    let status = unsafe { fdio_sys::fdio_create(handle, fdio.as_mut_ptr()) };
233    let () = zx::Status::ok(status)?;
234    let fdio = unsafe { fdio.assume_init() };
235    debug_assert_ne!(fdio, std::ptr::null_mut());
236    // The fdio object is always consumed.
237    let bound_fd = unsafe { fdio_sys::fdio_bind_to_fd(fdio, fd, 0) };
238    if bound_fd < 0 {
239        return Err(zx::Status::BAD_STATE);
240    }
241    // We requested a specific fd, we expect to have gotten it, or failed.
242    assert_eq!(bound_fd, fd);
243    Ok(())
244}
245
246/// Clones an object's underlying handle and checks that it is a channel.
247pub fn clone_channel(f: impl AsFd) -> Result<zx::Channel, zx::Status> {
248    clone_channel_inner(f.as_fd())
249}
250
251fn clone_channel_inner<'a>(fd: BorrowedFd<'a>) -> Result<zx::Channel, zx::Status> {
252    let handle = clone_fd(fd)?;
253    let zx::HandleBasicInfo { object_type, .. } = handle.basic_info()?;
254    if object_type == zx::ObjectType::CHANNEL {
255        Ok(handle.into())
256    } else {
257        Err(zx::Status::WRONG_TYPE)
258    }
259}
260
261/// Creates a named pipe and returns one end as a zx::Socket.
262pub fn pipe_half() -> Result<(File, zx::Socket), zx::Status> {
263    // file descriptors are always positive; we expect fdio to initialize this to a legal value.
264    let mut fd = MaybeUninit::new(-1);
265    // we expect fdio to initialize this to a legal value.
266    let mut handle = MaybeUninit::new(zx::NullableHandle::invalid().raw_handle());
267    let status = {
268        let fd = fd.as_mut_ptr();
269        let handle = handle.as_mut_ptr();
270        unsafe { fdio_sys::fdio_pipe_half(fd, handle) }
271    };
272    let () = zx::Status::ok(status)?;
273    let fd = unsafe { fd.assume_init() };
274    debug_assert!(fd >= 0, "{} >= 0", fd);
275    let f = unsafe { File::from_raw_fd(fd) };
276    let handle = unsafe { handle.assume_init() };
277    let handle = unsafe { zx::NullableHandle::from_raw(handle) };
278    debug_assert!(!handle.is_invalid(), "({:?}).is_invalid()", handle);
279    Ok((f, zx::Socket::from(handle)))
280}
281
282/// Creates a transferrable object and returns one end as a zx::Channel.
283pub fn create_transferrable() -> Result<(File, zx::Channel), zx::Status> {
284    // We expect fdio to initialize this to a legal value.
285    let mut fd = MaybeUninit::new(-1);
286    // We expect fdio to initialize this to a legal value.
287    let mut handle = MaybeUninit::new(zx::NullableHandle::invalid().raw_handle());
288    let status: i32 =
289        { unsafe { fdio_sys::fdio_transferable_fd(fd.as_mut_ptr(), handle.as_mut_ptr()) } };
290    zx::Status::ok(status)?;
291
292    let fd = unsafe { fd.assume_init() };
293    debug_assert!(fd >= 0, "{} >= 0", fd);
294    let file = unsafe { File::from_raw_fd(fd) };
295    let handle = unsafe { handle.assume_init() };
296    let handle = unsafe { zx::NullableHandle::from_raw(handle) };
297    debug_assert!(!handle.is_invalid(), "({:?}).is_invalid()", handle);
298    Ok((file, zx::Channel::from(handle)))
299}
300
301bitflags! {
302    /// Options to allow some or all of the environment of the running process
303    /// to be shared with the process being spawned.
304    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
305    pub struct SpawnOptions: u32 {
306        /// Provide the spawned process with the job in which the process was created.
307        ///
308        /// The job will be available to the new process as the PA_JOB_DEFAULT argument
309        /// (exposed in Rust as `fuchsia_runtim::job_default()`).
310        const CLONE_JOB = fdio_sys::FDIO_SPAWN_CLONE_JOB;
311
312        /// Provide the spawned process with the shared library loader via the
313        /// PA_LDSVC_LOADER argument.
314        const DEFAULT_LOADER = fdio_sys::FDIO_SPAWN_DEFAULT_LDSVC;
315
316        /// Clones the filesystem namespace into the spawned process.
317        const CLONE_NAMESPACE = fdio_sys::FDIO_SPAWN_CLONE_NAMESPACE;
318
319        /// Clones file descriptors 0, 1, and 2 into the spawned process.
320        ///
321        /// Skips any of these file descriptors that are closed without
322        /// generating an error.
323        const CLONE_STDIO = fdio_sys::FDIO_SPAWN_CLONE_STDIO;
324
325        /// Clones the environment into the spawned process.
326        const CLONE_ENVIRONMENT = fdio_sys::FDIO_SPAWN_CLONE_ENVIRON;
327
328        /// Clones the namespace, stdio, and environment into the spawned process.
329        const CLONE_ALL = fdio_sys::FDIO_SPAWN_CLONE_ALL;
330    }
331}
332
333// TODO: someday we'll have custom DSTs which will make this unnecessary.
334fn nul_term_from_slice(argv: &[&CStr]) -> Vec<*const raw::c_char> {
335    argv.iter().map(|cstr| cstr.as_ptr()).chain(std::iter::once(std::ptr::null())).collect()
336}
337
338/// Spawn a process in the given `job`.
339pub fn spawn(
340    job: &zx::Job,
341    options: SpawnOptions,
342    path: &CStr,
343    argv: &[&CStr],
344) -> Result<zx::Process, zx::Status> {
345    let job = job.raw_handle();
346    let flags = options.bits();
347    let path = path.as_ptr();
348    let argv = nul_term_from_slice(argv);
349    // we expect fdio to initialize this to a legal value.
350    let mut process = MaybeUninit::new(zx::NullableHandle::invalid().raw_handle());
351
352    // Safety: spawn consumes no handles and frees no pointers, and only
353    // produces a valid process upon success.
354    let status = {
355        let argv = argv.as_ptr();
356        let process = process.as_mut_ptr();
357        unsafe { fdio_sys::fdio_spawn(job, flags, path, argv, process) }
358    };
359    let () = zx::Status::ok(status)?;
360    let process = unsafe { process.assume_init() };
361    let process = unsafe { zx::NullableHandle::from_raw(process) };
362    debug_assert!(!process.is_invalid(), "({:?}).is_invalid()", process);
363    Ok(zx::Process::from(process))
364}
365
366/// An action to take in `spawn_etc`.
367#[repr(transparent)]
368pub struct SpawnAction<'a>(fdio_sys::fdio_spawn_action_t, PhantomData<&'a ()>);
369
370// TODO(https://github.com/rust-lang/rust-bindgen/issues/2000): bindgen
371// generates really bad names for these.
372mod fdio_spawn_action {
373    #![allow(dead_code)]
374    #![allow(non_camel_case_types)]
375
376    pub(super) type action_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1;
377    pub(super) type fd_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1__bindgen_ty_1;
378    pub(super) type ns_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1__bindgen_ty_2;
379    pub(super) type h_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1__bindgen_ty_3;
380    pub(super) type name_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1__bindgen_ty_4;
381    pub(super) type dir_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1__bindgen_ty_5;
382}
383
384impl<'a> SpawnAction<'a> {
385    pub const USE_FOR_STDIO: i32 = fdio_sys::FDIO_FLAG_USE_FOR_STDIO as i32;
386
387    /// Clone a file descriptor into the new process.
388    ///
389    /// `local_fd`: File descriptor within the current process.
390    /// `target_fd`: File descriptor within the new process that will receive the clone.
391    pub fn clone_fd(local_fd: BorrowedFd<'a>, target_fd: i32) -> Self {
392        let local_fd = local_fd.as_fd();
393        // Safety: `local_fd` is a valid file descriptor so long as we're inside the
394        // 'a lifetime.
395        Self(
396            fdio_sys::fdio_spawn_action_t {
397                action: fdio_sys::FDIO_SPAWN_ACTION_CLONE_FD,
398                __bindgen_anon_1: fdio_spawn_action::action_t {
399                    fd: fdio_spawn_action::fd_t { local_fd: local_fd.as_raw_fd(), target_fd },
400                },
401            },
402            PhantomData,
403        )
404    }
405
406    /// Transfer a file descriptor into the new process.
407    ///
408    /// `local_fd`: File descriptor within the current process.
409    /// `target_fd`: File descriptor within the new process that will receive the transfer.
410    pub fn transfer_fd(local_fd: OwnedFd, target_fd: i32) -> Self {
411        // Safety: ownership of `local_fd` is consumed, so `Self` can live arbitrarily long.
412        // When the action is executed, the fd will be transferred.
413        Self(
414            fdio_sys::fdio_spawn_action_t {
415                action: fdio_sys::FDIO_SPAWN_ACTION_TRANSFER_FD,
416                __bindgen_anon_1: fdio_spawn_action::action_t {
417                    fd: fdio_spawn_action::fd_t { local_fd: local_fd.into_raw_fd(), target_fd },
418                },
419            },
420            PhantomData,
421        )
422    }
423
424    /// Add the given entry to the namespace of the spawned process.
425    ///
426    /// If `SpawnOptions::CLONE_NAMESPACE` is set, the namespace entry is added
427    /// to the cloned namespace from the calling process.
428    pub fn add_namespace_entry(prefix: &'a CStr, handle: zx::NullableHandle) -> Self {
429        // Safety: ownership of the `handle` is consumed.
430        // The prefix string must stay valid through the 'a lifetime.
431        Self(
432            fdio_sys::fdio_spawn_action_t {
433                action: fdio_sys::FDIO_SPAWN_ACTION_ADD_NS_ENTRY,
434                __bindgen_anon_1: fdio_spawn_action::action_t {
435                    ns: fdio_spawn_action::ns_t {
436                        prefix: prefix.as_ptr(),
437                        handle: handle.into_raw(),
438                    },
439                },
440            },
441            PhantomData,
442        )
443    }
444
445    /// Add the given handle to the process arguments of the spawned process.
446    pub fn add_handle(kind: fuchsia_runtime::HandleInfo, handle: zx::NullableHandle) -> Self {
447        // Safety: ownership of the `handle` is consumed.
448        // The prefix string must stay valid through the 'a lifetime.
449        Self(
450            fdio_sys::fdio_spawn_action_t {
451                action: fdio_sys::FDIO_SPAWN_ACTION_ADD_HANDLE,
452                __bindgen_anon_1: fdio_spawn_action::action_t {
453                    h: fdio_spawn_action::h_t { id: kind.as_raw(), handle: handle.into_raw() },
454                },
455            },
456            PhantomData,
457        )
458    }
459
460    /// Sets the name of the spawned process to the given name.
461    pub fn set_name(name: &'a CStr) -> Self {
462        // Safety: the `name` pointer must be valid at least as long as `Self`.
463        Self(
464            fdio_sys::fdio_spawn_action_t {
465                action: fdio_sys::FDIO_SPAWN_ACTION_SET_NAME,
466                __bindgen_anon_1: fdio_spawn_action::action_t {
467                    name: fdio_spawn_action::name_t { data: name.as_ptr() },
468                },
469            },
470            PhantomData,
471        )
472    }
473
474    fn is_null(&self) -> bool {
475        let Self(fdio_sys::fdio_spawn_action_t { action, __bindgen_anon_1: _ }, PhantomData) = self;
476        *action == 0
477    }
478
479    /// Nullifies the action to prevent the inner contents from being dropped.
480    fn nullify(&mut self) {
481        // Assert that our null value doesn't conflict with any "real" actions.
482        debug_assert!(
483            (fdio_sys::FDIO_SPAWN_ACTION_CLONE_FD != 0)
484                && (fdio_sys::FDIO_SPAWN_ACTION_TRANSFER_FD != 0)
485                && (fdio_sys::FDIO_SPAWN_ACTION_ADD_NS_ENTRY != 0)
486                && (fdio_sys::FDIO_SPAWN_ACTION_ADD_HANDLE != 0)
487                && (fdio_sys::FDIO_SPAWN_ACTION_SET_NAME != 0)
488        );
489        let Self(fdio_sys::fdio_spawn_action_t { action, __bindgen_anon_1: _ }, PhantomData) = self;
490        *action = 0;
491    }
492}
493
494const ERR_MSG_MAX_LENGTH: usize = fdio_sys::FDIO_SPAWN_ERR_MSG_MAX_LENGTH as usize;
495
496fn spawn_with_actions(
497    job: &zx::Job,
498    options: SpawnOptions,
499    argv: &[&CStr],
500    environ: Option<&[&CStr]>,
501    actions: &mut [SpawnAction<'_>],
502    spawn_fn: impl FnOnce(
503        zx::sys::zx_handle_t,                 // job
504        u32,                                  // flags
505        *const *const raw::c_char,            // argv
506        *const *const raw::c_char,            // environ
507        usize,                                // action_count
508        *const fdio_sys::fdio_spawn_action_t, // actions
509        *mut zx::sys::zx_handle_t,            // process_out,
510        *mut raw::c_char,                     // err_msg_out
511    ) -> zx::sys::zx_status_t,
512) -> Result<zx::Process, (zx::Status, String)> {
513    let job = job.raw_handle();
514    let flags = options.bits();
515    let argv = nul_term_from_slice(argv);
516    let environ = environ.map(nul_term_from_slice);
517
518    if actions.iter().any(SpawnAction::is_null) {
519        return Err((zx::Status::INVALID_ARGS, "null SpawnAction".to_string()));
520    }
521
522    // we expect fdio to initialize this to a legal value.
523    let mut process = MaybeUninit::new(zx::NullableHandle::invalid().raw_handle());
524    let mut err_msg = MaybeUninit::new([0; ERR_MSG_MAX_LENGTH]);
525
526    let status = {
527        let environ = environ.as_ref().map_or_else(std::ptr::null, Vec::as_ptr);
528        spawn_fn(
529            job,
530            flags,
531            argv.as_ptr(),
532            environ,
533            actions.len(),
534            actions.as_ptr() as _,
535            process.as_mut_ptr(),
536            err_msg.as_mut_ptr() as _,
537        )
538    };
539
540    // Statically verify this hasn't been moved out of during the call above;
541    // raw pointers escape the borrow checker.
542    std::mem::drop(environ);
543
544    zx::Status::ok(status).map_err(|status| {
545        let err_msg = unsafe { err_msg.assume_init() };
546        let err_msg = unsafe { CStr::from_ptr(err_msg.as_ptr()) };
547        let err_msg = err_msg.to_string_lossy().into_owned();
548        (status, err_msg)
549    })?;
550
551    // Clear out the actions so we can't unsafely re-use them in a future call.
552    actions.iter_mut().for_each(SpawnAction::nullify);
553
554    let process = unsafe { process.assume_init() };
555    let process = unsafe { zx::NullableHandle::from_raw(process) };
556    debug_assert!(!process.is_invalid(), "({:?}).is_invalid()", process);
557    Ok(zx::Process::from(process))
558}
559
560/// Spawn a process in the given `job` using a series of `SpawnAction`s.
561/// All `SpawnAction`s are nullified after their use in this function.
562pub fn spawn_etc(
563    job: &zx::Job,
564    options: SpawnOptions,
565    path: &CStr,
566    argv: &[&CStr],
567    environ: Option<&[&CStr]>,
568    actions: &mut [SpawnAction<'_>],
569) -> Result<zx::Process, (zx::Status, String)> {
570    let path = path.as_ptr();
571    spawn_with_actions(
572        job,
573        options,
574        argv,
575        environ,
576        actions,
577        |job, flags, argv, environ, action_count, actions_ptr, process_out, err_msg_out| unsafe {
578            fdio_sys::fdio_spawn_etc(
579                job,
580                flags,
581                path,
582                argv,
583                environ,
584                action_count,
585                actions_ptr,
586                process_out,
587                err_msg_out,
588            )
589        },
590    )
591}
592
593/// Spawn a process in the given job using an executable VMO.
594pub fn spawn_vmo(
595    job: &zx::Job,
596    options: SpawnOptions,
597    executable_vmo: zx::Vmo,
598    argv: &[&CStr],
599    environ: Option<&[&CStr]>,
600    actions: &mut [SpawnAction<'_>],
601) -> Result<zx::Process, (zx::Status, String)> {
602    let executable_vmo = executable_vmo.into_raw();
603    spawn_with_actions(
604        job,
605        options,
606        argv,
607        environ,
608        actions,
609        |job, flags, argv, environ, action_count, actions_ptr, process_out, err_msg_out| unsafe {
610            fdio_sys::fdio_spawn_vmo(
611                job,
612                flags,
613                executable_vmo,
614                argv,
615                environ,
616                action_count,
617                actions_ptr,
618                process_out,
619                err_msg_out,
620            )
621        },
622    )
623}
624
625/// Gets a read-only VMO containing the whole contents of the file. This function
626/// creates a clone of the underlying VMO when possible, falling back to eagerly
627/// reading the contents into a freshly-created VMO.
628pub fn get_vmo_copy_from_file(f: &File) -> Result<zx::Vmo, zx::Status> {
629    let fd = f.as_raw_fd();
630    // we expect fdio to initialize this to a legal value.
631    let mut vmo = MaybeUninit::new(zx::NullableHandle::invalid().raw_handle());
632    let status = {
633        let vmo = vmo.as_mut_ptr();
634        unsafe { fdio_sys::fdio_get_vmo_copy(fd, vmo) }
635    };
636    let () = zx::Status::ok(status)?;
637    let vmo = unsafe { vmo.assume_init() };
638    let vmo = unsafe { zx::NullableHandle::from_raw(vmo) };
639    debug_assert!(!vmo.is_invalid(), "({:?}).is_invalid()", vmo);
640    Ok(zx::Vmo::from(vmo))
641}
642
643/// Gets a read-exec VMO containing the whole contents of the file.
644pub fn get_vmo_exec_from_file(f: &File) -> Result<zx::Vmo, zx::Status> {
645    let fd = f.as_raw_fd();
646    // we expect fdio to initialize this to a legal value.
647    let mut vmo = MaybeUninit::new(zx::NullableHandle::invalid().raw_handle());
648    let status = {
649        let vmo = vmo.as_mut_ptr();
650        unsafe { fdio_sys::fdio_get_vmo_exec(fd, vmo) }
651    };
652    zx::Status::ok(status)?;
653    let vmo = unsafe { zx::NullableHandle::from_raw(vmo.assume_init()) };
654    debug_assert!(!vmo.is_invalid(), "({:?}).is_invalid()", vmo);
655    Ok(zx::Vmo::from(vmo))
656}
657
658/// Get a read-only handle to the exact VMO used by the file system server to represent the file.
659/// This VMO will track size and content changes to the file.
660pub fn get_vmo_exact_from_file(f: &File) -> Result<zx::Vmo, zx::Status> {
661    let fd = f.as_raw_fd();
662    // we expect fdio to initialize this to a legal value.
663    let mut vmo = MaybeUninit::new(zx::NullableHandle::invalid().raw_handle());
664    let status = {
665        let vmo = vmo.as_mut_ptr();
666        unsafe { fdio_sys::fdio_get_vmo_exact(fd, vmo) }
667    };
668    let () = zx::Status::ok(status)?;
669    let vmo = unsafe { vmo.assume_init() };
670    let vmo = unsafe { zx::NullableHandle::from_raw(vmo) };
671    debug_assert!(!vmo.is_invalid(), "({:?}).is_invalid()", vmo);
672    Ok(zx::Vmo::from(vmo))
673}
674
675pub struct Namespace {
676    ns: *mut fdio_sys::fdio_ns_t,
677}
678
679impl Namespace {
680    /// Get the currently installed namespace.
681    pub fn installed() -> Result<Self, zx::Status> {
682        // we expect fdio to initialize this to a legal value.
683        let mut ns = std::ptr::null_mut();
684        let status = { unsafe { fdio_sys::fdio_ns_get_installed(&mut ns) } };
685        zx::Status::ok(status)?;
686        debug_assert_ne!(ns, std::ptr::null_mut());
687        Ok(Namespace { ns })
688    }
689
690    /// Open an object at |path| relative to the root of this namespace with |flags|.
691    ///
692    /// |path| must be absolute.
693    ///
694    /// This corresponds with fdio_ns_open3 in C.
695    pub fn open(
696        &self,
697        path: &str,
698        flags: fio::Flags,
699        channel: zx::Channel,
700    ) -> Result<(), zx::Status> {
701        let &Self { ns } = self;
702        let path = CString::new(path).map_err(|_| zx::Status::INVALID_ARGS)?;
703        let path = path.as_ptr();
704        let flags = flags.bits();
705
706        // The channel is always consumed.
707        let channel = channel.into_raw();
708        let status = unsafe { fdio_sys::fdio_ns_open3(ns, path, flags, channel) };
709        zx::Status::ok(status)
710    }
711
712    /// Create a new directory within the namespace, bound to the provided
713    /// directory-protocol-compatible channel. The path must be an absolute path, like "/x/y/z",
714    /// containing no "." nor ".." entries. It is relative to the root of the namespace.
715    ///
716    /// This corresponds with fdio_ns_bind in C.
717    pub fn bind(
718        &self,
719        path: &str,
720        channel: fidl::endpoints::ClientEnd<fio::DirectoryMarker>,
721    ) -> Result<(), zx::Status> {
722        let &Self { ns } = self;
723        let path = CString::new(path).map_err(|_| zx::Status::INVALID_ARGS)?;
724        let path = path.as_ptr();
725
726        // The channel is always consumed.
727        let channel = channel.into_channel().into_raw();
728        let status = unsafe { fdio_sys::fdio_ns_bind(ns, path, channel) };
729        zx::Status::ok(status)
730    }
731
732    /// Unbind the channel at path, closing the associated handle when all references to the node go
733    /// out of scope. The path must be an absolute path, like "/x/y/z", containing no "." nor ".."
734    /// entries. It is relative to the root of the namespace.
735    ///
736    /// This corresponds with fdio_ns_unbind in C.
737    pub fn unbind(&self, path: &str) -> Result<(), zx::Status> {
738        let &Self { ns } = self;
739        let path = CString::new(path).map_err(|_| zx::Status::INVALID_ARGS)?;
740        let path = path.as_ptr();
741        let status = unsafe { fdio_sys::fdio_ns_unbind(ns, path) };
742        zx::Status::ok(status)
743    }
744
745    // Export this namespace, by returning a flat representation of it. The
746    // handles returned are clones of the handles within the namespace.
747    pub fn export(&self) -> Result<Vec<NamespaceEntry>, zx::Status> {
748        let mut flat: *mut fdio_sys::fdio_flat_namespace_t = std::ptr::null_mut();
749
750        unsafe {
751            zx::Status::ok(fdio_sys::fdio_ns_export(self.ns, &mut flat))?;
752            let entries = Self::export_entries(flat);
753            fdio_sys::fdio_ns_free_flat_ns(flat);
754            entries
755        }
756    }
757
758    unsafe fn export_entries(
759        flat: *mut fdio_sys::fdio_flat_namespace_t,
760    ) -> Result<Vec<NamespaceEntry>, zx::Status> {
761        let fdio_sys::fdio_flat_namespace_t { count, handle, path } = unsafe { *flat };
762        let len: isize = count.try_into().map_err(|_: TryFromIntError| zx::Status::INVALID_ARGS)?;
763        let mut entries = Vec::with_capacity(count);
764        for i in 0..len {
765            // Explicitly take ownership of the handle, and invalidate the source.
766            let handle = unsafe {
767                zx::NullableHandle::from_raw(mem::replace(
768                    &mut *handle.offset(i),
769                    zx::sys::ZX_HANDLE_INVALID,
770                ))
771            };
772            entries.push(NamespaceEntry {
773                handle,
774                path: unsafe { CStr::from_ptr(*path.offset(i)) }
775                    .to_str()
776                    .map_err(|_: Utf8Error| zx::Status::INVALID_ARGS)?
777                    .to_owned(),
778            });
779        }
780        Ok(entries)
781    }
782
783    pub fn into_raw(self) -> *mut fdio_sys::fdio_ns_t {
784        let Self { ns } = self;
785        ns
786    }
787}
788
789/// Entry in a flat representation of a namespace.
790#[derive(Debug)]
791pub struct NamespaceEntry {
792    pub handle: zx::NullableHandle,
793    pub path: String,
794}
795
796#[cfg(test)]
797mod tests {
798    use super::*;
799    use assert_matches::assert_matches;
800    use zx::{MonotonicInstant, Signals, Status, object_wait_many};
801
802    #[test]
803    fn namespace_get_installed() {
804        let namespace = Namespace::installed().expect("failed to get installed namespace");
805        assert!(!namespace.into_raw().is_null());
806    }
807
808    #[test]
809    fn namespace_bind_open_unbind() {
810        let namespace = Namespace::installed().unwrap();
811        // client => ns_server => ns_client => server
812        //        ^            ^            ^-- zx channel connection
813        //        |            |-- connected through namespace bind/connect
814        //        |-- zx channel connection
815        let (ns_client, _server) = fidl::endpoints::create_endpoints();
816        let (_client, ns_server) = zx::Channel::create();
817        let path = "/test_path1";
818
819        assert_eq!(namespace.bind(path, ns_client), Ok(()));
820        assert_eq!(namespace.open(path, fio::Flags::empty(), ns_server), Ok(()));
821        assert_eq!(namespace.unbind(path), Ok(()));
822    }
823
824    #[test]
825    fn namespace_double_bind_error() {
826        let namespace = Namespace::installed().unwrap();
827        let (ns_client1, _server1) = fidl::endpoints::create_endpoints();
828        let (ns_client2, _server2) = fidl::endpoints::create_endpoints();
829        let path = "/test_path2";
830
831        assert_eq!(namespace.bind(path, ns_client1), Ok(()));
832        assert_eq!(namespace.bind(path, ns_client2), Err(zx::Status::ALREADY_EXISTS));
833        assert_eq!(namespace.unbind(path), Ok(()));
834    }
835
836    #[test]
837    fn namespace_open_error() {
838        let namespace = Namespace::installed().unwrap();
839        let path = "/test_path3";
840
841        let (_client, ns_server) = zx::Channel::create();
842        assert_eq!(
843            namespace.open(path, fio::Flags::empty(), ns_server),
844            Err(zx::Status::NOT_FOUND)
845        );
846    }
847
848    #[test]
849    fn namespace_unbind_error() {
850        let namespace = Namespace::installed().unwrap();
851        let path = "/test_path4";
852
853        assert_eq!(namespace.unbind(path), Err(zx::Status::NOT_FOUND));
854    }
855
856    #[test]
857    fn namespace_export() {
858        let namespace = Namespace::installed().unwrap();
859        let entries = namespace.export().unwrap();
860
861        assert!(!entries.is_empty());
862    }
863
864    fn cstr(orig: &str) -> CString {
865        CString::new(orig).expect("CString::new failed")
866    }
867
868    #[test]
869    fn fdio_spawn_run_target_bin_no_env() {
870        let job = zx::Job::from(zx::NullableHandle::invalid());
871        let cpath = cstr("/pkg/bin/spawn_test_target");
872        let (stdout_file, stdout_sock) = pipe_half().expect("Failed to make pipe");
873        let mut spawn_actions = [SpawnAction::clone_fd(stdout_file.as_fd(), 1)];
874
875        let cstrags: Vec<CString> = vec![cstr("test_arg")];
876        let mut cargs: Vec<&CStr> = cstrags.iter().map(|x| x.as_c_str()).collect();
877        cargs.insert(0, cpath.as_c_str());
878        let process = spawn_etc(
879            &job,
880            SpawnOptions::CLONE_ALL,
881            cpath.as_c_str(),
882            cargs.as_slice(),
883            None,
884            &mut spawn_actions,
885        )
886        .expect("Unable to spawn process");
887
888        let mut output = vec![];
889        loop {
890            let mut items = vec![
891                process.wait_item(Signals::PROCESS_TERMINATED),
892                stdout_sock.wait_item(Signals::SOCKET_READABLE | Signals::SOCKET_PEER_CLOSED),
893            ];
894
895            let signals_result =
896                object_wait_many(&mut items, MonotonicInstant::INFINITE).expect("unable to wait");
897
898            if items[1].pending().contains(Signals::SOCKET_READABLE) {
899                let bytes_len = stdout_sock.outstanding_read_bytes().expect("Socket error");
900                let mut buf: Vec<u8> = vec![0; bytes_len];
901                let read_len = stdout_sock
902                    .read(&mut buf[..])
903                    .or_else(|status| match status {
904                        Status::SHOULD_WAIT => Ok(0),
905                        _ => Err(status),
906                    })
907                    .expect("Unable to read buff");
908                output.extend_from_slice(&buf[0..read_len]);
909            }
910
911            // read stdout buffer until test process dies or the socket is closed
912            if items[1].pending().contains(Signals::SOCKET_PEER_CLOSED) {
913                break;
914            }
915
916            if items[0].pending().contains(Signals::PROCESS_TERMINATED) {
917                break;
918            }
919
920            if signals_result {
921                break;
922            };
923        }
924
925        assert_eq!(String::from_utf8(output).expect("unable to decode stdout"), "hello world\n");
926    }
927
928    // Simple tests of the fdio_open and fdio_open_at wrappers. These aren't intended to
929    // exhaustively test the fdio functions - there are separate tests for that - but they do
930    // exercise one success and one failure case for each function.
931    #[test]
932    fn fdio_open_and_open_at() {
933        use rand::distr::SampleString as _;
934
935        // fdio_open requires paths to be absolute
936        {
937            let (_, pkg_server) = zx::Channel::create();
938            assert_eq!(open("pkg", fio::PERM_READABLE, pkg_server), Err(zx::Status::NOT_FOUND));
939        }
940
941        let (pkg_client, pkg_server) = zx::Channel::create();
942        assert_eq!(open("/pkg", fio::PERM_READABLE, pkg_server), Ok(()));
943
944        // fdio_open/fdio_open_at disallow paths that are too long
945        {
946            let path = rand::distr::Alphanumeric
947                .sample_string(&mut rand::rng(), libc::PATH_MAX.try_into().unwrap());
948            let (_, server) = zx::Channel::create();
949            assert_eq!(
950                open_at(&pkg_client, &path, fio::Flags::empty(), server),
951                Err(zx::Status::INVALID_ARGS)
952            );
953        }
954
955        let (_, bin_server) = zx::Channel::create();
956        assert_eq!(open_at(&pkg_client, "bin", fio::PERM_READABLE, bin_server), Ok(()));
957    }
958
959    // Simple tests of the fdio_open_fd and fdio_open_fd_at wrappers. These aren't intended to
960    // exhaustively test the fdio functions - there are separate tests for that - but they do
961    // exercise one success and one failure case for each function.
962    #[test]
963    fn fdio_open_fd_and_open_fd_at() {
964        let pkg_fd =
965            open_fd("/pkg", fio::PERM_READABLE).expect("Failed to open /pkg using fdio_open_fd");
966
967        // Trying to open a non-existent directory should fail.
968        assert_matches!(
969            open_fd_at(&pkg_fd, "blahblah", fio::PERM_READABLE),
970            Err(zx::Status::NOT_FOUND)
971        );
972
973        let _: File = open_fd_at(&pkg_fd, "bin", fio::PERM_READABLE)
974            .expect("Failed to open bin/ subdirectory using fdio_open_fd_at");
975    }
976
977    #[test]
978    fn test_create_fd_null() {
979        let fd = create_fd_null().expect("failed to create null fd");
980        use std::io::{Read, Write};
981        let mut file = std::fs::File::from(fd);
982        assert!(file.write_all(b"hello").is_ok());
983        let mut buf = [0; 10];
984        assert_eq!(file.read(&mut buf).unwrap(), 0);
985    }
986}