Skip to content

Commit

Permalink
Add SerialPort::pair() for Unix platforms.
Browse files Browse the repository at this point in the history
  • Loading branch information
de-vri-es committed Feb 4, 2024
1 parent 1db7f24 commit 90e7be4
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
- [add][minor] Add `SerialPort::pair()` on Unix platforms to open a pair of connected pseudo-terminals.

# Version 0.2.18 - 2024-02-04
- [change][minor] Open serial ports on Unix with the `O_NOCTTY` flag.

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ winapi = { version = "0.3.9", features = ["commapi", "fileapi", "handleapi", "io
[dev-dependencies]
assert2 = "0.3.11"
serde_json = "1.0.108"
serial2 = { path = ".", features = ["serde"] }
serial2 = { path = ".", features = ["serde", "unix", "windows"] }

[package.metadata.docs.rs]
features = ["doc-cfg"]
4 changes: 0 additions & 4 deletions check-targets
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@ targets=(
i686-linux-android
i686-unknown-linux-gnu
i686-unknown-linux-musl
mips-unknown-linux-gnu
mips-unknown-linux-musl
mips64-unknown-linux-gnuabi64
mips64-unknown-linux-muslabi64
mips64el-unknown-linux-gnuabi64
mips64el-unknown-linux-muslabi64
mipsel-unknown-linux-gnu
mipsel-unknown-linux-musl
powerpc-unknown-linux-gnu
powerpc64-unknown-linux-gnu
Expand Down
21 changes: 21 additions & 0 deletions src/serial_port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ impl SerialPort {
Ok(serial_port)
}

/// Open a connected pair of pseudo-terminals.
#[cfg(any(doc, all(unix, feature = "unix")))]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "unix")))]
pub fn pair() -> std::io::Result<(Self, Self)> {
let (pty_a, pty_b) = sys::SerialPort::pair()?;
let mut pty_a = Self { inner: pty_a };
let mut pty_b = Self { inner: pty_b };
{
let mut settings = pty_a.get_configuration()?;
settings.set_raw();
pty_a.set_configuration(&settings)?;
}
{
let mut settings = pty_b.get_configuration()?;
settings.set_raw();
pty_b.set_configuration(&settings)?;
}

Ok((pty_a, pty_b))
}

/// Get a list of available serial ports.
///
/// Not currently supported on all platforms.
Expand Down
58 changes: 58 additions & 0 deletions src/sys/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ impl SerialPort {
Ok(Self::from_file(file))
}

#[cfg(any(doc, all(unix, feature = "unix")))]
pub fn pair() -> std::io::Result<(Self, Self)> {
use std::os::unix::io::FromRawFd;
unsafe {
let pty_a = check(libc::posix_openpt(libc::O_RDWR | libc::O_CLOEXEC | libc::O_NOCTTY))?;
let pty_a = std::fs::File::from_raw_fd(pty_a);
let pty_a = Self::from_file(pty_a);
let pty_b_name = pts_name(&pty_a)?;
check(libc::unlockpt(pty_a.file.as_raw_fd()))?;
check(libc::grantpt(pty_a.file.as_raw_fd()))?;
let pty_b = Self::open(&pty_b_name)?;
Ok((pty_a, pty_b))
}
}

pub fn from_file(file: std::fs::File) -> Self {
Self {
file,
Expand Down Expand Up @@ -341,6 +356,49 @@ where
std::io::Error::new(std::io::ErrorKind::Other, msg)
}

#[cfg(any(doc, all(unix, feature = "unix")))]
fn pts_name(master: &SerialPort) -> std::io::Result<std::path::PathBuf> {
use std::os::unix::ffi::OsStringExt;
use std::ffi::OsString;

cfg_if! {
if #[cfg(any(
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "illumos",
target_os = "solaris",
))] {
static PTSNAME: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _lock = PTSNAME.lock();
unsafe {
let name = libc::ptsname(master.file.as_raw_fd());
let name = std::ffi::CStr::from_ptr(name).to_bytes().to_vec();
return Ok(OsString::from_vec(name).into())
}
} else {
let mut name = Vec::with_capacity(256);
let ptr = name.spare_capacity_mut().as_mut_ptr().cast();
loop {
unsafe {
let ret = libc::ptsname_r(master.file.as_raw_fd(), ptr, name.capacity());
match ret {
0 => {
let len = std::ffi::CStr::from_ptr(ptr).to_bytes().len();
name.set_len(len);
return Ok(OsString::from_vec(name).into())
},
libc::ERANGE if name.capacity() < 2048 => {
name.reserve(name.capacity() * 2);
},
error => return Err(std::io::Error::from_raw_os_error(error)),
}
}
}
}
}
}

impl Settings {
pub fn set_raw(&mut self) {
unsafe {
Expand Down
18 changes: 18 additions & 0 deletions tests/pair.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#![cfg(unix)]

use serial2::SerialPort;
use assert2::{assert, let_assert};

#[test]
fn open_pair() {
let_assert!(Ok((a, b)) = SerialPort::pair());
assert!(let Ok(()) = a.write_all(b"Hello!"));
let mut buffer = [0; 6];
assert!(let Ok(()) = b.read_exact(&mut buffer));
assert!(&buffer == b"Hello!");

assert!(let Ok(()) = b.write_all(b"Goodbye!"));
let mut buffer = [0; 8];
assert!(let Ok(()) = a.read_exact(&mut buffer));
assert!(&buffer == b"Goodbye!");
}

0 comments on commit 90e7be4

Please sign in to comment.