-
Notifications
You must be signed in to change notification settings - Fork 676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add various pty functions #556
Conversation
src/pty.rs
Outdated
pub fn ptsname(fd: RawFd) -> Result<String> { | ||
let name_ptr = unsafe { libc::ptsname(fd) }; | ||
if name_ptr.is_null() { | ||
return Err(Errno::ENOTTY.into()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is Err(Error::last().into()) not safer here? I mean according to the manpage ENOTTY is the only error, but better safe then sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's probably the correct thing to do, though I do think they'll end up with the same functionality. I'll change it.
@Mic92 Also do you have any comment on the two |
src/pty.rs
Outdated
} | ||
|
||
let name_cstr = unsafe { CStr::from_ptr(name_ptr) }; | ||
let name = match name_cstr.to_str() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is probably safe to ignore utf8 validation errors as we get the string from the operating system:
see https://github.com/nix-rust/nix/blob/master/src/sys/utsname.rs#L65
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it is not safe, then it is better to return the value as it is.
@Mic92 I think I've resolved both issues. Let me know what you think. I left them as separate commits so they're easier to review, but I'll squash them before they're pulled. |
src/pty.rs
Outdated
}; | ||
Ok(name) | ||
Ok(name.to_string_lossy().into_owned()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
....replacing any invalid UTF-8 sequences with U+FFFD REPLACEMENT CHARACTER...
Is the unmodified value of this function not meant to be as an input for functions like open(2)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should never receive invalid UTF-8 sequences from the OS, so that should never happen. I'd prefer to use from_utf8_unchecked
(as you mentioned in your comment), which doesn't do any error detection, but I couldn't figure out how to use it. And note that using from_utf8_unchecked
has the exact same problem, but instead of fixing invalid UTF-8 strings it will just crash later when it's attempted to be decoded as proper UTF-8.
Basically if we use the String
type as the output from this function, it must be valid UTF-8 regardless of how we convert it. So the question really is, does ptsname*()
always return valid UTF-8. I think the answer is yes, but I don't really know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wanted to note that gethostname
returns CStr
. So to be consistent I would either change both to String
or both to CStr
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I would think that gethostname
should return a Result<String>
instead of returning it in an output argument as a CStr
. @posborne Any comments here?
@nix-rust/nix-maintainers Any comments on the APIs I've exposed here. Unlike with |
3b71257
to
96a7553
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this module could benefit from some type-safety, and it would be nice to eliminate the extra data copy in ptsname_r
src/pty.rs
Outdated
/// `grantpt()` changezs the mode and owner of the slave pseudoterminal device corresponding to the | ||
/// master pseudoterminal referred to by `fd`. This is a necessary step towards opening the slave. | ||
#[inline] | ||
pub fn grantpt(fd: RawFd) -> Result<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about creating a Newtype like struct PseudoTerminal(RawFd)
to provide some static type checking on these functions? It could also close itself on Drop
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, interesting. Would this be as simple as implementing that type and also impl From<RawFd>
for it? We'd need to make sure that this can be easily used both with std
and other nix
functions. Additionally, is this sound in the context of #594?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it should implement From<RawFd>
, because that would make it too easy to accidentally convert an inappropriate value. Conversion should always be explicit. In fact, are there any other Rust functions, in nix
or in std
, that deal with pseudoterminals?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I agree, maybe just AsRawFd
is a better trait instead? But no, nothing else really deals with this. I have some code in my serialport-rs library here that deals with this same thing, and that's like the only other thing working with safe ptty abstractions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AsRawFd
sounds good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So in continuing to think about this, I don't think it makes sense to provide an abstraction over RawFd
. The use case of these ttys is generally what you can see in serialport-rs
. Most commonly you call read
and write
, both of which take a RawFd
. Additionally, the other functions I'm adding here all take RawFd
. So every call you'd be doing fn(port.as_raw_fd())
, so it's just a needless abstraction that doesn't actually add any safety. I think it's the role of a library like serialport-rs
to provide abstractions on top of nix should it be desired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the newtype implemented Into<RawFd>
? Then fn
could automatically coerce it, as long as fn
's argument is a T: Into<RawFd>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if you implement Into
you also implement From
, that's what my comment was about before. It's also bidirectional, so we're back to where we started. And you'd still need to use .into()
everywhere since not all functions use T: Into<RawFd>
. Since the topic of changing function signatures came up, there are 168 functions in libc
that have fd's as inputs (as c_int
, not RawFd
) and 88 functions in nix
that have RawFd
inputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, From
implies Into
, but not the other way around. If this newtype implements Into<RawFd>
, that does not automatically create an implementation for From<Newtype>
on RawFd
. Do you think that typing .into()
is too much work to justify the free type checking on the pty functions? If the newtype implements Drop
, that could save some typing too, because you wouldn't have to automatically close the ptys.
src/pty.rs
Outdated
} | ||
|
||
let name = unsafe { | ||
CStr::from_ptr(name_buf.as_ptr()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The subsequent Cow::into_owned()
will perform a data copy. How about something like this instead?
let mut name_buf = OsString::with_capacity(64);
libc::ptsname(fd, name.to_str().as_ptr(), 64);
return name_buf.into_string()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two things: Is OsString
the right type rather than CString
? The docs don't clarify what the difference between the two really is, so I'm not certain.
Also, that code you provided doesn't compile. because name.to_str().as_ptr()
doesn't return a mutable pointer and I don't see a way to get one. Additionally, I'm not certain how to map the error types. I'm thinking we should add to nix::Error
a StringConversion
type and map the into_string()
failure to that, otherwise I think this should be an unwrap()
or expect()
since I don't think this will ever fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take a look at getcwd
in unistd.rs. It actually uses a Vec
as a buffer, before transforming it into an OsString
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that what you'd recommend doing instead of what you originally suggested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My original suggestion was just a guess, a starting point. I didn't know exactly how to make it work. But getcwd
presents exactly the same problem as this function, so its solution will probably work.
} | ||
|
||
let name = unsafe { | ||
CStr::from_ptr(name_ptr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about a test that calls ptsname
on two separate pseudoterminals, and checks that their names are not the same? That would enforce that this function performs a data copy (as I think it probably should).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't you instead want to call ptsname()
on the same ptty and check that they don't point to the same underlying buffer? I'm unclear what the test you suggested actually tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that could do it. I'm really just trying to think up ways to improve the coverage.
test/test_pty.rs
Outdated
/// Test equivalence of `ptsname` and `ptsname_r` | ||
#[test] | ||
#[cfg(any(target_os = "android", target_os = "linux"))] | ||
fn test_ptsname() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test name is not very accurate.
// Open the slave device | ||
let slave_fd = open(Path::new(&slave_name), O_RDWR, stat::Mode::empty()).unwrap(); | ||
assert!(slave_fd > 0); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test leaks its file descriptors.
test/test_pty.rs
Outdated
let slave_name = ptsname(master_fd).unwrap(); | ||
let slave_name_r = ptsname_r(master_fd).unwrap(); | ||
assert_eq!(slave_name, slave_name_r); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test leaks its file descriptors, too.
I'm still working on how to best return a One question I have is if errors when calling |
e8b7137
to
ea2d03f
Compare
Here's the commit to |
#[test] | ||
#[cfg(any(target_os = "android", target_os = "linux"))] | ||
fn test_ptsname_equivalence() { | ||
// Open a new PTTY master |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/PTTY/PTY here and below
test/test_pty.rs
Outdated
let slave_name1 = ptsname(&master_fd).unwrap(); | ||
let slave_name2 = ptsname(&master_fd).unwrap(); | ||
assert!(slave_name1 == slave_name2); | ||
assert!(slave_name1.as_ptr() != slave_name2.as_ptr()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assertion probably deserves a comment. It's not obvious why a consumer should care about this behavior.
The new type looks nice, and it fixes all of the file descriptor leaks in the tests. The integration into serialport-rs looks good too. I have conflicting feelings about errors during |
aa9d724
to
51f7199
Compare
Yes, we need to explicitly forget the type. This is actually done in Alright, I think this really should be everything that we need. |
Did you ever figure out a way to reduce the data copies in |
Whoops! I knew I was forgetting something. I'll hop on IRC again today and see if I can figure out the "right" way to do this. |
Popped onto IRC and found a reasonable implementation that should be pretty efficient. It's still not clear if |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like some debugging code accidentally made it into the PR
src/lib.rs
Outdated
Error::Sys(errno) => io::Error::from_raw_os_error(errno as i32), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you meant to leave a space here
src/pty.rs
Outdated
pub fn ptsname_r(fd: &PtyMaster) -> Result<String> { | ||
let mut name_buf = vec![0u8; 64]; | ||
let name_buf_ptr = name_buf.as_mut_ptr() as *mut libc::c_char; | ||
println!("{}/{}", name_buf.len(), name_buf.capacity()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this debugging code
src/pty.rs
Outdated
if unsafe { libc::ptsname_r(fd.as_raw_fd(), name_buf_ptr, name_buf.capacity()) } != 0 { | ||
return Err(Error::last().into()); | ||
} | ||
println!("{}/{}", name_buf.len(), name_buf.capacity()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one too.
* grantpt * ptsname/ptsname_r * posix_openpt * unlockpt
Sorry, I thought I did one last look over of it, must have forgot! Okay, maybe now this is looking finalized. |
bors r+ |
556: Add various pty functions r=asomers * grantpt * posix_openpt * ptsname/ptsname_r * unlockpt I've refactored my code to use these functions and they work correctly at least in the non-error case. Only issue is what to do for failure in `ptsname` when converting strings. This should effectively never happen. I think maybe adding an `Unknown` to the `Error` type and returning that might be useful.
Build succeeded |
I've refactored my code to use these functions and they work correctly at least in the non-error case. Only issue is what to do for failure in
ptsname
when converting strings. This should effectively never happen. I think maybe adding anUnknown
to theError
type and returning that might be useful.