| // 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)), |
| } |
| } |
| } |