blob: 5aecada5d7bb2dc00e49ac67f729ba953b95af05 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Bindings for the Zircon fdio library
mod fdio_sys;
mod spawn_builder;
pub use spawn_builder::{Error as SpawnBuilderError, SpawnBuilder};
use {
bitflags::bitflags,
fidl_fuchsia_device as fdevice, fidl_fuchsia_io as fio,
fuchsia_zircon::{self as zx, AsHandleRef as _, HandleBased as _},
std::{
convert::TryInto as _,
ffi::{CStr, CString, NulError},
fs::File,
marker::PhantomData,
mem::MaybeUninit,
os::{
raw,
unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
},
},
};
/// Connects a channel to a named service.
pub fn service_connect(service_path: &str, channel: zx::Channel) -> Result<(), zx::Status> {
let service_path =
CString::new(service_path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
let service_path = service_path.as_ptr();
// The channel is always consumed.
let channel = channel.into_raw();
let status = unsafe { fdio_sys::fdio_service_connect(service_path, channel) };
zx::Status::ok(status)
}
/// Connects a channel to a named service relative to a directory `dir`.
/// `dir` must be a directory protocol channel.
pub fn service_connect_at(
dir: &zx::Channel,
service_path: &str,
channel: zx::Channel,
) -> Result<(), zx::Status> {
let dir = dir.raw_handle();
let service_path =
CString::new(service_path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
let service_path = service_path.as_ptr();
// The channel is always consumed.
let channel = channel.into_raw();
let status = unsafe { fdio_sys::fdio_service_connect_at(dir, service_path, channel) };
zx::Status::ok(status)
}
/// Opens the remote object at the given `path` with the given `flags` asynchronously.
/// ('asynchronous' here is refering to fuchsia.io.Directory.Open not having a return value).
///
/// Wraps fdio_open.
pub fn open(path: &str, flags: fio::OpenFlags, channel: zx::Channel) -> Result<(), zx::Status> {
let path = CString::new(path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
let path = path.as_ptr();
let flags = flags.bits();
// The channel is always consumed.
let channel = channel.into_raw();
let status = unsafe { fdio_sys::fdio_open(path, flags, channel) };
zx::Status::ok(status)
}
/// Opens the remote object at the given `path` relative to the given `dir` with the given `flags`
/// asynchronously. ('asynchronous' here is refering to fuchsia.io.Directory.Open not having a
/// return value).
///
/// `dir` must be a directory protocol channel.
///
/// Wraps fdio_open_at.
pub fn open_at(
dir: &zx::Channel,
path: &str,
flags: fio::OpenFlags,
channel: zx::Channel,
) -> Result<(), zx::Status> {
let dir = dir.raw_handle();
let path = CString::new(path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
let path = path.as_ptr();
let flags = flags.bits();
// The channel is always consumed.
let channel = channel.into_raw();
let status = unsafe { fdio_sys::fdio_open_at(dir, path, flags, channel) };
zx::Status::ok(status)
}
/// Opens the remote object at the given `path` with the given `flags` synchronously, and on
/// success, binds that channel to a file descriptor and returns it.
///
/// Wraps fdio_open_fd.
pub fn open_fd(path: &str, flags: fio::OpenFlags) -> Result<File, zx::Status> {
let path = CString::new(path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
let path = path.as_ptr();
let flags = flags.bits();
// file descriptors are always positive; we expect fdio to initialize this to a legal value.
let mut fd = MaybeUninit::new(-1);
let status = {
let fd = fd.as_mut_ptr();
unsafe { fdio_sys::fdio_open_fd(path, flags, fd) }
};
let () = zx::Status::ok(status)?;
let fd = unsafe { fd.assume_init() };
debug_assert!(fd >= 0, "{} >= 0", fd);
let f = unsafe { File::from_raw_fd(fd) };
Ok(f)
}
/// Opens the remote object at the given `path` relative to the given `dir` with the given `flags`
/// synchronously, and on success, binds that channel to a file descriptor and returns it.
///
/// `dir` must be backed by a directory protocol channel (even though it is
/// wrapped in a std::fs::File).
///
/// Wraps fdio_open_fd_at.
pub fn open_fd_at(dir: &File, path: &str, flags: fio::OpenFlags) -> Result<File, zx::Status> {
let dir = dir.as_raw_fd();
let path = CString::new(path).map_err(|NulError { .. }| zx::Status::INVALID_ARGS)?;
let path = path.as_ptr();
let flags = flags.bits();
// file descriptors are always positive; we expect fdio to initialize this to a legal value.
let mut fd = MaybeUninit::new(-1);
let status = {
let fd = fd.as_mut_ptr();
unsafe { fdio_sys::fdio_open_fd_at(dir, path, flags, fd) }
};
let () = zx::Status::ok(status)?;
let fd = unsafe { fd.assume_init() };
debug_assert!(fd >= 0, "{} >= 0", fd);
let f = unsafe { File::from_raw_fd(fd) };
Ok(f)
}
/// Clones an object's underlying handle.
pub fn clone_fd<F: AsRawFd>(f: F) -> Result<zx::Handle, zx::Status> {
let fd = f.as_raw_fd();
// we expect fdio to initialize this to a legal value.
let mut handle = MaybeUninit::new(zx::Handle::invalid().raw_handle());
let status = {
let handle = handle.as_mut_ptr();
unsafe { fdio_sys::fdio_fd_clone(fd, handle) }
};
let () = zx::Status::ok(status)?;
let handle = unsafe { handle.assume_init() };
let handle = unsafe { zx::Handle::from_raw(handle) };
debug_assert!(!handle.is_invalid(), "({:?}).is_invalid()", handle);
Ok(handle)
}
/// Removes an object from the file descriptor table and returns its underlying handle.
pub fn transfer_fd<F: AsRawFd>(f: F) -> Result<zx::Handle, zx::Status> {
let fd = f.as_raw_fd();
// we expect fdio to initialize this to a legal value.
let mut handle = MaybeUninit::new(zx::Handle::invalid().raw_handle());
let status = {
let handle = handle.as_mut_ptr();
unsafe { fdio_sys::fdio_fd_transfer(fd, handle) }
};
let () = zx::Status::ok(status)?;
let handle = unsafe { handle.assume_init() };
let handle = unsafe { zx::Handle::from_raw(handle) };
debug_assert!(!handle.is_invalid(), "({:?}).is_invalid()", handle);
Ok(handle)
}
/// Create an object from a handle.
///
/// Afterward, the handle is owned by fdio, and will close with `F`.
/// See `transfer_fd` for a way to get it back.
pub fn create_fd<F: FromRawFd>(handle: zx::Handle) -> Result<F, zx::Status> {
let handle = handle.into_raw();
// file descriptors are always positive; we expect fdio to initialize this to a legal value.
let mut fd = MaybeUninit::new(-1);
let status = {
let fd = fd.as_mut_ptr();
unsafe { fdio_sys::fdio_fd_create(handle, fd) }
};
let () = zx::Status::ok(status)?;
let fd = unsafe { fd.assume_init() };
debug_assert!(fd >= 0, "{} >= 0", fd);
let f = unsafe { F::from_raw_fd(fd) };
Ok(f)
}
/// Bind a handle to a specific file descriptor.
///
/// Afterward, the handle is owned by fdio, and will close when the file descriptor is closed.
/// See `transfer_fd` for a way to get it back.
pub fn bind_to_fd(handle: zx::Handle, fd: RawFd) -> Result<(), zx::Status> {
if fd < 0 {
// fdio_bind_to_fd supports finding the next available fd when provided with a negative
// number, but due to lack of use-cases for this in Rust this is currently unsupported by
// this function.
return Err(zx::Status::INVALID_ARGS);
}
// The handle is always consumed.
let handle = handle.into_raw();
// we expect fdio to initialize this to a legal value.
let mut fdio = MaybeUninit::new(std::ptr::null_mut());
let status = unsafe { fdio_sys::fdio_create(handle, fdio.as_mut_ptr()) };
let () = zx::Status::ok(status)?;
let fdio = unsafe { fdio.assume_init() };
debug_assert_ne!(fdio, std::ptr::null_mut());
// The fdio object is always consumed.
let bound_fd = unsafe { fdio_sys::fdio_bind_to_fd(fdio, fd, 0) };
if bound_fd < 0 {
return Err(zx::Status::BAD_STATE);
}
// We requested a specific fd, we expect to have gotten it, or failed.
assert_eq!(bound_fd, fd);
Ok(())
}
/// Open a new connection to `f` by sending a request to open a new connection to the server.
pub fn clone_channel(f: &impl AsRawFd) -> Result<zx::Channel, zx::Status> {
let fd = f.as_raw_fd();
let fdio = unsafe { fdio_sys::fdio_unsafe_fd_to_io(fd) };
let handle = unsafe { fdio_sys::fdio_unsafe_borrow_channel(fdio) };
let handle = unsafe { fdio_sys::fdio_service_clone(handle) };
let () = unsafe { fdio_sys::fdio_unsafe_release(fdio) };
match handle {
zx::sys::ZX_HANDLE_INVALID => Err(zx::Status::NOT_SUPPORTED),
handle => {
let handle = unsafe { zx::Handle::from_raw(handle) };
Ok(zx::Channel::from(handle))
}
}
}
/// Retrieves the topological path for a device node.
pub fn device_get_topo_path(dev: &File) -> Result<String, zx::Status> {
let channel = clone_channel(dev)?;
let interface = fdevice::ControllerSynchronousProxy::new(channel);
interface
.get_topological_path(fuchsia_zircon::Time::INFINITE)
.map_err(|_| zx::Status::IO)?
.map_err(zx::Status::from_raw)
}
/// Creates a named pipe and returns one end as a zx::Socket.
pub fn pipe_half() -> Result<(File, zx::Socket), zx::Status> {
// file descriptors are always positive; we expect fdio to initialize this to a legal value.
let mut fd = MaybeUninit::new(-1);
// we expect fdio to initialize this to a legal value.
let mut handle = MaybeUninit::new(zx::Handle::invalid().raw_handle());
let status = {
let fd = fd.as_mut_ptr();
let handle = handle.as_mut_ptr();
unsafe { fdio_sys::fdio_pipe_half(fd, handle) }
};
let () = zx::Status::ok(status)?;
let fd = unsafe { fd.assume_init() };
debug_assert!(fd >= 0, "{} >= 0", fd);
let f = unsafe { File::from_raw_fd(fd) };
let handle = unsafe { handle.assume_init() };
let handle = unsafe { zx::Handle::from_raw(handle) };
debug_assert!(!handle.is_invalid(), "({:?}).is_invalid()", handle);
Ok((f, zx::Socket::from(handle)))
}
bitflags! {
/// Options to allow some or all of the environment of the running process
/// to be shared with the process being spawned.
pub struct SpawnOptions: u32 {
/// Provide the spawned process with the job in which the process was created.
///
/// The job will be available to the new process as the PA_JOB_DEFAULT argument
/// (exposed in Rust as `fuchsia_runtim::job_default()`).
const CLONE_JOB = fdio_sys::FDIO_SPAWN_CLONE_JOB;
/// Provide the spawned process with the shared library loader via the
/// PA_LDSVC_LOADER argument.
const DEFAULT_LOADER = fdio_sys::FDIO_SPAWN_DEFAULT_LDSVC;
/// Clones the filesystem namespace into the spawned process.
const CLONE_NAMESPACE = fdio_sys::FDIO_SPAWN_CLONE_NAMESPACE;
/// Clones file descriptors 0, 1, and 2 into the spawned process.
///
/// Skips any of these file descriptors that are closed without
/// generating an error.
const CLONE_STDIO = fdio_sys::FDIO_SPAWN_CLONE_STDIO;
/// Clones the environment into the spawned process.
const CLONE_ENVIRONMENT = fdio_sys::FDIO_SPAWN_CLONE_ENVIRON;
/// Clones the namespace, stdio, and environment into the spawned process.
const CLONE_ALL = fdio_sys::FDIO_SPAWN_CLONE_ALL;
}
}
// TODO: someday we'll have custom DSTs which will make this unnecessary.
fn nul_term_from_slice(argv: &[&CStr]) -> Vec<*const raw::c_char> {
argv.iter().map(|cstr| cstr.as_ptr()).chain(std::iter::once(std::ptr::null())).collect()
}
/// Spawn a process in the given `job`.
pub fn spawn(
job: &zx::Job,
options: SpawnOptions,
path: &CStr,
argv: &[&CStr],
) -> Result<zx::Process, zx::Status> {
let job = job.raw_handle();
let flags = options.bits();
let path = path.as_ptr();
let argv = nul_term_from_slice(argv);
// we expect fdio to initialize this to a legal value.
let mut process = MaybeUninit::new(zx::Handle::invalid().raw_handle());
// Safety: spawn consumes no handles and frees no pointers, and only
// produces a valid process upon success.
let status = {
let argv = argv.as_ptr();
let process = process.as_mut_ptr();
unsafe { fdio_sys::fdio_spawn(job, flags, path, argv, process) }
};
let () = zx::Status::ok(status)?;
let process = unsafe { process.assume_init() };
let process = unsafe { zx::Handle::from_raw(process) };
debug_assert!(!process.is_invalid(), "({:?}).is_invalid()", process);
Ok(zx::Process::from(process))
}
/// An action to take in `spawn_etc`.
#[repr(transparent)]
pub struct SpawnAction<'a>(fdio_sys::fdio_spawn_action_t, PhantomData<&'a ()>);
// TODO(https://github.com/rust-lang/rust-bindgen/issues/2000): bindgen
// generates really bad names for these.
mod fdio_spawn_action {
#![allow(dead_code)]
#![allow(non_camel_case_types)]
pub(super) type action_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1;
pub(super) type fd_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1__bindgen_ty_1;
pub(super) type ns_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1__bindgen_ty_2;
pub(super) type h_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1__bindgen_ty_3;
pub(super) type name_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1__bindgen_ty_4;
pub(super) type dir_t = super::fdio_sys::fdio_spawn_action__bindgen_ty_1__bindgen_ty_5;
}
impl<'a> SpawnAction<'a> {
pub const USE_FOR_STDIO: i32 = fdio_sys::FDIO_FLAG_USE_FOR_STDIO as i32;
/// Clone a file descriptor into the new process.
///
/// `local_fd`: File descriptor within the current process.
/// `target_fd`: File descriptor within the new process that will receive the clone.
pub fn clone_fd(local_fd: &'a impl AsRawFd, target_fd: i32) -> Self {
// Safety: `local_fd` is a valid file descriptor so long as we're inside the
// 'a lifetime.
Self(
fdio_sys::fdio_spawn_action_t {
action: fdio_sys::FDIO_SPAWN_ACTION_CLONE_FD,
__bindgen_anon_1: fdio_spawn_action::action_t {
fd: fdio_spawn_action::fd_t { local_fd: local_fd.as_raw_fd(), target_fd },
},
},
PhantomData,
)
}
/// Transfer a file descriptor into the new process.
///
/// `local_fd`: File descriptor within the current process.
/// `target_fd`: File descriptor within the new process that will receive the transfer.
pub fn transfer_fd(local_fd: impl IntoRawFd, target_fd: i32) -> Self {
// Safety: ownership of `local_fd` is consumed, so `Self` can live arbitrarily long.
// When the action is executed, the fd will be transferred.
Self(
fdio_sys::fdio_spawn_action_t {
action: fdio_sys::FDIO_SPAWN_ACTION_TRANSFER_FD,
__bindgen_anon_1: fdio_spawn_action::action_t {
fd: fdio_spawn_action::fd_t { local_fd: local_fd.into_raw_fd(), target_fd },
},
},
PhantomData,
)
}
/// Add the given entry to the namespace of the spawned process.
///
/// If `SpawnOptions::CLONE_NAMESPACE` is set, the namespace entry is added
/// to the cloned namespace from the calling process.
pub fn add_namespace_entry(prefix: &'a CStr, handle: zx::Handle) -> Self {
// Safety: ownership of the `handle` is consumed.
// The prefix string must stay valid through the 'a lifetime.
Self(
fdio_sys::fdio_spawn_action_t {
action: fdio_sys::FDIO_SPAWN_ACTION_ADD_NS_ENTRY,
__bindgen_anon_1: fdio_spawn_action::action_t {
ns: fdio_spawn_action::ns_t {
prefix: prefix.as_ptr(),
handle: handle.into_raw(),
},
},
},
PhantomData,
)
}
/// Add the given handle to the process arguments of the spawned process.
pub fn add_handle(kind: fuchsia_runtime::HandleInfo, handle: zx::Handle) -> Self {
// Safety: ownership of the `handle` is consumed.
// The prefix string must stay valid through the 'a lifetime.
Self(
fdio_sys::fdio_spawn_action_t {
action: fdio_sys::FDIO_SPAWN_ACTION_ADD_HANDLE,
__bindgen_anon_1: fdio_spawn_action::action_t {
h: fdio_spawn_action::h_t { id: kind.as_raw(), handle: handle.into_raw() },
},
},
PhantomData,
)
}
/// Sets the name of the spawned process to the given name.
pub fn set_name(name: &'a CStr) -> Self {
// Safety: the `name` pointer must be valid at least as long as `Self`.
Self(
fdio_sys::fdio_spawn_action_t {
action: fdio_sys::FDIO_SPAWN_ACTION_SET_NAME,
__bindgen_anon_1: fdio_spawn_action::action_t {
name: fdio_spawn_action::name_t { data: name.as_ptr() },
},
},
PhantomData,
)
}
fn is_null(&self) -> bool {
let Self(fdio_sys::fdio_spawn_action_t { action, __bindgen_anon_1: _ }, PhantomData) = self;
*action == 0
}
/// Nullifies the action to prevent the inner contents from being dropped.
fn nullify(&mut self) {
// Assert that our null value doesn't conflict with any "real" actions.
debug_assert!(
(fdio_sys::FDIO_SPAWN_ACTION_CLONE_FD != 0)
&& (fdio_sys::FDIO_SPAWN_ACTION_TRANSFER_FD != 0)
&& (fdio_sys::FDIO_SPAWN_ACTION_ADD_NS_ENTRY != 0)
&& (fdio_sys::FDIO_SPAWN_ACTION_ADD_HANDLE != 0)
&& (fdio_sys::FDIO_SPAWN_ACTION_SET_NAME != 0)
);
let Self(fdio_sys::fdio_spawn_action_t { action, __bindgen_anon_1: _ }, PhantomData) = self;
*action = 0;
}
}
const ERR_MSG_MAX_LENGTH: usize = fdio_sys::FDIO_SPAWN_ERR_MSG_MAX_LENGTH as usize;
fn spawn_with_actions(
job: &zx::Job,
options: SpawnOptions,
argv: &[&CStr],
environ: Option<&[&CStr]>,
actions: &mut [SpawnAction<'_>],
spawn_fn: impl FnOnce(
zx::sys::zx_handle_t, // job
u32, // flags
*const *const raw::c_char, // argv
*const *const raw::c_char, // environ
u64, // action_count
*const fdio_sys::fdio_spawn_action_t, // actions
*mut zx::sys::zx_handle_t, // process_out,
*mut raw::c_char, // err_msg_out
) -> zx::sys::zx_status_t,
) -> Result<zx::Process, (zx::Status, String)> {
let job = job.raw_handle();
let flags = options.bits();
let argv = nul_term_from_slice(argv);
let environ = environ.map(nul_term_from_slice);
if actions.iter().any(SpawnAction::is_null) {
return Err((zx::Status::INVALID_ARGS, "null SpawnAction".to_string()));
}
// we expect fdio to initialize this to a legal value.
let mut process = MaybeUninit::new(zx::Handle::invalid().raw_handle());
let mut err_msg = MaybeUninit::new([0; ERR_MSG_MAX_LENGTH]);
let status = {
let environ = environ.as_ref().map_or_else(std::ptr::null, Vec::as_ptr);
match actions.len().try_into() {
Ok(action_count) => spawn_fn(
job,
flags,
argv.as_ptr(),
environ,
action_count,
actions.as_ptr() as _,
process.as_mut_ptr(),
err_msg.as_mut_ptr() as _,
),
Err(std::num::TryFromIntError { .. }) => zx::Status::INVALID_ARGS.into_raw(),
}
};
// Statically verify this hasn't been moved out of during the call above;
// raw pointers escape the borrow checker.
std::mem::drop(environ);
zx::Status::ok(status).map_err(|status| {
let err_msg = unsafe { err_msg.assume_init() };
let err_msg = unsafe { CStr::from_ptr(err_msg.as_ptr()) };
let err_msg = err_msg.to_string_lossy().into_owned();
(status, err_msg)
})?;
// Clear out the actions so we can't unsafely re-use them in a future call.
actions.iter_mut().for_each(SpawnAction::nullify);
let process = unsafe { process.assume_init() };
let process = unsafe { zx::Handle::from_raw(process) };
debug_assert!(!process.is_invalid(), "({:?}).is_invalid()", process);
Ok(zx::Process::from(process))
}
/// Spawn a process in the given `job` using a series of `SpawnAction`s.
/// All `SpawnAction`s are nullified after their use in this function.
pub fn spawn_etc(
job: &zx::Job,
options: SpawnOptions,
path: &CStr,
argv: &[&CStr],
environ: Option<&[&CStr]>,
actions: &mut [SpawnAction<'_>],
) -> Result<zx::Process, (zx::Status, String)> {
let path = path.as_ptr();
spawn_with_actions(
job,
options,
argv,
environ,
actions,
|job, flags, argv, environ, action_count, actions_ptr, process_out, err_msg_out| unsafe {
fdio_sys::fdio_spawn_etc(
job,
flags,
path,
argv,
environ,
action_count,
actions_ptr,
process_out,
err_msg_out,
)
},
)
}
/// Spawn a process in the given job using an executable VMO.
pub fn spawn_vmo(
job: &zx::Job,
options: SpawnOptions,
executable_vmo: zx::Vmo,
argv: &[&CStr],
environ: Option<&[&CStr]>,
actions: &mut [SpawnAction<'_>],
) -> Result<zx::Process, (zx::Status, String)> {
let executable_vmo = executable_vmo.into_raw();
spawn_with_actions(
job,
options,
argv,
environ,
actions,
|job, flags, argv, environ, action_count, actions_ptr, process_out, err_msg_out| unsafe {
fdio_sys::fdio_spawn_vmo(
job,
flags,
executable_vmo,
argv,
environ,
action_count,
actions_ptr,
process_out,
err_msg_out,
)
},
)
}
/// Gets a read-only VMO containing the whole contents of the file. This function
/// creates a clone of the underlying VMO when possible, falling back to eagerly
/// reading the contents into a freshly-created VMO.
pub fn get_vmo_copy_from_file(f: &File) -> Result<zx::Vmo, zx::Status> {
let fd = f.as_raw_fd();
// we expect fdio to initialize this to a legal value.
let mut vmo = MaybeUninit::new(zx::Handle::invalid().raw_handle());
let status = {
let vmo = vmo.as_mut_ptr();
unsafe { fdio_sys::fdio_get_vmo_copy(fd, vmo) }
};
let () = zx::Status::ok(status)?;
let vmo = unsafe { vmo.assume_init() };
let vmo = unsafe { zx::Handle::from_raw(vmo) };
debug_assert!(!vmo.is_invalid(), "({:?}).is_invalid()", vmo);
Ok(zx::Vmo::from(vmo))
}
/// Gets a read-exec VMO containing the whole contents of the file.
pub fn get_vmo_exec_from_file(f: &File) -> Result<zx::Vmo, zx::Status> {
let fd = f.as_raw_fd();
// we expect fdio to initialize this to a legal value.
let mut vmo = MaybeUninit::new(zx::Handle::invalid().raw_handle());
let status = {
let vmo = vmo.as_mut_ptr();
unsafe { fdio_sys::fdio_get_vmo_exec(fd, vmo) }
};
let () = zx::Status::ok(status)?;
let vmo = unsafe { vmo.assume_init() };
let vmo = unsafe { zx::Handle::from_raw(vmo) };
debug_assert!(!vmo.is_invalid(), "({:?}).is_invalid()", vmo);
Ok(zx::Vmo::from(vmo))
}
pub struct Namespace {
ns: *mut fdio_sys::fdio_ns_t,
}
impl Namespace {
/// Get the currently installed namespace.
pub fn installed() -> Result<Self, zx::Status> {
// we expect fdio to initialize this to a legal value.
let mut ns = MaybeUninit::new(std::ptr::null_mut());
let status = {
let ns = ns.as_mut_ptr();
unsafe { fdio_sys::fdio_ns_get_installed(ns) }
};
let () = zx::Status::ok(status)?;
let ns = unsafe { ns.assume_init() };
debug_assert_ne!(ns, std::ptr::null_mut());
Ok(Namespace { ns })
}
/// Open an object at |path| relative to the root of this namespace with |flags|.
///
/// |path| must be absolute.
///
/// This corresponds with fdio_ns_open in C.
pub fn open(
&self,
path: &str,
flags: fio::OpenFlags,
channel: zx::Channel,
) -> Result<(), zx::Status> {
let &Self { ns } = self;
let path = CString::new(path)?;
let path = path.as_ptr();
let flags = flags.bits();
// The channel is always consumed.
let channel = channel.into_raw();
let status = unsafe { fdio_sys::fdio_ns_open(ns, path, flags, channel) };
zx::Status::ok(status)
}
/// Create a new directory within the namespace, bound to the provided
/// directory-protocol-compatible channel. The path must be an absolute path, like "/x/y/z",
/// containing no "." nor ".." entries. It is relative to the root of the namespace.
///
/// This corresponds with fdio_ns_bind in C.
pub fn bind(&self, path: &str, channel: zx::Channel) -> Result<(), zx::Status> {
let &Self { ns } = self;
let path = CString::new(path)?;
let path = path.as_ptr();
// The channel is always consumed.
let channel = channel.into_raw();
let status = unsafe { fdio_sys::fdio_ns_bind(ns, path, channel) };
zx::Status::ok(status)
}
/// Unbind the channel at path, closing the associated handle when all references to the node go
/// out of scope. The path must be an absolute path, like "/x/y/z", containing no "." nor ".."
/// entries. It is relative to the root of the namespace.
///
/// This corresponds with fdio_ns_unbind in C.
pub fn unbind(&self, path: &str) -> Result<(), zx::Status> {
let &Self { ns } = self;
let path = CString::new(path)?;
let path = path.as_ptr();
let status = unsafe { fdio_sys::fdio_ns_unbind(ns, path) };
zx::Status::ok(status)
}
pub fn into_raw(self) -> *mut fdio_sys::fdio_ns_t {
let Self { ns } = self;
ns
}
}
/// Returns the handle for the given FIDL service, or a Zircon Error Status. This function takes
/// ownership of 'service' because the File is released when acquiring the service handle.
///
/// This corresponds with fdio_get_service_handle in C.
pub fn get_service_handle(service: File) -> Result<zx::Channel, zx::Status> {
let fd = service.into_raw_fd();
// we expect fdio to initialize this to a legal value.
let mut handle = MaybeUninit::new(zx::Handle::invalid().raw_handle());
// Safety: fdio_get_service_handle() is an unsafe function because its 'out' parameter is a raw
// pointer and because it closes the given file descriptor. This invocation is safe because:
// 1) the supplied 'out' value, "&mut handle", is guaranteed to be valid,
// 2) fdio_get_service_handle() does not retain a copy of `out`, and
// 3) 'fd' is not used following this invocation.
let status = {
let handle = handle.as_mut_ptr();
unsafe { fdio_sys::fdio_get_service_handle(fd, handle) }
};
let () = zx::Status::ok(status)?;
// Safety: from_raw() is an unsafe function because it assumes the given raw handle is valid.
// Above, fdio_get_service_handle()'s return value was ok, so 'handle' must be valid.
let handle = unsafe { handle.assume_init() };
let handle = unsafe { zx::Handle::from_raw(handle) };
debug_assert!(!handle.is_invalid(), "({:?}).is_invalid()", handle);
Ok(zx::Channel::from(handle))
}
#[cfg(test)]
mod tests {
use {
super::*,
assert_matches::assert_matches,
fuchsia_zircon::{object_wait_many, Signals, Status, Time, WaitItem},
};
#[test]
fn namespace_get_installed() {
let namespace = Namespace::installed().expect("failed to get installed namespace");
assert!(!namespace.into_raw().is_null());
}
#[test]
fn namespace_bind_open_unbind() {
let namespace = Namespace::installed().unwrap();
// client => ns_server => ns_client => server
// ^ ^ ^-- zx channel connection
// | |-- connected through namespace bind/connect
// |-- zx channel connection
let (ns_client, _server) = zx::Channel::create().unwrap();
let (_client, ns_server) = zx::Channel::create().unwrap();
let path = "/test_path1";
assert_eq!(namespace.bind(path, ns_client), Ok(()));
assert_eq!(namespace.open(path, fio::OpenFlags::empty(), ns_server), Ok(()));
assert_eq!(namespace.unbind(path), Ok(()));
}
#[test]
fn namespace_double_bind_error() {
let namespace = Namespace::installed().unwrap();
let (ns_client1, _server1) = zx::Channel::create().unwrap();
let (ns_client2, _server2) = zx::Channel::create().unwrap();
let path = "/test_path2";
assert_eq!(namespace.bind(path, ns_client1), Ok(()));
assert_eq!(namespace.bind(path, ns_client2), Err(zx::Status::ALREADY_EXISTS));
assert_eq!(namespace.unbind(path), Ok(()));
}
#[test]
fn namespace_open_error() {
let namespace = Namespace::installed().unwrap();
let (_client, ns_server) = zx::Channel::create().unwrap();
let path = "/test_path3";
assert_eq!(
namespace.open(path, fio::OpenFlags::empty(), ns_server),
Err(zx::Status::NOT_FOUND)
);
}
#[test]
fn namespace_unbind_error() {
let namespace = Namespace::installed().unwrap();
let path = "/test_path4";
assert_eq!(namespace.unbind(path), Err(zx::Status::NOT_FOUND));
}
fn cstr(orig: &str) -> CString {
CString::new(orig).expect("CString::new failed")
}
#[test]
fn fdio_spawn_run_target_bin_no_env() {
let job = zx::Job::from(zx::Handle::invalid());
let cpath = cstr("/pkg/bin/spawn_test_target");
let (stdout_file, stdout_sock) = pipe_half().expect("Failed to make pipe");
let mut spawn_actions = [SpawnAction::clone_fd(&stdout_file, 1)];
let cstrags: Vec<CString> = vec![cstr("test_arg")];
let mut cargs: Vec<&CStr> = cstrags.iter().map(|x| x.as_c_str()).collect();
cargs.insert(0, cpath.as_c_str());
let process = spawn_etc(
&job,
SpawnOptions::CLONE_ALL,
cpath.as_c_str(),
cargs.as_slice(),
None,
&mut spawn_actions,
)
.expect("Unable to spawn process");
let mut output = vec![];
loop {
let mut items = vec![
WaitItem {
handle: process.as_handle_ref(),
waitfor: Signals::PROCESS_TERMINATED,
pending: Signals::NONE,
},
WaitItem {
handle: stdout_sock.as_handle_ref(),
waitfor: Signals::SOCKET_READABLE | Signals::SOCKET_PEER_CLOSED,
pending: Signals::NONE,
},
];
let signals_result =
object_wait_many(&mut items, Time::INFINITE).expect("unable to wait");
if items[1].pending.contains(Signals::SOCKET_READABLE) {
let bytes_len = stdout_sock.outstanding_read_bytes().expect("Socket error");
let mut buf: Vec<u8> = vec![0; bytes_len];
let read_len = stdout_sock
.read(&mut buf[..])
.or_else(|status| match status {
Status::SHOULD_WAIT => Ok(0),
_ => Err(status),
})
.expect("Unable to read buff");
output.extend_from_slice(&buf[0..read_len]);
}
// read stdout buffer until test process dies or the socket is closed
if items[1].pending.contains(Signals::SOCKET_PEER_CLOSED) {
break;
}
if items[0].pending.contains(Signals::PROCESS_TERMINATED) {
break;
}
if signals_result {
break;
};
}
assert_eq!(String::from_utf8(output).expect("unable to decode stdout"), "hello world\n");
}
// Simple tests of the fdio_open and fdio_open_at wrappers. These aren't intended to
// exhaustively test the fdio functions - there are separate tests for that - but they do
// exercise one success and one failure case for each function.
#[test]
fn fdio_open_and_open_at() {
use rand::distributions::DistString as _;
// fdio_open requires paths to be absolute
{
let (_, pkg_server) = zx::Channel::create().unwrap();
assert_eq!(
open("pkg", fio::OpenFlags::RIGHT_READABLE, pkg_server),
Err(zx::Status::NOT_FOUND)
);
}
let (pkg_client, pkg_server) = zx::Channel::create().unwrap();
assert_eq!(open("/pkg", fio::OpenFlags::RIGHT_READABLE, pkg_server), Ok(()));
// fdio_open/fdio_open_at disallow paths that are too long
{
let path = rand::distributions::Alphanumeric
.sample_string(&mut rand::thread_rng(), libc::PATH_MAX.try_into().unwrap());
let (_, server) = zx::Channel::create().unwrap();
assert_eq!(
open_at(&pkg_client, &path, fio::OpenFlags::empty(), server),
Err(zx::Status::INVALID_ARGS)
);
}
let (_, bin_server) = zx::Channel::create().unwrap();
assert_eq!(open_at(&pkg_client, "bin", fio::OpenFlags::RIGHT_READABLE, bin_server), Ok(()));
}
// Simple tests of the fdio_open_fd and fdio_open_fd_at wrappers. These aren't intended to
// exhaustively test the fdio functions - there are separate tests for that - but they do
// exercise one success and one failure case for each function.
#[test]
fn fdio_open_fd_and_open_fd_at() {
let pkg_fd = open_fd("/pkg", fio::OpenFlags::RIGHT_READABLE)
.expect("Failed to open /pkg using fdio_open_fd");
// Trying to open a non-existent directory should fail.
assert_matches!(
open_fd_at(&pkg_fd, "blahblah", fio::OpenFlags::RIGHT_READABLE),
Err(zx::Status::NOT_FOUND)
);
let _: File = open_fd_at(&pkg_fd, "bin", fio::OpenFlags::RIGHT_READABLE)
.expect("Failed to open bin/ subdirectory using fdio_open_fd_at");
}
// Tests success & error handling of the get_service_handle function. This does not exhaustively
// test the underlying FDIO function, as that is tested alongside its C implementation.
#[test]
fn fdio_get_service_handle() {
// Setup two handles to the same underlying file.
let service = File::open("/svc").expect("failed to open /svc");
let clone = service.try_clone().expect("failed to clone /svc service");
// 'service' keeps the file busy during 'get_service_handle(clone)'.
assert_eq!(get_service_handle(clone), Err(zx::Status::UNAVAILABLE));
// 'clone' released its handle to the file above; 'service' now holds the sole handle.
let _: zx::Channel =
get_service_handle(service).expect("failed to get fdio service handle");
}
}