#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))]
use std::ffi::OsStr;
#[cfg(target_os = "macos")]
use std::ffi::{CStr, CString};
use std::os::unix::prelude::*;
use std::path::Path;
use std::time::Duration;
use std::{io, mem};
#[cfg(target_os = "macos")]
use cf::*;
#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))]
use libudev;
use nix::fcntl::fcntl;
#[cfg(target_os = "macos")]
use nix::libc::{c_char, c_void};
use nix::{self, libc, unistd};
use posix::ioctl::{self, SerialLines};
#[cfg(target_os = "macos")]
use IOKit_sys::*;
use {DataBits, FlowControl, Parity, SerialPort, SerialPortInfo, SerialPortSettings, StopBits};
use {Error, ErrorKind};
#[cfg(any(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"), target_os = "macos"))]
use {SerialPortType, UsbPortInfo};
fn close(fd: RawFd) {
let _ = ioctl::tiocnxcl(fd);
let _ = unistd::close(fd);
}
#[derive(Debug)]
pub struct TTYPort {
fd: RawFd,
timeout: Duration,
exclusive: bool,
port_name: Option<String>,
}
impl TTYPort {
pub fn open(path: &Path, settings: &SerialPortSettings) -> ::Result<TTYPort> {
use nix::fcntl::FcntlArg::F_SETFL;
use nix::fcntl::OFlag;
use nix::libc::{self, cfmakeraw, tcflush, tcgetattr, tcsetattr};
let fd = nix::fcntl::open(
path,
OFlag::O_RDWR | OFlag::O_NOCTTY | OFlag::O_NONBLOCK,
nix::sys::stat::Mode::empty(),
)?;
let mut termios = unsafe { mem::uninitialized() };
let res = unsafe { tcgetattr(fd, &mut termios) };
if let Err(e) = nix::errno::Errno::result(res) {
close(fd);
return Err(e.into());
}
{
termios.c_cflag |= libc::CREAD | libc::CLOCAL;
unsafe { cfmakeraw(&mut termios) };
unsafe { tcsetattr(fd, libc::TCSANOW, &termios) };
let mut actual_termios = unsafe { mem::uninitialized() };
unsafe { tcgetattr(fd, &mut actual_termios) };
if actual_termios.c_iflag != termios.c_iflag
|| actual_termios.c_oflag != termios.c_oflag
|| actual_termios.c_lflag != termios.c_lflag
|| actual_termios.c_cflag != termios.c_cflag
{
return Err(Error::new(
ErrorKind::Unknown,
"Settings did not apply correctly",
));
};
unsafe { tcflush(fd, libc::TCIOFLUSH) };
ioctl::tiocexcl(fd)?;
fcntl(fd, F_SETFL(nix::fcntl::OFlag::empty()))?;
Ok(())
}.map_err(|e: Error| {
close(fd);
e
})?;
let mut port = TTYPort {
fd,
timeout: Duration::new(0, 0),
exclusive: true,
port_name: path.to_str().map(|s| s.to_string()),
};
if let Err(err) = port.set_all(settings) {
close(fd);
return Err(err);
}
Ok(port)
}
pub fn exclusive(&self) -> bool {
self.exclusive
}
pub fn set_exclusive(&mut self, exclusive: bool) -> ::Result<()> {
let setting_result = if exclusive {
ioctl::tiocexcl(self.fd)
} else {
ioctl::tiocnxcl(self.fd)
};
if let Err(err) = setting_result {
Err(err)
} else {
self.exclusive = exclusive;
Ok(())
}
}
fn set_pin(&mut self, pin: ioctl::SerialLines, level: bool) -> ::Result<()> {
let retval = if level {
ioctl::tiocmbis(self.fd, pin)
} else {
ioctl::tiocmbic(self.fd, pin)
};
match retval {
Ok(()) => Ok(()),
Err(err) => Err(err),
}
}
fn read_pin(&mut self, pin: ioctl::SerialLines) -> ::Result<bool> {
match ioctl::tiocmget(self.fd) {
Ok(pins) => Ok(pins.contains(pin)),
Err(err) => Err(err),
}
}
pub fn pair() -> ::Result<(Self, Self)> {
let next_pty_fd = nix::pty::posix_openpt(nix::fcntl::OFlag::O_RDWR)?;
nix::pty::grantpt(&next_pty_fd)?;
nix::pty::unlockpt(&next_pty_fd)?;
#[cfg(
not(
any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia"
)
)
)]
let ptty_name = unsafe { nix::pty::ptsname(&next_pty_fd)? };
#[cfg(
any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia"
)
)]
let ptty_name = nix::pty::ptsname_r(&next_pty_fd)?;
let slave_tty = TTYPort::open(Path::new(&ptty_name), &Default::default())?;
let master_tty = TTYPort {
fd: next_pty_fd.into_raw_fd(),
timeout: Duration::from_millis(100),
exclusive: true,
port_name: None,
};
Ok((master_tty, slave_tty))
}
#[cfg(
any(
target_os = "dragonflybsd",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
all(
target_os = "linux",
any(target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64")
)
)
)]
fn get_termios(&self) -> ::Result<libc::termios> {
let mut termios = unsafe { mem::uninitialized() };
let res = unsafe { libc::tcgetattr(self.fd, &mut termios) };
nix::errno::Errno::result(res)?;
Ok(termios)
}
#[cfg(
any(
target_os = "android",
all(
target_os = "linux",
not(any(target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64"))
)
)
)]
fn get_termios(&self) -> ::Result<libc::termios2> {
ioctl::tcgets2(self.fd)
}
#[cfg(
any(
target_os = "dragonflybsd",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
all(
target_os = "linux",
any(target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64")
)
)
)]
fn set_termios(&self, termios: &libc::termios) -> ::Result<()> {
let res = unsafe { libc::tcsetattr(self.fd, libc::TCSANOW, termios) };
nix::errno::Errno::result(res)?;
Ok(())
}
#[cfg(
any(
target_os = "android",
all(
target_os = "linux",
not(any(target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64"))
)
)
)]
fn set_termios(&self, termios2: &libc::termios2) -> ::Result<()> {
ioctl::tcsets2(self.fd, &termios2)
}
}
impl Drop for TTYPort {
fn drop(&mut self) {
close(self.fd);
}
}
impl AsRawFd for TTYPort {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl IntoRawFd for TTYPort {
fn into_raw_fd(self) -> RawFd {
let TTYPort { fd, .. } = self;
mem::forget(self);
fd
}
}
impl FromRawFd for TTYPort {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
let exclusive = match ioctl::tiocexcl(fd) {
Ok(_) => true,
Err(_) => false,
};
TTYPort {
fd,
timeout: Duration::from_millis(100),
exclusive,
port_name: None,
}
}
}
impl io::Read for TTYPort {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if let Err(e) = super::poll::wait_read_fd(self.fd, self.timeout) {
return Err(io::Error::from(::Error::from(e)));
}
match nix::unistd::read(self.fd, buf) {
Ok(n) => Ok(n),
Err(e) => Err(io::Error::from(::Error::from(e))),
}
}
}
impl io::Write for TTYPort {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if let Err(e) = super::poll::wait_write_fd(self.fd, self.timeout) {
return Err(io::Error::from(::Error::from(e)));
}
match nix::unistd::write(self.fd, buf) {
Ok(n) => Ok(n),
Err(e) => Err(io::Error::from(::Error::from(e))),
}
}
fn flush(&mut self) -> io::Result<()> {
nix::sys::termios::tcdrain(self.fd)
.map_err(|_| io::Error::new(io::ErrorKind::Other, "flush failed"))
}
}
impl SerialPort for TTYPort {
fn name(&self) -> Option<String> {
self.port_name.clone()
}
fn settings(&self) -> SerialPortSettings {
SerialPortSettings {
baud_rate: self.baud_rate().expect("Couldn't retrieve baud rate"),
data_bits: self.data_bits().expect("Couldn't retrieve data bits"),
flow_control: self.flow_control().expect("Couldn't retrieve flow control"),
parity: self.parity().expect("Couldn't retrieve parity"),
stop_bits: self.stop_bits().expect("Couldn't retrieve stop bits"),
timeout: self.timeout,
}
}
#[cfg(
any(
target_os = "android",
all(
target_os = "linux",
not(any(target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64"))
)
)
)]
fn baud_rate(&self) -> ::Result<u32> {
let termios2 = ioctl::tcgets2(self.fd)?;
assert!(termios2.c_ospeed == termios2.c_ispeed);
Ok(termios2.c_ospeed as u32)
}
#[cfg(
any(
target_os = "dragonflybsd",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"
)
)]
fn baud_rate(&self) -> ::Result<u32> {
let termios = self.get_termios()?;
let ospeed = unsafe { libc::cfgetospeed(&termios) };
let ispeed = unsafe { libc::cfgetispeed(&termios) };
assert!(ospeed == ispeed);
Ok(ospeed as u32)
}
#[cfg(
all(
target_os = "linux",
any(target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64")
)
)]
fn baud_rate(&self) -> ::Result<u32> {
use self::libc::{B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000,
B4000000, B460800, B500000, B576000, B921600};
use self::libc::{B110, B115200, B1200, B134, B150, B1800, B19200, B200, B230400, B2400,
B300, B38400, B4800, B50, B57600, B600, B75, B9600};
let termios = self.get_termios()?;
let ospeed = unsafe { libc::cfgetospeed(&termios) };
let ispeed = unsafe { libc::cfgetispeed(&termios) };
assert!(ospeed == ispeed);
let res: u32 = match ospeed {
B50 => 50,
B75 => 75,
B110 => 110,
B134 => 134,
B150 => 150,
B200 => 200,
B300 => 300,
B600 => 600,
B1200 => 1200,
B1800 => 1800,
B2400 => 2400,
B4800 => 4800,
B9600 => 9600,
B19200 => 19_200,
B38400 => 38_400,
B57600 => 57_600,
B115200 => 115_200,
B230400 => 230_400,
B460800 => 460_800,
B500000 => 500_000,
B576000 => 576_000,
B921600 => 921_600,
B1000000 => 1_000_000,
B1152000 => 1_152_000,
B1500000 => 1_500_000,
B2000000 => 2_000_000,
B2500000 => 2_500_000,
B3000000 => 3_000_000,
B3500000 => 3_500_000,
B4000000 => 4_000_000,
_ => unreachable!(),
};
Ok(res)
}
fn data_bits(&self) -> ::Result<DataBits> {
let termios = self.get_termios()?;
match termios.c_cflag & libc::CSIZE {
libc::CS8 => Ok(DataBits::Eight),
libc::CS7 => Ok(DataBits::Seven),
libc::CS6 => Ok(DataBits::Six),
libc::CS5 => Ok(DataBits::Five),
_ => Err(Error::new(
ErrorKind::Unknown,
"Invalid data bits setting encountered",
)),
}
}
fn flow_control(&self) -> ::Result<FlowControl> {
let termios = self.get_termios()?;
if termios.c_cflag & libc::CRTSCTS == libc::CRTSCTS {
Ok(FlowControl::Hardware)
} else if termios.c_iflag & (libc::IXON | libc::IXOFF) == (libc::IXON | libc::IXOFF) {
Ok(FlowControl::Software)
} else {
Ok(FlowControl::None)
}
}
fn parity(&self) -> ::Result<Parity> {
let termios = self.get_termios()?;
if termios.c_cflag & libc::PARENB == libc::PARENB {
if termios.c_cflag & libc::PARODD == libc::PARODD {
Ok(Parity::Odd)
} else {
Ok(Parity::Even)
}
} else {
Ok(Parity::None)
}
}
fn stop_bits(&self) -> ::Result<StopBits> {
let termios = self.get_termios()?;
if termios.c_cflag & libc::CSTOPB == libc::CSTOPB {
Ok(StopBits::Two)
} else {
Ok(StopBits::One)
}
}
fn timeout(&self) -> Duration {
self.timeout
}
fn set_all(&mut self, settings: &SerialPortSettings) -> ::Result<()> {
self.set_baud_rate(settings.baud_rate)?;
self.set_data_bits(settings.data_bits)?;
self.set_flow_control(settings.flow_control)?;
self.set_parity(settings.parity)?;
self.set_stop_bits(settings.stop_bits)?;
self.set_timeout(settings.timeout)?;
Ok(())
}
#[cfg(
any(
target_os = "android",
all(
target_os = "linux",
not(any(target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64"))
)
)
)]
fn set_baud_rate(&mut self, baud_rate: u32) -> ::Result<()> {
let mut termios2 = ioctl::tcgets2(self.fd)?;
termios2.c_cflag &= !nix::libc::CBAUD;
termios2.c_cflag |= nix::libc::BOTHER;
termios2.c_ispeed = baud_rate;
termios2.c_ospeed = baud_rate;
ioctl::tcsets2(self.fd, &termios2)
}
#[cfg(
any(
target_os = "dragonflybsd",
target_os = "freebsd",
target_os = "macos",
target_os = "netbsd",
target_os = "ios",
target_os = "openbsd"
)
)]
fn set_baud_rate(&mut self, baud_rate: u32) -> ::Result<()> {
let mut termios = self.get_termios()?;
let res = unsafe { libc::cfsetspeed(&mut termios, baud_rate.into()) };
nix::errno::Errno::result(res)?;
self.set_termios(&termios)
}
#[cfg(
all(
target_os = "linux",
any(target_env = "musl", target_arch = "powerpc", target_arch = "powerpc64")
)
)]
fn set_baud_rate(&mut self, baud_rate: u32) -> ::Result<()> {
use self::libc::{B1000000, B1152000, B1500000, B2000000, B2500000, B3000000, B3500000,
B4000000, B460800, B500000, B576000, B921600};
use self::libc::{B110, B115200, B1200, B134, B150, B1800, B19200, B200, B230400, B2400,
B300, B38400, B4800, B50, B57600, B600, B75, B9600};
let mut termios = self.get_termios()?;
let baud_rate = match baud_rate {
50 => B50,
75 => B75,
110 => B110,
134 => B134,
150 => B150,
200 => B200,
300 => B300,
600 => B600,
1200 => B1200,
1800 => B1800,
2400 => B2400,
4800 => B4800,
9600 => B9600,
19_200 => B19200,
38_400 => B38400,
57_600 => B57600,
115_200 => B115200,
230_400 => B230400,
460_800 => B460800,
500_000 => B500000,
576_000 => B576000,
921_600 => B921600,
1_000_000 => B1000000,
1_152_000 => B1152000,
1_500_000 => B1500000,
2_000_000 => B2000000,
2_500_000 => B2500000,
3_000_000 => B3000000,
3_500_000 => B3500000,
4_000_000 => B4000000,
_ => return Err(Error::new(ErrorKind::InvalidInput, "invalid baud rate")),
};
let res = unsafe { libc::cfsetspeed(&mut termios, baud_rate) };
nix::errno::Errno::result(res)?;
self.set_termios(&termios)
}
fn set_flow_control(&mut self, flow_control: FlowControl) -> ::Result<()> {
let mut termios = self.get_termios()?;
match flow_control {
FlowControl::None => {
termios.c_iflag &= !(libc::IXON | libc::IXOFF);
termios.c_cflag &= !libc::CRTSCTS;
}
FlowControl::Software => {
termios.c_iflag |= libc::IXON | libc::IXOFF;
termios.c_cflag &= !libc::CRTSCTS;
}
FlowControl::Hardware => {
termios.c_iflag &= !(libc::IXON | libc::IXOFF);
termios.c_cflag |= libc::CRTSCTS;
}
};
self.set_termios(&termios)
}
fn set_parity(&mut self, parity: Parity) -> ::Result<()> {
let mut termios = self.get_termios()?;
match parity {
Parity::None => {
termios.c_cflag &= !(libc::PARENB | libc::PARODD);
termios.c_iflag &= !libc::INPCK;
termios.c_iflag |= libc::IGNPAR;
}
Parity::Odd => {
termios.c_cflag |= libc::PARENB | libc::PARODD;
termios.c_iflag |= libc::INPCK;
termios.c_iflag &= !libc::IGNPAR;
}
Parity::Even => {
termios.c_cflag &= !libc::PARODD;
termios.c_cflag |= libc::PARENB;
termios.c_iflag |= libc::INPCK;
termios.c_iflag &= !libc::IGNPAR;
}
};
self.set_termios(&termios)
}
fn set_data_bits(&mut self, data_bits: DataBits) -> ::Result<()> {
let size = match data_bits {
DataBits::Five => libc::CS5,
DataBits::Six => libc::CS6,
DataBits::Seven => libc::CS7,
DataBits::Eight => libc::CS8,
};
let mut termios = self.get_termios()?;
termios.c_cflag &= !libc::CSIZE;
termios.c_cflag |= size;
self.set_termios(&termios)
}
fn set_stop_bits(&mut self, stop_bits: StopBits) -> ::Result<()> {
let mut termios = self.get_termios()?;
match stop_bits {
StopBits::One => termios.c_cflag &= !libc::CSTOPB,
StopBits::Two => termios.c_cflag |= libc::CSTOPB,
};
self.set_termios(&termios)
}
fn set_timeout(&mut self, timeout: Duration) -> ::Result<()> {
self.timeout = timeout;
Ok(())
}
fn write_request_to_send(&mut self, level: bool) -> ::Result<()> {
self.set_pin(SerialLines::REQUEST_TO_SEND, level)
}
fn write_data_terminal_ready(&mut self, level: bool) -> ::Result<()> {
self.set_pin(SerialLines::DATA_TERMINAL_READY, level)
}
fn read_clear_to_send(&mut self) -> ::Result<bool> {
self.read_pin(SerialLines::CLEAR_TO_SEND)
}
fn read_data_set_ready(&mut self) -> ::Result<bool> {
self.read_pin(SerialLines::DATA_SET_READY)
}
fn read_ring_indicator(&mut self) -> ::Result<bool> {
self.read_pin(SerialLines::RING)
}
fn read_carrier_detect(&mut self) -> ::Result<bool> {
self.read_pin(SerialLines::DATA_CARRIER_DETECT)
}
fn bytes_to_read(&self) -> ::Result<u32> {
ioctl::fionread(self.fd)
}
fn bytes_to_write(&self) -> ::Result<u32> {
ioctl::tiocoutq(self.fd)
}
fn clear(&self, buffer_to_clear: ::ClearBuffer) -> ::Result<()> {
let buffer_id = match buffer_to_clear {
::ClearBuffer::Input => libc::TCIFLUSH,
::ClearBuffer::Output => libc::TCOFLUSH,
::ClearBuffer::All => libc::TCIOFLUSH,
};
let res = unsafe { nix::libc::tcflush(self.fd, buffer_id) };
nix::errno::Errno::result(res)
.map(|_| ())
.map_err(|e| e.into())
}
fn try_clone(&self) -> ::Result<Box<SerialPort>> {
let fd_cloned: i32 = fcntl(self.fd, nix::fcntl::F_DUPFD(self.fd))?;
Ok(Box::new(TTYPort {
fd: fd_cloned,
exclusive: self.exclusive,
port_name: self.port_name.clone(),
timeout: self.timeout,
}))
}
}
#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))]
fn udev_property_as_string(d: &libudev::Device, key: &str) -> Option<String> {
if let Some(s) = d.property_value(key).and_then(OsStr::to_str) {
Some(s.to_string())
} else {
None
}
}
#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))]
fn udev_hex_property_as_u16(d: &libudev::Device, key: &str) -> ::Result<u16> {
if let Some(hex_str) = d.property_value(key).and_then(OsStr::to_str) {
if let Ok(num) = u16::from_str_radix(hex_str, 16) {
Ok(num)
} else {
Err(Error::new(ErrorKind::Unknown, "value not hex string"))
}
} else {
Err(Error::new(ErrorKind::Unknown, "key not found"))
}
}
#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))]
fn port_type(d: &libudev::Device) -> ::Result<::SerialPortType> {
match d.property_value("ID_BUS").and_then(OsStr::to_str) {
Some("usb") => {
let serial_number = udev_property_as_string(d, "ID_SERIAL_SHORT");
Ok(SerialPortType::UsbPort(UsbPortInfo {
vid: udev_hex_property_as_u16(d, "ID_VENDOR_ID")?,
pid: udev_hex_property_as_u16(d, "ID_MODEL_ID")?,
serial_number,
manufacturer: udev_property_as_string(d, "ID_VENDOR"),
product: udev_property_as_string(d, "ID_MODEL"),
}))
}
Some("pci") => Ok(::SerialPortType::PciPort),
_ => Ok(::SerialPortType::Unknown),
}
}
#[cfg(target_os = "macos")]
fn get_parent_device_by_type(
device: io_object_t,
parent_type: *const c_char,
) -> Option<io_registry_entry_t> {
let parent_type = unsafe { CStr::from_ptr(parent_type) };
use mach::kern_return::KERN_SUCCESS;
let mut device = device;
loop {
let mut class_name: [c_char; 128] = unsafe { mem::uninitialized() };
unsafe { IOObjectGetClass(device, &mut class_name[0]) };
let name = unsafe { CStr::from_ptr(&class_name[0]) };
if name == parent_type {
return Some(device);
}
let mut parent: io_registry_entry_t = unsafe { mem::uninitialized() };
if unsafe {
IORegistryEntryGetParentEntry(device, kIOServiceClass(), &mut parent) != KERN_SUCCESS
} {
return None;
}
device = parent;
}
}
#[cfg(target_os = "macos")]
#[allow(non_upper_case_globals)]
fn get_int_property(
device_type: io_registry_entry_t,
property: &str,
cf_number_type: CFNumberType,
) -> Option<u32> {
unsafe {
let prop_str = CString::new(property).unwrap();
let key = CFStringCreateWithCString(
kCFAllocatorDefault,
prop_str.as_ptr(),
kCFStringEncodingUTF8,
);
let container = IORegistryEntryCreateCFProperty(device_type, key, kCFAllocatorDefault, 0);
if container.is_null() {
return None;
}
let num = match cf_number_type {
kCFNumberSInt16Type => {
let mut num: u16 = 0;
let num_ptr: *mut c_void = &mut num as *mut _ as *mut c_void;
CFNumberGetValue(container as CFNumberRef, cf_number_type, num_ptr);
Some(num as u32)
}
kCFNumberSInt32Type => {
let mut num: u32 = 0;
let num_ptr: *mut c_void = &mut num as *mut _ as *mut c_void;
CFNumberGetValue(container as CFNumberRef, cf_number_type, num_ptr);
Some(num)
}
_ => None,
};
CFRelease(container);
num
}
}
#[cfg(target_os = "macos")]
fn get_string_property(device_type: io_registry_entry_t, property: &str) -> Option<String> {
unsafe {
let prop_str = CString::new(property).unwrap();
let key = CFStringCreateWithCString(
kCFAllocatorDefault,
prop_str.as_ptr(),
kCFStringEncodingUTF8,
);
let container = IORegistryEntryCreateCFProperty(device_type, key, kCFAllocatorDefault, 0);
if container.is_null() {
return None;
}
let str_ptr = CFStringGetCStringPtr(container as CFStringRef, kCFStringEncodingMacRoman);
if str_ptr.is_null() {
CFRelease(container);
return None;
}
let opt_str = CStr::from_ptr(str_ptr).to_str().ok().map(String::from);
CFRelease(container);
opt_str
}
}
#[cfg(target_os = "macos")]
fn port_type(service: io_object_t) -> ::SerialPortType {
let bluetooth_device_class_name = b"IOBluetoothSerialClient\0".as_ptr() as *const c_char;
if let Some(usb_device) = get_parent_device_by_type(service, kIOUSBDeviceClassName()) {
SerialPortType::UsbPort(UsbPortInfo {
vid: get_int_property(usb_device, "idVendor", kCFNumberSInt16Type).unwrap_or_default()
as u16,
pid: get_int_property(usb_device, "idProduct", kCFNumberSInt16Type).unwrap_or_default()
as u16,
serial_number: get_string_property(usb_device, "USB Serial Number"),
manufacturer: get_string_property(usb_device, "USB Vendor Name"),
product: get_string_property(usb_device, "USB Product Name"),
})
} else if get_parent_device_by_type(service, bluetooth_device_class_name).is_some() {
::SerialPortType::BluetoothPort
} else {
::SerialPortType::PciPort
}
}
cfg_if! {
if #[cfg(target_os = "macos")] {
pub fn available_ports() -> ::Result<Vec<SerialPortInfo>> {
use cf::*;
use mach::kern_return::KERN_SUCCESS;
use mach::port::{mach_port_t, MACH_PORT_NULL};
use IOKit_sys::*;
let mut vec = Vec::new();
unsafe {
let classes_to_match = IOServiceMatching(kIOSerialBSDServiceValue());
if classes_to_match.is_null() {
return Err(Error::new(
ErrorKind::Unknown,
"IOServiceMatching returned a NULL dictionary.",
));
}
let key = CFStringCreateWithCString(
kCFAllocatorDefault,
kIOSerialBSDTypeKey(),
kCFStringEncodingUTF8,
);
if key.is_null() {
return Err(Error::new(
ErrorKind::Unknown,
"Failed to allocate key string.",
));
}
let value = CFStringCreateWithCString(
kCFAllocatorDefault,
kIOSerialBSDAllTypes(),
kCFStringEncodingUTF8,
);
if value.is_null() {
return Err(Error::new(
ErrorKind::Unknown,
"Failed to allocate value string.",
));
}
CFDictionarySetValue(classes_to_match, key as CFTypeRef, value as CFTypeRef);
let mut master_port: mach_port_t = MACH_PORT_NULL;
let mut kern_result = IOMasterPort(MACH_PORT_NULL, &mut master_port);
if kern_result != KERN_SUCCESS {
return Err(Error::new(
ErrorKind::Unknown,
format!("ERROR: {}", kern_result),
));
}
let mut matching_services: io_iterator_t = mem::uninitialized();
kern_result = IOServiceGetMatchingServices(
kIOMasterPortDefault,
classes_to_match,
&mut matching_services,
);
if kern_result != KERN_SUCCESS {
return Err(Error::new(
ErrorKind::Unknown,
format!("ERROR: {}", kern_result),
));
}
loop {
let modem_service = IOIteratorNext(matching_services);
if modem_service == MACH_PORT_NULL {
break;
}
let mut props = mem::uninitialized();
let result = IORegistryEntryCreateCFProperties(
modem_service,
&mut props,
kCFAllocatorDefault,
0,
);
if result == KERN_SUCCESS {
let key = CString::new("IODialinDevice").unwrap();
let key_cfstring = CFStringCreateWithCString(
kCFAllocatorDefault,
key.as_ptr(),
kCFStringEncodingUTF8,
);
let value = CFDictionaryGetValue(props, key_cfstring as *const c_void);
let type_id = CFGetTypeID(value);
if type_id == CFStringGetTypeID() {
let mut buf = Vec::with_capacity(256);
CFStringGetCString(
value as CFStringRef,
buf.as_mut_ptr(),
256,
kCFStringEncodingUTF8,
);
let path = CStr::from_ptr(buf.as_ptr()).to_string_lossy();
vec.push(SerialPortInfo {
port_name: path.to_string(),
port_type: port_type(modem_service),
});
} else {
return Err(Error::new(
ErrorKind::Unknown,
"Found invalid type for TypeID",
));
}
} else {
return Err(Error::new(ErrorKind::Unknown, format!("ERROR: {}", result)));
}
IOObjectRelease(modem_service);
}
}
Ok(vec)
}
} else if #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] {
pub fn available_ports() -> ::Result<Vec<SerialPortInfo>> {
let mut vec = Vec::new();
if let Ok(context) = libudev::Context::new() {
let mut enumerator = libudev::Enumerator::new(&context)?;
enumerator.match_subsystem("tty")?;
let devices = enumerator.scan_devices()?;
for d in devices {
if let Some(p) = d.parent() {
if let Some(devnode) = d.devnode() {
if let Some(path) = devnode.to_str() {
if let Some(driver) = p.driver() {
if driver == "serial8250"
&& TTYPort::open(devnode, &Default::default()).is_err()
{
continue;
}
}
if let Ok(pt) = port_type(&d) {
vec.push(SerialPortInfo {
port_name: String::from(path),
port_type: pt,
});
}
}
}
}
}
}
Ok(vec)
}
} else if #[cfg(all(target_os = "linux", not(target_env = "musl")))] {
pub fn available_ports() -> ::Result<Vec<SerialPortInfo>> {
Err(Error::new(
ErrorKind::Unknown,
"Serial port enumeration disabled (to enable compile with the 'libudev' feature)",
))
}
} else if #[cfg(target_os = "freebsd")] {
pub fn available_ports() -> ::Result<Vec<SerialPortInfo>> {
let mut vec = Vec::new();
let dev_path = Path::new("/dev/");
for path in dev_path.read_dir()? {
let path = path?;
let filename = path.file_name();
let filename_string = filename.to_string_lossy();
if filename_string.starts_with("cuaU") || filename_string.starts_with("cuau") || filename_string.starts_with("cuad") {
if !filename_string.ends_with(".init") && !filename_string.ends_with(".lock") {
vec.push(SerialPortInfo {
port_name: path.path().to_string_lossy().to_string(),
port_type: ::SerialPortType::Unknown,
});
}
}
}
Ok(vec)
}
} else {
pub fn available_ports() -> ::Result<Vec<SerialPortInfo>> {
Err(Error::new(
ErrorKind::Unknown,
"Not implemented for this OS",
))
}
}
}
#[test]
fn test_ttyport_into_raw_fd() {
#![allow(unused_variables)]
let (master, slave) = TTYPort::pair().expect("Unable to create ptty pair");
let master_fd = master.into_raw_fd();
let mut termios = unsafe { mem::uninitialized() };
let res = unsafe { nix::libc::tcgetattr(master_fd, &mut termios) };
if res != 0 {
close(master_fd);
panic!("tcgetattr on the master port failed");
}
let slave_fd = slave.into_raw_fd();
let res = unsafe { nix::libc::tcgetattr(slave_fd, &mut termios) };
if res != 0 {
close(slave_fd);
panic!("tcgetattr on the master port failed");
}
close(master_fd);
close(slave_fd);
}