Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 68 additions & 3 deletions src/posix/tty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::path::Path;
use std::time::{Duration, Instant};
use std::{io, mem};

use nix::fcntl::{fcntl, OFlag};
use nix::fcntl::{fcntl, FlockArg, OFlag};
use nix::{libc, unistd};

use crate::posix::ioctl::{self, SerialLines};
Expand All @@ -31,6 +31,43 @@ fn close(fd: RawFd) {
let _ = unistd::close(fd);
}

/// Convenience method to acquire an exclusive lock using flock
///
/// This is used ensure that no other applications
/// are using the port. This requires that the other application also uses flock,
/// so this does not work with all applications.
fn flock_exclusive(fd: RawFd) -> Result<()> {
nix::fcntl::flock(fd, FlockArg::LockExclusiveNonblock).map_err(|e| {
if e == nix::errno::Errno::EWOULDBLOCK {
Error::new(
ErrorKind::NoDevice,
"Unable to acquire exclusive lock on serial port",
)
} else {
e.into()
}
})
}

/// Convenience method to acquire a shared lock using flock
///
/// With the shared lock, other applications are able to use the port
/// as well, if they're also using a shared lock,
/// but this makes sure that they cannot acquire an exclusive
/// lock while we're using the port.
fn flock_shared(fd: RawFd) -> Result<()> {
nix::fcntl::flock(fd, FlockArg::LockSharedNonblock).map_err(|e| {
if e == nix::errno::Errno::EWOULDBLOCK {
Error::new(
ErrorKind::NoDevice,
"Unable to acquire exclusive lock on serial port",
)
} else {
e.into()
}
})
}

/// A serial port implementation for POSIX TTY ports
///
/// The port will be closed when the value is dropped. This struct
Expand Down Expand Up @@ -130,6 +167,9 @@ impl TTYPort {
// other applications that may have an exclusive port lock.
ioctl::tiocexcl(fd.0)?;

// Also use flock to lock the port
flock_exclusive(fd.0)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After sleeping over it: Shouldn't we make this a "all or nothing" operation and attempt to gracefully retreat from this situation an not leave a port locked in the one way or the other?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that is what should be happening already. If the flock fails, then an error will be returned, and the serial port is not opened.

If the call to ioctl::tiocexcl fails, then we never get to the point where flock is called.

The lock placed by flock will be removed automatically when the file descriptor is closed, according to its man page.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I should add a note on that for future me.


let mut termios = MaybeUninit::uninit();
nix::errno::Errno::result(unsafe { tcgetattr(fd.0, termios.as_mut_ptr()) })?;
let mut termios = unsafe { termios.assume_init() };
Expand Down Expand Up @@ -215,7 +255,13 @@ impl TTYPort {
/// If a port is exclusive, then trying to open the same device path again
/// will fail.
///
/// See the man pages for the tiocexcl and tiocnxcl ioctl's for more details.
/// The tiocexcl ioctl is used to prevent other applications from opening
/// the port.
///
/// `flock` is used to place an advisory lock, which prevents conflicts with
/// other applications using `flock`.
///
/// See the man pages for the tiocexcl/tiocnxcl ioctl's and `flock` for more details.
///
/// ## Errors
///
Expand All @@ -228,6 +274,15 @@ impl TTYPort {
};

setting_result?;

let flock_result = if exclusive {
flock_exclusive(self.fd)
} else {
flock_shared(self.fd)
};

flock_result?;

self.exclusive = exclusive;
Ok(())
}
Expand Down Expand Up @@ -414,10 +469,20 @@ fn get_termios_speed(fd: RawFd) -> u32 {

impl FromRawFd for TTYPort {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
let flock_successful = flock_exclusive(fd).is_ok();

// TODO: If we fail to get the exclusive lock, this probably means that
// another process is using the port, and we should return an error,
// instead of using the port in non-exclusive mode.
//
// This will require a breaking change, as this method currently can't fail.

let in_exclusive_mode = ioctl::tiocexcl(fd).is_ok();

TTYPort {
fd,
timeout: Duration::from_millis(100),
exclusive: ioctl::tiocexcl(fd).is_ok(),
exclusive: in_exclusive_mode && flock_successful,
// It is not trivial to get the file path corresponding to a file descriptor.
// We'll punt on it and set it to `None` here.
port_name: None,
Expand Down
Loading