blob: 2aabda58116c8a50b641b921d84c8b911a55e05b [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
#![deny(warnings)]
#[allow(bad_style)]
pub mod fdio_sys;
pub use self::fdio_sys::fdio_ioctl as ioctl_raw;
use {
::bitflags::bitflags,
fidl_fuchsia_device::ControllerSynchronousProxy,
fuchsia_zircon::{
self as zx,
prelude::*,
sys::{self, zx_handle_t, zx_status_t},
},
std::{
ffi::{self, CStr, CString},
fs::File,
marker::PhantomData,
os::{
raw,
unix::{
ffi::OsStrExt,
io::{AsRawFd, FromRawFd, IntoRawFd},
},
},
path::Path,
},
};
pub unsafe fn ioctl(
dev: &File,
op: raw::c_int,
in_buf: *const raw::c_void,
in_len: usize,
out_buf: *mut raw::c_void,
out_len: usize,
) -> Result<i32, zx::Status> {
match ioctl_raw(dev.as_raw_fd(), op, in_buf, in_len, out_buf, out_len) as i32 {
e if e < 0 => Err(zx::Status::from_raw(e)),
e => Ok(e),
}
}
/// Connects a channel to a named service.
pub fn service_connect(service_path: &str, channel: zx::Channel) -> Result<(), zx::Status> {
let c_service_path = CString::new(service_path).map_err(|_| zx::Status::INVALID_ARGS)?;
// TODO(raggi): this should be convered to an asynchronous FDIO
// client protocol as soon as that is available (post fidl2) as this
// call can block indefinitely.
//
// fdio_service connect takes a *const c_char service path and a channel.
// On success, the channel is connected, and on failure, it is closed.
// In either case, we do not need to clean up the channel so we use
// `into_raw` so that Rust forgets about it.
zx::ok(unsafe { fdio_sys::fdio_service_connect(c_service_path.as_ptr(), channel.into_raw()) })
}
/// 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 c_service_path = CString::new(service_path).map_err(|_| zx::Status::INVALID_ARGS)?;
// TODO(raggi): this should be convered to an asynchronous FDIO
// client protocol as soon as that is available (post fidl2) as this
// call can block indefinitely.
//
// fdio_service_connect_at takes a directory handle,
// a *const c_char service path, and a channel to connect.
// The directory handle is never consumed, so we borrow the raw handle.
// On success, the channel is connected, and on failure, it is closed.
// In either case, we do not need to clean up the channel so we use
// `into_raw` so that Rust forgets about it.
zx::ok(unsafe {
fdio_sys::fdio_service_connect_at(
dir.raw_handle(),
c_service_path.as_ptr(),
channel.into_raw(),
)
})
}
pub fn transfer_fd(file: std::fs::File) -> Result<zx::Handle, zx::Status> {
unsafe {
let mut fd_handle = zx::sys::ZX_HANDLE_INVALID;
let status = fdio_sys::fdio_fd_transfer(
file.into_raw_fd(),
&mut fd_handle as *mut zx::sys::zx_handle_t,
);
if status != zx::sys::ZX_OK {
return Err(zx::Status::from_raw(status));
}
Ok(zx::Handle::from_raw(fd_handle))
}
}
/// Open a new connection to `file` by sending a request to open
/// a new connection to the sever.
pub fn clone_channel(file: &std::fs::File) -> Result<zx::Channel, zx::Status> {
unsafe {
// First, we must open a new connection to the handle, since
// we must return a newly owned copy.
let fdio = fdio_sys::fdio_unsafe_fd_to_io(file.as_raw_fd());
let unowned_handle = fdio_sys::fdio_unsafe_borrow_channel(fdio);
let handle = fdio_sys::fdio_service_clone(unowned_handle);
fdio_sys::fdio_unsafe_release(fdio);
match handle {
zx::sys::ZX_HANDLE_INVALID => Err(zx::Status::NOT_SUPPORTED),
_ => Ok(zx::Channel::from(zx::Handle::from_raw(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 mut interface = ControllerSynchronousProxy::new(channel);
let (status, topo) = interface
.get_topological_path(fuchsia_zircon::Time::INFINITE)
.map_err(|_| zx::Status::IO)?;
fuchsia_zircon::Status::ok(status)?;
match topo {
Some(topo) => Ok(topo),
None => Err(zx::Status::BAD_STATE),
}
}
/// Creates a named pipe and returns one end as a zx::Socket.
pub fn pipe_half() -> Result<(std::fs::File, zx::Socket), zx::Status> {
unsafe {
let mut fd = -1;
let mut handle = zx::sys::ZX_HANDLE_INVALID;
let status = fdio_sys::fdio_pipe_half2(
&mut fd as *mut i32,
&mut handle as *mut zx::sys::zx_handle_t,
);
if status != zx::sys::ZX_OK {
return Err(zx::Status::from_raw(status));
}
Ok((std::fs::File::from_raw_fd(fd), zx::Socket::from(zx::Handle::from_raw(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(0 as *const raw::c_char)).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);
let mut process_out = 0;
// Safety: spawn consumes no handles and frees no pointers, and only
// produces a valid process upon success.
let status = unsafe { fdio_sys::fdio_spawn(job, flags, path, argv.as_ptr(), &mut process_out) };
zx::ok(status)?;
Ok(zx::Process::from(unsafe { zx::Handle::from_raw(process_out) }))
}
/// An action to take in `spawn_etc`.
#[repr(transparent)]
pub struct SpawnAction<'a>(fdio_sys::fdio_spawn_action_t, PhantomData<&'a ()>);
impl<'a> SpawnAction<'a> {
/// 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 File, 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_tag: fdio_sys::FDIO_SPAWN_ACTION_CLONE_FD,
action_value: fdio_sys::fdio_spawn_action_union_t {
fd: fdio_sys::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: File, 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_tag: fdio_sys::FDIO_SPAWN_ACTION_TRANSFER_FD,
action_value: fdio_sys::fdio_spawn_action_union_t {
fd: fdio_sys::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_tag: fdio_sys::FDIO_SPAWN_ACTION_ADD_NS_ENTRY,
action_value: fdio_sys::fdio_spawn_action_union_t {
ns: fdio_sys::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::HandleType, 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_tag: fdio_sys::FDIO_SPAWN_ACTION_ADD_HANDLE,
action_value: fdio_sys::fdio_spawn_action_union_t {
h: fdio_sys::fdio_spawn_action_h_t {
id: kind as u32,
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_tag: fdio_sys::FDIO_SPAWN_ACTION_SET_NAME,
action_value: fdio_sys::fdio_spawn_action_union_t {
name: fdio_sys::fdio_spawn_action_name_t { data: name.as_ptr() },
},
},
PhantomData,
)
}
fn is_null(&self) -> bool {
self.0.action_tag == 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)
);
self.0.action_tag = 0;
}
}
fn spawn_with_actions(
job: &zx::Job,
options: SpawnOptions,
argv: &[&CStr],
environ: &[&CStr],
actions: &mut [SpawnAction],
spawn_fn: impl FnOnce(
zx_handle_t, // job
u32, // flags
*const *const raw::c_char, // argv
*const *const raw::c_char, // environ
usize, // action_count
*const fdio_sys::fdio_spawn_action_t, // actions
*mut zx_handle_t, // process_out,
*mut [raw::c_char; fdio_sys::FDIO_SPAWN_ERR_MSG_MAX_LENGTH as usize], // err_msg_out
) -> 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 = nul_term_from_slice(environ);
if actions.iter().any(|a| a.is_null()) {
return Err((zx::Status::INVALID_ARGS, "null SpawnAction".to_string()));
}
// Safety: actions are repr(transparent) wrappers around fdio_spawn_action_t
let action_count = actions.len();
let actions_ptr: *const SpawnAction = actions.as_ptr();
let actions_ptr = actions_ptr as *const fdio_sys::fdio_spawn_action_t;
let mut process_out = 0;
let mut err_msg_out = [0 as raw::c_char; fdio_sys::FDIO_SPAWN_ERR_MSG_MAX_LENGTH as usize];
let status = spawn_fn(
job,
flags,
argv.as_ptr(),
environ.as_ptr(),
action_count,
actions_ptr,
&mut process_out,
&mut err_msg_out,
);
zx::ok(status).map_err(|status| {
let err_msg =
unsafe { CStr::from_ptr(err_msg_out.as_ptr()) }.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(|a| a.nullify());
Ok(zx::Process::from(unsafe { zx::Handle::from_raw(process_out) }))
}
/// 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: &[&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: &[&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,
)
},
)
}
/// Events that can occur while watching a directory, including files that already exist prior to
/// running a Watcher.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum WatchEvent {
/// A file was added.
AddFile,
/// A file was removed.
RemoveFile,
/// The Watcher has enumerated all the existing files and has started to wait for new files to
/// be added.
Idle,
Unknown(i32),
#[doc(hidden)]
#[allow(non_camel_case_types)]
// Try to prevent exhaustive matching since this enum may grow if fdio's events expand.
__do_not_match,
}
impl From<raw::c_int> for WatchEvent {
fn from(i: raw::c_int) -> WatchEvent {
match i {
fdio_sys::WATCH_EVENT_ADD_FILE => WatchEvent::AddFile,
fdio_sys::WATCH_EVENT_REMOVE_FILE => WatchEvent::RemoveFile,
fdio_sys::WATCH_EVENT_IDLE => WatchEvent::Idle,
_ => WatchEvent::Unknown(i),
}
}
}
impl From<WatchEvent> for raw::c_int {
fn from(i: WatchEvent) -> raw::c_int {
match i {
WatchEvent::AddFile => fdio_sys::WATCH_EVENT_ADD_FILE,
WatchEvent::RemoveFile => fdio_sys::WATCH_EVENT_REMOVE_FILE,
WatchEvent::Idle => fdio_sys::WATCH_EVENT_IDLE,
WatchEvent::Unknown(i) => i as raw::c_int,
_ => -1 as raw::c_int,
}
}
}
unsafe extern "C" fn watcher_cb<F>(
_dirfd: raw::c_int,
event: raw::c_int,
fn_: *const raw::c_char,
watcher: *mut raw::c_void,
) -> sys::zx_status_t
where
F: Sized + FnMut(WatchEvent, &Path) -> Result<(), zx::Status>,
{
let cb: &mut F = &mut *(watcher as *mut F);
let filename = ffi::OsStr::from_bytes(CStr::from_ptr(fn_).to_bytes());
match cb(WatchEvent::from(event), Path::new(filename)) {
Ok(()) => sys::ZX_OK,
Err(e) => e.into_raw(),
}
}
/// Runs the given callback for each file in the directory and each time a new file is
/// added to the directory.
///
/// If the callback returns an error, the watching stops, and the zx::Status is returned.
///
/// This function blocks for the duration of the watch operation. The deadline parameter will stop
/// the watch at the given (absolute) time and return zx::Status::TIMED_OUT. A deadline of
/// zx::ZX_TIME_INFINITE will never expire.
///
/// The callback may use zx::Status::STOP as a way to signal to the caller that it wants to
/// stop because it found what it was looking for. Since this error code is not returned by
/// syscalls or public APIs, the callback does not need to worry about it turning up normally.
pub fn watch_directory<F>(dir: &File, deadline: sys::zx_time_t, mut f: F) -> zx::Status
where
F: Sized + FnMut(WatchEvent, &Path) -> Result<(), zx::Status>,
{
let cb_ptr: *mut raw::c_void = &mut f as *mut _ as *mut raw::c_void;
unsafe {
zx::Status::from_raw(fdio_sys::fdio_watch_directory(
dir.as_raw_fd(),
Some(watcher_cb::<F>),
deadline,
cb_ptr,
))
}
}
/// Calculates an IOCTL value from kind, family and number.
pub const fn make_ioctl(kind: raw::c_int, family: raw::c_int, number: raw::c_int) -> raw::c_int {
((kind & 0xF) << 20) | ((family & 0xFF) << 8) | (number & 0xFF)
}
pub fn get_vmo_copy_from_file(file: &File) -> Result<zx::Vmo, zx::Status> {
unsafe {
let mut vmo_handle: zx::sys::zx_handle_t = zx::sys::ZX_HANDLE_INVALID;
match fdio_sys::fdio_get_vmo_copy(file.as_raw_fd(), &mut vmo_handle) {
0 => Ok(zx::Vmo::from(zx::Handle::from_raw(vmo_handle))),
error_code => Err(zx::Status::from_raw(error_code)),
}
}
}