| // 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 { |
| fuchsia_zircon::{ |
| self as zx, |
| prelude::*, |
| sys, |
| }, |
| std::{ |
| ffi::{self, CString, CStr}, |
| fs::File, |
| os::{ |
| raw, |
| unix::{ |
| ffi::OsStrExt, |
| io::{ |
| AsRawFd, |
| 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<(Vec<zx::Channel>, Vec<u32>), zx::Status> { |
| unsafe { |
| let mut channels: [zx::sys::zx_handle_t; fdio_sys::FDIO_MAX_HANDLES as usize] = [0, 0, 0]; |
| let mut types: [u32; fdio_sys::FDIO_MAX_HANDLES as usize] = [0, 0, 0]; |
| let res = |
| fdio_sys::fdio_transfer_fd(file.into_raw_fd(), 0, &mut channels[0], &mut types[0]); |
| if res < 0 { |
| return Err(zx::Status::from_raw(res)); |
| } |
| let num_handles = res as usize; |
| Ok(( |
| channels[0..num_handles] |
| .iter() |
| .map(|c| zx::Channel::from(zx::Handle::from_raw(*c))) |
| .collect(), |
| types[0..num_handles].to_vec(), |
| )) |
| } |
| } |
| |
| /// 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 mut topo = vec![0; 1024]; |
| |
| // This is safe because the length of the output buffer is computed from the vector, and the |
| // callee does not retain any pointers. |
| let size = unsafe { |
| ioctl( |
| dev, |
| IOCTL_DEVICE_GET_TOPO_PATH, |
| ::std::ptr::null(), |
| 0, |
| topo.as_mut_ptr() as *mut raw::c_void, |
| topo.len())? |
| }; |
| topo.truncate((size - 1) as usize); |
| String::from_utf8(topo).map_err(|_| zx::Status::IO) |
| } |
| |
| /// 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::ErrTimedOut. A deadline of |
| /// zx::ZX_TIME_INFINITE will never expire. |
| /// |
| /// The callback may use zx::ErrStop 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, |
| )) |
| } |
| } |
| |
| // TODO(raggi): when const fn is stable, replace the macro with const fn. |
| #[macro_export] |
| macro_rules! make_ioctl { |
| ($kind:expr, $family:expr, $number:expr) => { |
| (((($kind) & 0xF) << 20) | ((($family) & 0xFF) << 8) | (($number) & 0xFF)) |
| }; |
| } |
| /// Calculates an IOCTL value from kind, family and number. |
| pub fn make_ioctl(kind: raw::c_int, family: raw::c_int, number: raw::c_int) -> raw::c_int { |
| make_ioctl!(kind, family, number) |
| } |
| |
| 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)) |
| } |
| } |
| } |
| |
| pub const IOCTL_DEVICE_GET_TOPO_PATH: raw::c_int = make_ioctl!( |
| fdio_sys::IOCTL_KIND_DEFAULT, |
| fdio_sys::IOCTL_FAMILY_DEVICE, |
| 4 |
| ); |